package wacc

import wacc.ast.Position
import wacc.types._
import java.io.File

private val SYNTAX_ERROR = 100
private val SEMANTIC_ERROR = 200

/** Error types for semantic errors
  */
enum Error {
  case DuplicateDeclaration(ident: ast.Ident)
  case UndeclaredVariable(ident: ast.Ident)
  case UndefinedFunction(ident: ast.Ident)

  case FunctionParamsMismatch(ident: ast.Ident, expected: Int, got: Int, funcType: FuncType)
  case SemanticError(pos: Position, msg: String)
  case TypeMismatch(pos: Position, expected: SemType, got: SemType, msg: String)
  case InternalError(pos: Position, msg: String)

  case SyntaxError(file: File, msg: String)
}

extension (e: Error) {
  def exitCode: Int = e match {
    case Error.SyntaxError(_, _) => SYNTAX_ERROR
    case _                       => SEMANTIC_ERROR
  }
}

/** Function to handle printing the details of a given semantic error
  *
  * @param error
  *   Error object
  * @param errorContent
  *   Contents of the file to generate code snippets
  */
def formatError(error: Error): String = {
  val sb = new StringBuilder()

  /** Format the file of an error
    *
    * @param file
    *   File of the error
    */
  def formatFile(file: File): Unit = {
    sb.append(s"File: ${file.getCanonicalPath}\n")
  }

  /** Function to format the position of an error
    *
    * @param pos
    *   Position of the error
    */
  def formatPosition(pos: Position): Unit = {
    formatFile(pos.file)
    sb.append(s"(line ${pos.line}, column ${pos.column}):\n")
  }

  /** Function to highlight a section of code for an error message
    *
    * @param pos
    *   Position of the error
    * @param size
    *   Size(in chars) of section to highlight
    */
  def formatHighlight(pos: Position, size: Int): Unit = {
    val lines = os.read(os.Path(pos.file.getCanonicalPath)).split("\n")
    val preLine = if (pos.line > 1) lines(pos.line - 2) else ""
    val midLine = lines(pos.line - 1)
    val postLine = if (pos.line < lines.size) lines(pos.line) else ""
    val linePointer = " " * (pos.column + 2) + ("^" * (size)) + "\n"

    sb.append(
      s"  >$preLine\n  >$midLine\n$linePointer  >$postLine\netscape"
    )
  }

  error match {
    case Error.SyntaxError(_, _) =>
      sb.append("Syntax error:\n")
    case _ =>
      sb.append("Semantic error:\n")
  }

  error match {
    case Error.DuplicateDeclaration(ident) =>
      formatPosition(ident.pos)
      sb.append(s"Duplicate declaration of identifier ${ident.v}\n")
      formatHighlight(ident.pos, ident.v.length)
    case Error.UndeclaredVariable(ident) =>
      formatPosition(ident.pos)
      sb.append(s"Undeclared variable ${ident.v}\n")
      formatHighlight(ident.pos, ident.v.length)
    case Error.UndefinedFunction(ident) =>
      formatPosition(ident.pos)
      sb.append(s"Undefined function ${ident.v}\n")
      formatHighlight(ident.pos, ident.v.length)
    case Error.FunctionParamsMismatch(id, expected, got, funcType) =>
      formatPosition(id.pos)
      sb.append(s"Function expects $expected parameters, got $got\n")
      sb.append(
        s"(function ${id.v} has type (${funcType.params.mkString(", ")}) -> ${funcType.returnType})\n"
      )
      formatHighlight(id.pos, 1)
    case Error.TypeMismatch(pos, expected, got, msg) =>
      formatPosition(pos)
      sb.append(s"Type mismatch: $msg\nExpected: $expected\nGot: $got\n")
      formatHighlight(pos, 1)
    case Error.SemanticError(pos, msg) =>
      formatPosition(pos)
      sb.append(msg + "\n")
      formatHighlight(pos, 1)
    case wacc.Error.InternalError(pos, msg) =>
      formatPosition(pos)
      sb.append(s"Internal error: $msg\n")
      formatHighlight(pos, 1)
    case Error.SyntaxError(file, msg) =>
      formatFile(file)
      sb.append(msg + "\n")
      sb.append("\n")
  }

  sb.toString()
}
