/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package org.apache.geode.test.process;

import java.util.concurrent.TimeUnit;

/**
 * Starts the stdout and stderr reader threads for a running process. Provides a mechanism to
 * waitFor the process to terminate.
 * <p>
 * Extracted from ProcessWrapper.
 *
 */
public class ProcessOutputReader {

  private boolean started;

  private final Process process;
  private final ProcessStreamReader stdout;
  private final ProcessStreamReader stderr;

  public ProcessOutputReader(final Process process, final ProcessStreamReader stdout,
      final ProcessStreamReader stderr) {
    this.process = process;
    this.stdout = stdout;
    this.stderr = stderr;
  }

  public void start() {
    synchronized (this) {
      stdout.start();
      stderr.start();
      started = true;
    }
  }

  public boolean waitFor(final long timeout, final TimeUnit unit) throws InterruptedException {
    synchronized (this) {
      if (!started) {
        throw new IllegalStateException("Must be started before waitFor");
      }
    }

    final long startTime = System.nanoTime();

    long millisToJoin = unit.toMillis(timeout);
    stderr.join(millisToJoin);

    long nanosRemaining = unit.toNanos(timeout) - (System.nanoTime() - startTime);
    millisToJoin = unit.toMillis(nanosRemaining);
    stdout.join(millisToJoin);

    nanosRemaining = unit.toNanos(timeout) - (System.nanoTime() - startTime);
    return waitForProcess(nanosRemaining, unit);
  }

  private boolean waitForProcess(final long timeout, final TimeUnit unit)
      throws InterruptedException {
    long startTime = System.nanoTime();
    long nanosRemaining = unit.toNanos(timeout);

    while (nanosRemaining > 0) {
      try {
        process.exitValue();
        return true;
      } catch (IllegalThreadStateException ex) {
        if (nanosRemaining > 0) {
          long millisToSleep = Math.min(TimeUnit.NANOSECONDS.toMillis(nanosRemaining) + 1, 100);
          Thread.sleep(millisToSleep);
        }
      }
      nanosRemaining = unit.toNanos(timeout) - (System.nanoTime() - startTime);
    }
    return false;
  }
}
