// TLCockpit
// Copyright 2017-2018 Norbert Preining
// Licensed according to GPLv3+
//
// Front end for tlmgr

package TeXLive

import java.io._

import TeXLive.OsTools._
import com.typesafe.scalalogging.LazyLogging

import scala.collection.mutable.ArrayBuffer
import scala.concurrent.SyncVar
import scala.sys.process._

class TlmgrProcess(updout: String => Unit, upderr: String => Unit)  extends LazyLogging  {
  val inputString = new SyncVar[String]                 // used for the tlmgr process input
  // val outputString = new SyncVar[String]                // used for the tlmgr process output
  // val errorBuffer: StringBuffer = new StringBuffer()    // buffer used for both tmgr process error console AND logging

  val tlroot: String = "kpsewhich -var-value SELFAUTOPARENT".!!.trim
  logger.debug("tlroot ==" + tlroot + "==")

  // set in only one place, in the main thread
  var process: Process = _

  def send_command(input: String): Unit = {
    logger.debug(s"send_command: ${input}")
    try {
      assert(!inputString.isSet)
      inputString.put(input)
    } catch {
      case exc: Throwable =>
        logger.warn("Main thread: " +
          (if (exc.isInstanceOf[NoSuchElementException]) "Timeout" else "Exception: " + exc))
    }
  }

  def isAlive(): Boolean = {
    if (process != null)
      process.isAlive()
    else
      // return true on not-started process
      true
  }

  def start_process(): Boolean = {
    logger.debug("tlmgr process: entering starting process")
    // process creation
    if (process == null) {
      logger.debug("tlmgr process: start_process: creating procIO")
      val procIO = new ProcessIO(inputFn, outputFn(_, updout), outputFn(_, upderr))
      val startCmd =
        if (isWindows) {
          logger.debug("detected Windows, using tlmgr.bat")
          Seq("tlmgr.bat", "-v", "--machine-readable", "shell")
        } else if (isCygwin) {
          logger.debug("detected Cygwin, using bash -l -c tlmgr")
          Seq("bash", "-l", "-c", "tlmgr -v --machine-readable shell")
        } else {
          logger.debug("detected Unixish, using tlmgr")
          Seq("tlmgr", "-v", "--machine-readable", "shell")
        }
      val processBuilder: ProcessBuilder = startCmd
      logger.debug("tlmgr process: start_process: running new tlmgr process")
      process = processBuilder.run(procIO)
    }
    logger.debug("tlmgr process: start_process: checking for being alive")
    process.isAlive()
  }

  def cleanup(): Unit = {
    if (process != null) {
      logger.debug("tlmgr process: cleanup - sending quit")
      send_command("quit")
      logger.debug("tlmgr process: cleanup - sleeping 2s")
      Thread.sleep(1000)
      logger.debug("tlmgr process: cleanup - destroying process")
      process.destroy()
    }
  }

  /* The standard input passing function */
  private[this] def inputFn(stdin: OutputStream): Unit = {
    val writer = new BufferedWriter(new OutputStreamWriter(stdin))
    try {
      var input = ""
      while (true) {
        input = inputString.take()
        if (input == "quit") {
          stdin.close()
          return
        } else {
          writer.write(input + "\n")
          logger.trace("writing " + input + " to process")
          writer.flush()
        }
      }
      stdin.close()
    } catch {
      case exc: Throwable =>
        logger.warn("Exception in inputFn thread: " + exc + "\n")
        stdin.close()
    }
  }

  private[this] def outputFn(outStr: InputStream, updfun: String => Unit): Unit = {
    val reader = new BufferedReader(new InputStreamReader(outStr))
    try {
      var line: String = ""
      while (true) {
        line = reader.readLine
        logger.trace("DDD did read " + line + " from process")
        try {
          updfun(line)
        } catch {
          case exc: Throwable =>
            logger.debug("Update output line function failed, continuing anyway. Exception: " + exc)
        }
      }
      outStr.close()
    } catch {
      case exc: Throwable =>
        logger.warn("Exception in outputFn thread: " + exc + "\n")
        outStr.close()
    }
  }
}