/*
 * Copyright 2000-2017 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 com.intellij.openapi.externalSystem.service.execution;

import com.intellij.build.process.BuildProcessHandler;
import com.intellij.execution.process.AnsiEscapeDecoder;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTask;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.util.io.StreamUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

/**
 * @author Vladislav.Soroka
 */
public class ExternalSystemProcessHandler extends BuildProcessHandler implements AnsiEscapeDecoder.ColoredTextAcceptor, Disposable {
  private static final Logger LOG = Logger.getInstance(ExternalSystemProcessHandler.class);
  private final String myExecutionName;
  @Nullable
  private ExternalSystemTask myTask;
  private final AnsiEscapeDecoder myAnsiEscapeDecoder = new AnsiEscapeDecoder();
  @Nullable
  private OutputStream myProcessInput;

  public ExternalSystemProcessHandler(@NotNull ExternalSystemTask task, String executionName) {
    myTask = task;
    myExecutionName = executionName;
    if (task instanceof UserDataHolder) {
      try {
        PipedInputStream inputStream = new MyPipedInputStream();
        myProcessInput = new MyPipedOutputStream(inputStream);
        ((UserDataHolder)task).putUserData(ExternalSystemRunConfiguration.RUN_INPUT_KEY, inputStream);
      }
      catch (IOException e) {
        LOG.warn("Unable to setup process input", e);
      }
    }
  }

  @Override
  public String getExecutionName() {
    return myExecutionName;
  }

  @Override
  public void notifyTextAvailable(@NotNull final String text, @NotNull final Key outputType) {
    myAnsiEscapeDecoder.escapeText(text, outputType, this);
  }

  @Override
  protected void destroyProcessImpl() {
    ExternalSystemTask task = myTask;
    if (task != null) {
      task.cancel();
    }
    closeInput();
  }

  @Override
  protected void detachProcessImpl() {
    notifyProcessDetached();
    closeInput();
  }

  @Override
  public boolean detachIsDefault() {
    return false;
  }

  @Nullable
  @Override
  public OutputStream getProcessInput() {
    return myProcessInput;
  }

  @Override
  public void notifyProcessTerminated(int exitCode) {
    super.notifyProcessTerminated(exitCode);
    closeInput();
  }

  @Override
  public void coloredTextAvailable(@NotNull String text, @NotNull Key attributes) {
    super.notifyTextAvailable(text, attributes);
  }

  protected void closeInput() {
    ExternalSystemTask task = myTask;
    if (task instanceof UserDataHolder) {
      ((UserDataHolder)task).putUserData(ExternalSystemRunConfiguration.RUN_INPUT_KEY, null);
    }
    StreamUtil.closeStream(myProcessInput);
    myProcessInput = null;
  }

  @Override
  public void dispose() {
    try {
      detachProcessImpl();
    }
    finally {
      myTask = null;
    }
  }

  /**
   * @see <a href="http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4545831">JDK-4545831: PipedInputStream performance problems</a>
   * @see <a href="https://bugs.openjdk.java.net/browse/JDK-8014239">JDK-8014239: PipedInputStream not notifying waiting readers on receive</a>
   */
  private static class MyPipedInputStream extends PipedInputStream {
    MyPipedInputStream() {
      super();
    }

    @Override
    public synchronized int read(byte[] b, int off, int len) throws IOException {
      try {
        return super.read(b, off, len);
      }
      finally {
        //noinspection SynchronizeOnThis
        notifyAll();
      }
    }
  }

  /**
   * @see <a href="https://bugs.openjdk.java.net/browse/JDK-8014239">JDK-8014239: PipedInputStream not notifying waiting readers on receive</a>
   */
  private static class MyPipedOutputStream extends PipedOutputStream {
    MyPipedOutputStream(PipedInputStream snk) throws IOException {
      super(snk);
    }

    @Override
    public void write(int b) throws IOException {
      super.write(b);
      flush();
    }

    @Override
    public void write(byte[] b, int off, int len) throws IOException {
      super.write(b, off, len);
      flush();
    }

    @Override
    public void write(@NotNull byte[] b) throws IOException {
      write(b, 0, b.length);
    }
  }
}

@@ -1167,27 +1167,17 @@
 java.io.
-IOException
+*
 ;%0Aimport
@@ -1186,23 +1186,29 @@
 ava.
+n
 io.
-OutputStream
+channels.Channels
 ;%0Aim
@@ -1221,61 +1221,25 @@
 ava.
+n
 io.
-PipedInputStream;%0Aimport java.io.PipedOutputStream
+channels.Pipe
 ;%0A%0A/
@@ -1934,25 +1934,53 @@
         Pipe
-d
+ pipe = Pipe.open();%0A        
 InputStream 
@@ -1997,21 +1997,22 @@
 m = new 
-MyPip
+Buffer
 edInputS
@@ -2017,16 +2017,54 @@
 tStream(
+Channels.newInputStream(pipe.source())
 );%0A     
@@ -2087,21 +2087,22 @@
 t = new 
-MyPip
+Buffer
 edOutput
@@ -2108,27 +2108,53 @@
 tStream(
-inputStream
+Channels.newOutputStream(pipe.sink())
 );%0A     
@@ -3716,24 +3716,24 @@
   finally %7B%0A
+
       myTask
@@ -3754,1351 +3754,7 @@
 %0A  %7D
-%0A%0A  /**%0A   * @see %3Ca href=%22http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4545831%22%3EJDK-4545831: PipedInputStream performance problems%3C/a%3E%0A   * @see %3Ca href=%22https://bugs.openjdk.java.net/browse/JDK-8014239%22%3EJDK-8014239: PipedInputStream not notifying waiting readers on receive%3C/a%3E%0A   */%0A  private static class MyPipedInputStream extends PipedInputStream %7B%0A    MyPipedInputStream() %7B%0A      super();%0A    %7D%0A%0A    @Override%0A    public synchronized int read(byte%5B%5D b, int off, int len) throws IOException %7B%0A      try %7B%0A        return super.read(b, off, len);%0A      %7D%0A      finally %7B%0A        //noinspection SynchronizeOnThis%0A        notifyAll();%0A      %7D%0A    %7D%0A  %7D%0A%0A  /**%0A   * @see %3Ca href=%22https://bugs.openjdk.java.net/browse/JDK-8014239%22%3EJDK-8014239: PipedInputStream not notifying waiting readers on receive%3C/a%3E%0A   */%0A  private static class MyPipedOutputStream extends PipedOutputStream %7B%0A    MyPipedOutputStream(PipedInputStream snk) throws IOException %7B%0A      super(snk);%0A    %7D%0A%0A    @Override%0A    public void write(int b) throws IOException %7B%0A      super.write(b);%0A      flush();%0A    %7D%0A%0A    @Override%0A    public void write(byte%5B%5D b, int off, int len) throws IOException %7B%0A      super.write(b, off, len);%0A      flush();%0A    %7D%0A%0A    @Override%0A    public void write(@NotNull byte%5B%5D b) throws IOException %7B%0A      write(b, 0, b.length);%0A    %7D%0A  %7D
 %0A%7D%0A

change Piped*Stream to NIO Pipe IDEA-199623