package amyc.utils

import java.io.File
import scala.io.Source

// Reports errors and warnings during compilation
class Reporter:

  /** Issues some information from the compiler */
  def info(msg: Any, pos: Position = NoPosition): Unit =
    report("[ Info  ]", msg, pos)

  /** Issues a warning from the compiler */
  def warning(msg: Any, pos: Position = NoPosition): Unit =
    report("[Warning]", msg, pos)

  private var hasErrors = false

  /** Issues a recoverable error message */
  def error(msg: Any, pos: Position = NoPosition): Unit =
    hasErrors = true
    report("[ Error ]", msg, pos)

  /** Used for an unrecoverable error: Issues a message, then exits the compiler */
  def fatal(msg: Any, pos: Position = NoPosition): Nothing =
    report("[ Fatal ]", msg, pos)
    // Despite printing the message, we store it in the error for testing
    val errMsg = s"$pos: $msg"
    throw AmycFatalError(errMsg)

  // Versions for Positioned
  def info(msg: Any, pos: Positioned): Unit = info(msg, pos.position)
  def warning(msg: Any, pos: Positioned): Unit = warning(msg, pos.position)
  def error(msg: Any, pos: Positioned): Unit = error(msg, pos.position)
  def fatal(msg: Any, pos: Positioned): Nothing = fatal(msg, pos.position)


  /** Terminates the compiler if any errors have been detected. */
  def terminateIfErrors() =
    if hasErrors then
      fatal("There were errors.")

  private def err(msg: String): Unit =
    Console.err.println(msg)

  private def report(prefix: String, msg: Any, pos: Position): Unit =
    if pos.isDefined then
      err(s"$prefix $pos: $msg")

      val lines = getLines(pos.file)

      if pos.line > 0 && pos.line-1 < lines.size then
        err(s"$prefix ${lines(pos.line-1)}")
        err(prefix + " " + " "*(pos.col - 1)+"^")
      else
        err(s"$prefix <line unavailable in source file>")
    else
      err(s"$prefix $msg")

  private var filesToLines = Map[File, IndexedSeq[String]]()

  private def getLines(f: File): IndexedSeq[String] =
    filesToLines.get(f) match
      case Some(lines) =>
        lines

      case None =>
        val source = Source.fromFile(f).withPositioning(true)
        val lines = source.getLines().toIndexedSeq
        source.close()

        filesToLines += f -> lines

        lines
