package wacc

import scala.collection.mutable
import cats.data.Chain
import wacc.ast.Position

private class LabelGenerator {
  import assemblyIR._
  import microWacc.{CallTarget, Ident, Builtin}
  import asmGenerator.escaped

  private val strings = mutable.HashMap[String, String]()
  private val files = mutable.HashMap[String, Int]()
  private var labelVal = -1
  private var permittedFuncFile: Option[String] = None

  /** Get an arbitrary label. */
  def getLabel(): String = {
    labelVal += 1
    s".L$labelVal"
  }

  private def getLabel(target: CallTarget | RuntimeError): String = target match {
    case Ident(v, guid)    => s"wacc_${v}_$guid"
    case Builtin(name)     => s"_$name"
    case err: RuntimeError => s".L.${err.name}"
  }

  /** Get a named label def for a function or error. */
  def getLabelDef(target: CallTarget | RuntimeError): LabelDef =
    LabelDef(getLabel(target))

  /** Get a named label for a function or error. */
  def getLabelArg(target: CallTarget | RuntimeError): LabelArg =
    LabelArg(getLabel(target))

  /** Get an arbitrary label for a string. */
  def getLabelArg(str: String): LabelArg =
    LabelArg(strings.getOrElseUpdate(str, s".L.str${strings.size}"))

  /** Get a named label for a string. */
  def getLabelArg(src: String, name: String): LabelArg =
    LabelArg(strings.getOrElseUpdate(src, s".L.$name.str${strings.size}"))

  /** Get a debug directive for a file. */
  def getDebugFile(file: java.io.File): Int =
    files.getOrElseUpdate(file.getCanonicalPath, files.size)

  /** Get a debug directive for a function. */
  def getDebugFunc(pos: Position, name: String, label: LabelDef): Chain[AsmLine] = {
    permittedFuncFile match {
      case Some(f) if f != pos.file.getCanonicalPath => Chain.empty
      case _ =>
        val customLabel = if name == "main" then Chain.empty else Chain(LabelDef(name))
        permittedFuncFile = Some(pos.file.getCanonicalPath)
        customLabel ++ Chain(
          Directive.Location(getDebugFile(pos.file), pos.line, None),
          Directive.Type(label, SymbolType.Function),
          Directive.Func(name, label)
        )
    }
  }

  /** Generate the assembly labels for constants that were labelled using the LabelGenerator. */
  def generateConstants: Chain[AsmLine] =
    strings.foldLeft(Chain.empty) { case (acc, (str, label)) =>
      acc ++ Chain(
        LabelDef(label),
        Directive.Asciz(str.escaped)
      )
    }

  /** Generates debug directives that were created using the LabelGenerator. */
  def generateDebug: Chain[AsmLine] =
    files.foldLeft(Chain.empty) { case (acc, (file, no)) =>
      acc :+ Directive.File(no, file)
    }
}
