/*
 * Copyright 2000-2011 JetBrains s.r.o.
 *
 * Licensed 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 jetbrains.buildServer.buildTriggers.vcs.clearcase.process;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;

import jetbrains.buildServer.serverSide.TeamCityProperties;

import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.VcsException;

public abstract class InteractiveProcess implements InteractiveProcessFacade {
  private final InputStream myInput;
  private final OutputStream myOutput;
  private static final int ERROR_READING_SLEEP_MILLIS = TeamCityProperties.getInteger("clearcase.error.reading.sleep", 100);

  public InteractiveProcess(final InputStream inputStream, final OutputStream outputStream) {
    myInput = inputStream;
    myOutput = outputStream;
  }

  public void destroy() throws IOException {
    try {
      myInput.close();
      executeQuitCommand();
      myOutput.close();
    } finally {
      destroyOSProcess();
    }
  }

  protected abstract void destroyOSProcess();

  protected void executeQuitCommand() throws IOException {
    
  }

  public InputStream executeAndReturnProcessInput(final String[] params) throws IOException {
  	cleanStreams(myInput, getErrorStream()); //discard unread bytes produced by previous command to prevent phantom errors appeariance   
    execute(params);
    try {
      return readFromProcessInput(params);
    } catch (VcsException e) {
      throw new IOException(e.getLocalizedMessage());
    }
  }

  protected void execute(String[] args) throws IOException {
    for (String arg : args) {
      myOutput.write(' ');
      // file path must be quoted for interactive execution if it contains single ' (see http://devnet.jetbrains.net/thread/292758 for details)
      myOutput.write('"');
      myOutput.write(arg.getBytes());
      myOutput.write('"');
    }
    myOutput.write('\n');
    myOutput.flush();
  }

  private InputStream readFromProcessInput(final String[] params) throws IOException, VcsException {
    while (true) {
      if (myInput.available() > 0) break;
      if (getErrorStream().available() > 0) {
        throw new VcsException(readError());
      }      
    }
    final File tempFile = FileUtil.createTempFile("cc", "execution");
    try {
      final FileOutputStream fileOutput = new FileOutputStream(tempFile);
      try {
        final BufferedReader reader = new BufferedReader(new InputStreamReader(myInput));
  
        String line;
        while ((line = reader.readLine()) != null) {
          lineRead(line);
          if (isEndOfCommandOutput(line, params)) {
            break;
          }
          fileOutput.write(line.getBytes());
          fileOutput.write('\n');
        }
        fileOutput.flush();
      } finally {
        fileOutput.close();
      }
    } catch (IOException e) {
      FileUtil.delete(tempFile);
      throw e;
    }

    final FileInputStream fileInput = new FileInputStream(tempFile);

    return new InputStream() {
      public int read() throws IOException {
        return fileInput.read();
      }


      public void close() throws IOException {
        fileInput.close();
        FileUtil.delete(tempFile);
      }
    };
  }

  private void cleanStreams(InputStream... streams) throws IOException {
    for (final InputStream stream : streams) {
      if (stream.available() > 0) {
        while (stream.read() != -1) {
          if (stream.available() == 0) {
            break;
          }
        }
      }
    }
  }

  protected abstract boolean isEndOfCommandOutput(final String line, final String[] params) throws IOException;

  protected void lineRead(final String line) {
  }

  protected String readError() throws IOException {
    final InputStream errorStream = getErrorStream();
    final StringBuffer result = new StringBuffer();

    int available = errorStream.available();

    do {
      final byte[] read = new byte[available];
      //noinspection ResultOfMethodCallIgnored
      errorStream.read(read);
      result.append(new String(read));
      try {
        Thread.sleep(ERROR_READING_SLEEP_MILLIS);
      } catch (InterruptedException e) {
        //ignore
      }
      available = errorStream.available();
    } while (available > 0);

    return result.toString();
  }

  protected abstract InputStream getErrorStream();
}
