/* NSC -- new Scala compiler
 * Copyright 2005-2013 LAMP/EPFL
 * @author Stepan Koltsov
 */

package scala.tools.nsc
package interpreter

import java.io.IOException
import session.History
import InteractiveReader._
import Properties.isMac

/** Reads lines from an input stream */
trait InteractiveReader {
  def postInit(): Unit = {}

  val interactive: Boolean

  def reset(): Unit
  def history: History
  def completion: Completion
  def redrawLine(): Unit

  def readYesOrNo(prompt: String, alt: => Boolean): Boolean = readOneKey(prompt) match {
    case 'y'  => true
    case 'n'  => false
    case -1   => false // EOF
    case _    => alt
  }

  protected def readOneLine(prompt: String): String
  protected def readOneKey(prompt: String): Int

  def readLine(prompt: String): String =
    // hack necessary for OSX jvm suspension because read calls are not restarted after SIGTSTP
    if (isMac) restartSysCalls(readOneLine(prompt), reset())
    else readOneLine(prompt)
}

object InteractiveReader {
  val msgEINTR = "Interrupted system call"
  def restartSysCalls[R](body: => R, reset: => Unit): R =
    try body catch {
      case e: IOException if e.getMessage == msgEINTR => reset ; body
    }

  def apply(): InteractiveReader = SimpleReader()

  // a non-interactive InteractiveReader that returns the given text
  def apply(text: String): InteractiveReader = SimpleReader(text)

  @deprecated("Use `apply` instead.", "2.9.0")
  def createDefault(): InteractiveReader = apply() // used by sbt
}

/** Collect one line of user input from the supplied reader.
 *  Runs on a new thread while the REPL is initializing on the main thread.
 *
 *  The user can enter text or a `:paste` command.
 */
class SplashLoop(reader: InteractiveReader, prompt: String) extends Runnable {
  import java.util.concurrent.SynchronousQueue
  import scala.compat.Platform.EOL

  private val result = new SynchronousQueue[Option[String]]
  @volatile private var running: Boolean = _
  private var thread: Thread = _

  /** Read one line of input which can be retrieved with `line`. */
  def run(): Unit = {
    var line = ""
    try
      do {
        line = reader.readLine(prompt)
        if (line != null) {
          line = process(line.trim)
        }
      } while (line != null && line.isEmpty && running)
    finally {
      result.put(Option(line))
    }
  }

  /** Check for `:paste` command. */
  private def process(line: String): String = {
    def isPrefix(s: String, p: String, n: Int) = (
      //s != null && p.inits.takeWhile(_.length >= n).exists(s == _)
      s != null && s.length >= n && s.length <= p.length && s == p.take(s.length)
    )
    if (isPrefix(line, ":paste", 3)) {
      // while collecting lines, check running flag
      var help = f"// Entering paste mode (ctrl-D to finish)%n%n"
      def readWhile(cond: String => Boolean) = {
        Iterator continually reader.readLine(help) takeWhile { x =>
          help = ""
          x != null && cond(x)
        }
      }
      val text = (readWhile(_ => running) mkString EOL).trim
      val next =
        if (text.isEmpty) "// Nothing pasted, nothing gained."
        else "// Exiting paste mode, now interpreting."
      Console println f"%n${next}%n"
      text
    } else {
      line
    }
  }

  def start(): Unit = result.synchronized {
    require(thread == null, "Already started")
    thread = new Thread(this)
    thread.setDaemon(true)
    running = true
    thread.start()
  }

  def stop(): Unit = result.synchronized {
    running = false
    if (thread != null) thread.interrupt()
    thread = null
  }

  /** Block for the result line, or null on ctrl-D. */
  def line: String = result.take.orNull
}
object SplashLoop {
  def apply(reader: SplashReader, prompt: String): SplashLoop = new SplashLoop(reader, prompt)
}

/** Reader during splash. Handles splash-completion with a stub, otherwise delegates. */
class SplashReader(reader: InteractiveReader, postIniter: InteractiveReader => Unit) extends InteractiveReader {
  /** Invoke the postInit action with the underlying reader. */
  override def postInit(): Unit = postIniter(reader)

  override val interactive: Boolean = reader.interactive

  override def reset(): Unit = reader.reset()
  override def history: History = reader.history
  override val completion: Completion = NoCompletion
  override def redrawLine(): Unit = reader.redrawLine()

  override protected[interpreter] def readOneLine(prompt: String): String = ???   // unused
  override protected[interpreter] def readOneKey(prompt: String): Int     = ???   // unused

  override def readLine(prompt: String): String = reader.readLine(prompt)
}
object SplashReader {
  def apply(reader: InteractiveReader)(postIniter: InteractiveReader => Unit) =
    new SplashReader(reader, postIniter)
}
