package wacc

import cats.data.Chain
import wacc.assemblyIR._

sealed trait RuntimeError {
  val name: String
  protected val errStr: String

  protected def getErrLabel(using labelGenerator: LabelGenerator): LabelArg =
    labelGenerator.getLabelArg(errStr, name = name)

  protected def generateHandler(using labelGenerator: LabelGenerator): Chain[AsmLine]

  def generate(using labelGenerator: LabelGenerator): Chain[AsmLine] =
    labelGenerator.getLabelDef(this) +: generateHandler
}

object RuntimeError {
  import wacc.asmGenerator.stackAlign
  import assemblyIR.commonRegisters._

  private val ERROR_CODE = 255

  case object ZeroDivError extends RuntimeError {
    val name = "errDivZero"
    protected val errStr = "fatal error: division or modulo by zero"

    protected def generateHandler(using labelGenerator: LabelGenerator): Chain[AsmLine] = Chain(
      stackAlign,
      Load(RDI, MemLocation(RIP, getErrLabel)),
      assemblyIR.Call(CLibFunc.PrintF),
      Move(RDI, ImmediateVal(-1)),
      assemblyIR.Call(CLibFunc.Exit)
    )

  }

  case object BadChrError extends RuntimeError {
    val name = "errBadChr"
    protected val errStr = "fatal error: int %d is not an ASCII character 0-127"

    protected def generateHandler(using labelGenerator: LabelGenerator): Chain[AsmLine] = Chain(
      Pop(RSI),
      stackAlign,
      Load(RDI, MemLocation(RIP, getErrLabel)),
      assemblyIR.Call(CLibFunc.PrintF),
      Move(RDI, ImmediateVal(ERROR_CODE)),
      assemblyIR.Call(CLibFunc.Exit)
    )

  }

  case object NullPtrError extends RuntimeError {
    val name = "errNullPtr"
    protected val errStr = "fatal error: null pair dereferenced or freed"

    protected def generateHandler(using labelGenerator: LabelGenerator): Chain[AsmLine] = Chain(
      stackAlign,
      Load(RDI, MemLocation(RIP, getErrLabel)),
      assemblyIR.Call(CLibFunc.PrintF),
      Move(RDI, ImmediateVal(ERROR_CODE)),
      assemblyIR.Call(CLibFunc.Exit)
    )

  }

  case object OverflowError extends RuntimeError {
    val name = "errOverflow"
    protected val errStr = "fatal error: integer overflow or underflow occurred"

    protected def generateHandler(using labelGenerator: LabelGenerator): Chain[AsmLine] = Chain(
      stackAlign,
      Load(RDI, MemLocation(RIP, getErrLabel)),
      assemblyIR.Call(CLibFunc.PrintF),
      Move(RDI, ImmediateVal(ERROR_CODE)),
      assemblyIR.Call(CLibFunc.Exit)
    )

  }

  case object OutOfBoundsError extends RuntimeError {
    val name = "errOutOfBounds"
    protected val errStr = "fatal error: array index %d out of bounds"

    protected def generateHandler(using labelGenerator: LabelGenerator): Chain[AsmLine] = Chain(
      Move(RSI, RCX),
      stackAlign,
      Load(RDI, MemLocation(RIP, getErrLabel)),
      assemblyIR.Call(CLibFunc.PrintF),
      Move(RDI, ImmediateVal(ERROR_CODE)),
      assemblyIR.Call(CLibFunc.Exit)
    )
  }

  case object OutOfMemoryError extends RuntimeError {
    val name = "errOutOfMemory"
    protected val errStr = "fatal error: out of memory"

    def generateHandler(using labelGenerator: LabelGenerator): Chain[AsmLine] = Chain(
      stackAlign,
      Load(RDI, MemLocation(RIP, getErrLabel)),
      assemblyIR.Call(CLibFunc.PrintF),
      Move(RDI, ImmediateVal(ERROR_CODE)),
      assemblyIR.Call(CLibFunc.Exit)
    )
  }

  val all: Chain[RuntimeError] =
    Chain(ZeroDivError, BadChrError, NullPtrError, OverflowError, OutOfBoundsError,
      OutOfMemoryError)
}
