package wacc

import cats.data.Chain

object microWacc {
  import wacc.types._

  sealed trait CallTarget(val retTy: SemType)
  sealed trait Expr(val ty: SemType)
  sealed trait LValue extends Expr

  // Atomic expressions
  case class IntLiter(v: Int) extends Expr(KnownType.Int)
  case class BoolLiter(v: Boolean) extends Expr(KnownType.Bool)
  case class CharLiter(v: Char) extends Expr(KnownType.Char)
  case class ArrayLiter(elems: List[Expr])(ty: SemType) extends Expr(ty)
  case class NullLiter()(ty: SemType) extends Expr(ty)
  case class Ident(name: String, uid: Int)(identTy: SemType)
      extends Expr(identTy)
      with CallTarget(identTy)
      with LValue
  case class ArrayElem(value: LValue, index: Expr)(ty: SemType) extends Expr(ty) with LValue

  // Operators
  case class UnaryOp(x: Expr, op: UnaryOperator)(ty: SemType) extends Expr(ty)
  enum UnaryOperator {
    case Negate
    case Not
    case Len
    case Ord
    case Chr
  }
  case class BinaryOp(x: Expr, y: Expr, op: BinaryOperator)(ty: SemType) extends Expr(ty)
  enum BinaryOperator {
    case Add
    case Sub
    case Mul
    case Div
    case Mod
    case Greater
    case GreaterEq
    case Less
    case LessEq
    case Eq
    case Neq
    case And
    case Or
  }
  object BinaryOperator {
    def fromAst(op: ast.BinaryOp): BinaryOperator = op match {
      case _: ast.Add       => Add
      case _: ast.Sub       => Sub
      case _: ast.Mul       => Mul
      case _: ast.Div       => Div
      case _: ast.Mod       => Mod
      case _: ast.Greater   => Greater
      case _: ast.GreaterEq => GreaterEq
      case _: ast.Less      => Less
      case _: ast.LessEq    => LessEq
      case _: ast.Eq        => Eq
      case _: ast.Neq       => Neq
      case _: ast.And       => And
      case _: ast.Or        => Or
    }
  }

  // Statements
  sealed trait Stmt

  case class Builtin(val name: String)(retTy: SemType) extends CallTarget(retTy) {
    override def toString(): String = name
  }
  object Builtin {
    object Read extends Builtin("read")(?)
    object Printf extends Builtin("printf")(?)
    object Exit extends Builtin("exit")(?)
    object Free extends Builtin("free")(?)
    object Malloc extends Builtin("malloc")(?)
    object PrintCharArray extends Builtin("printCharArray")(?)
  }

  case class Assign(lhs: LValue, rhs: Expr) extends Stmt
  case class If(cond: Expr, thenBranch: Chain[Stmt], elseBranch: Chain[Stmt]) extends Stmt
  case class While(cond: Expr, body: Chain[Stmt]) extends Stmt
  case class Call(target: CallTarget, args: List[Expr]) extends Stmt with Expr(target.retTy)
  case class Return(expr: Expr) extends Stmt

  // Program
  case class FuncDecl(name: Ident, params: List[Ident], body: Chain[Stmt])
  case class Program(funcs: Chain[FuncDecl], stmts: Chain[Stmt])
}
