// 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()
   }
 }
}