/*
 * 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.internal;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


/**
 * A ProcessOutputReader will read both stdout and stderr from a {@link java.lang.Process} process.
 * The constructor does not return until all output from the process has been read and the process
 * has exited.
 *
 *
 */
public class ProcessOutputReader {
  private int exitCode;
  private final String output;

  /**
   * Creates a process output reader for the given process.
   *
   * @param p the process whose output should be read.
   */
  public ProcessOutputReader(final Process p) {
    final List lines = Collections.synchronizedList(new ArrayList());

    class ProcessStreamReader extends Thread {
      private final BufferedReader reader;
      public int linecount = 0;

      public ProcessStreamReader(InputStream stream) {
        reader = new BufferedReader(new InputStreamReader(stream));
      }

      @Override
      public void run() {
        try {
          String line;
          while ((line = reader.readLine()) != null) {
            linecount++;
            lines.add(line);
          }
          reader.close();
        } catch (Exception ignored) {
        }
      }
    }

    ProcessStreamReader stdout = new ProcessStreamReader(p.getInputStream());
    ProcessStreamReader stderr = new ProcessStreamReader(p.getErrorStream());
    stdout.start();
    stderr.start();
    try {
      stderr.join();
    } catch (InterruptedException ignore) {
      Thread.currentThread().interrupt();
    }
    try {
      stdout.join();
    } catch (InterruptedException ignore) {
      Thread.currentThread().interrupt();
    }
    exitCode = 0;
    int retryCount = 9;
    while (retryCount > 0) {
      retryCount--;
      try {
        exitCode = p.exitValue();
        break;
      } catch (IllegalThreadStateException e) {
        // due to bugs in Process we may not be able to get
        // a process's exit value.
        // We can't use Process.waitFor() because it can hang forever
        if (retryCount == 0) {
          if (stderr.linecount > 0) {
            // The process wrote to stderr so manufacture
            // an error exist code
            lines.add(
                "Failed to get exit status and it wrote to stderr so setting exit status to 1.");
            exitCode = 1;
          }
        } else {
          // We need to wait around to give a chance for
          // the child to be reaped.See bug 19682
          try {
            Thread.sleep(1000);
          } catch (InterruptedException ignore) {
            Thread.currentThread().interrupt();
            // TODO a cancellation check here?
          }
        }
      }
    }

    java.io.StringWriter sw = new java.io.StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    for (final Object line : lines) {
      pw.println((String) line);
    }
    pw.close();
    try {
      sw.close();
    } catch (java.io.IOException ignore) {
    }
    StringBuffer buf = sw.getBuffer();
    if (buf != null && buf.length() > 0) {
      output = sw.toString();
    } else {
      output = "";
    }
  }

  /**
   * Gets the process's exit status code. A code equal to 0 indicates all is well.
   */
  public int getExitCode() {
    return exitCode;
  }

  /**
   * Gets everything the process wrote to both stdout and stderr.
   */
  public String getOutput() {
    return output;
  }
}
