import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;

import jdk.jshell.tool.JavaShellToolBuilder;
import java.nio.charset.StandardCharsets;

import org.junit.Test;

class JRepel extends Thread {
  //private final static Logger LOG = Logger.getLogger(JRepel.class.getName());

  private static final char ETX = '\u0003'; // ctrl+c representation in Hexadecimal
  private static final char ENQ = '\u0005';
  private static final char ACK = '\u0006';

  private PipedInputStream inputStreamForOut;
  private PipedOutputStream outputStreamForIn;
  private PipedOutputStream outputStream;
  BufferedReader bufferedReader;
  PrintStream printStream;

  public JRepel() throws IOException {
    PipedInputStream inputStream = new PipedInputStream();
    InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
    bufferedReader = new BufferedReader(inputStreamReader);
    outputStreamForIn = new PipedOutputStream(inputStream);
    printStream = new PrintStream(outputStreamForIn);      
    outputStream = new PipedOutputStream();
    inputStreamForOut = new PipedInputStream(outputStream);
  }

  private static void quietlyClose(Closeable... closeables) {
    for (Closeable closeable : closeables) {
      if (closeable == null) {
        continue;
      }
      try {
        closeable.close();
      } catch (IOException e) {
        //LOG.warn("could not close", e);
      }
    }
  }

  private String consume() throws IOException {
    StringBuilder builder = new StringBuilder();
    int c;
    while ((c = bufferedReader.read()) != -1) {
      char character = (char) c;
      if (character == ENQ || character == ACK || character == ETX) {
        break;
      }
      builder.append(character);
    }
    return builder.toString();
  }

  public String eval(String cmd) throws IOException {
    outputStream.write((cmd + "\n").getBytes(StandardCharsets.UTF_8));
    outputStream.flush();
    String trimmed = cmd.trim();
    /*
     * jshell puts the command we send to it on the output for some reason,
     * so we find the line where the *real* output starts by comparing each
     * line with the original given command.
     */
    for (int i = 0; i < 10; i++) {
      String line = bufferedReader.readLine();
      if (line == null) {
        break;
      }
      if (trimmed.endsWith(line.trim())) {
        break;
      }
    }
    return consume();
  }

  @Override
  public void run() {
    try {
      JavaShellToolBuilder builder = JavaShellToolBuilder.builder() //
          .in(inputStreamForOut, null) //
          .out(printStream) //
          .err(printStream) //
          .promptCapture(true);
      builder.run("--execution", "local");
    } catch (Exception e) {
      //LOG.error("starting jshell tool failed", e);
    } finally {
      System.out.println("closing pipes");
      quietlyClose(inputStreamForOut, outputStreamForIn);
    }
  }
}

public class PipeClosedTest {
  
  @Test
  public void pipeClosedBug() throws IOException {
    JRepel jrep = new JRepel();
    jrep.start();
    jrep.eval("1+1");
    jrep.eval("/exit");
  }
}
