package amyc.ast

import amyc.utils.Positioned

/* A polymorphic module containing definitions of Amy trees.
 *
 * This trait represents either nominal trees (where names have not been resolved)
 * or symbolic trees (where names/qualified names) have been resolved to unique identifiers.
 * This is done by having two type fields within the module,
 * which will be instantiated differently by the two different modules.
 *
 */

trait TreeModule:
  self =>
  
  /* Represents the type for the name for this tree module.
   * (It will be either a plain string, or a unique symbol)
   */
  type Name

  // Represents a name within an module
  type QualifiedName

  // A printer that knows how to print trees in this module.
  // The modules will instantiate it as appropriate
  val printer: Printer { val treeModule: self.type }

  // Common ancestor for all trees
  trait Tree extends Positioned:
    override def toString: String = printer(this)

  // Expressions
  sealed trait Expr extends Tree

  // Variables
  case class Variable(name: Name) extends Expr

  // Literals
  sealed trait Literal[+T] extends Expr { val value: T }
  case class IntLiteral(value: Int) extends Literal[Int]
  case class BooleanLiteral(value: Boolean) extends Literal[Boolean]
  case class StringLiteral(value: String) extends Literal[String]
  case class UnitLiteral() extends Literal[Unit] { val value: Unit = () }

  // Binary operators
  case class Plus(lhs: Expr, rhs: Expr) extends Expr
  case class Minus(lhs: Expr, rhs: Expr) extends Expr
  case class Times(lhs: Expr, rhs: Expr) extends Expr
  case class Div(lhs: Expr, rhs: Expr) extends Expr
  case class Mod(lhs: Expr, rhs: Expr) extends Expr
  case class LessThan(lhs: Expr, rhs: Expr) extends Expr
  case class LessEquals(lhs: Expr, rhs: Expr) extends Expr
  case class And(lhs: Expr, rhs: Expr) extends Expr
  case class Or(lhs: Expr, rhs: Expr) extends Expr
  case class Equals(lhs: Expr, rhs: Expr) extends Expr
  case class Concat(lhs: Expr, rhs: Expr) extends Expr

  // Unary operators
  case class Not(e: Expr) extends Expr
  case class Neg(e: Expr) extends Expr

  // Function/constructor call
  case class Call(qname: QualifiedName, args: List[Expr]) extends Expr
  // The ; operator
  case class Sequence(e1: Expr, e2: Expr) extends Expr
  // Local variable definition
  case class Let(df: ParamDef, value: Expr, body: Expr) extends Expr
  // If-then-else
  case class Ite(cond: Expr, thenn: Expr, elze: Expr) extends Expr
  // Pattern matching
  case class Match(scrut: Expr, cases: List[MatchCase]) extends Expr {
    require(cases.nonEmpty)
  }
  // Represents a computational error; prints its message, then exits
  case class Error(msg: Expr) extends Expr

  // Cases and patterns for Match expressions
  case class MatchCase(pat: Pattern, expr: Expr) extends Tree

  abstract class Pattern extends Tree
  case class WildcardPattern() extends Pattern // _
  case class IdPattern(name: Name) extends Pattern // x
  case class LiteralPattern[+T](lit: Literal[T]) extends Pattern // 42, true
  case class CaseClassPattern(constr: QualifiedName, args: List[Pattern]) extends Pattern // C(arg1, arg2)

  // Definitions
  sealed trait Definition extends Tree:
    val name: Name
  
  case class ModuleDef(name: Name, defs: List[ClassOrFunDef], optExpr: Option[Expr]) extends Definition
  sealed trait ClassOrFunDef extends Definition
  case class FunDef(name: Name, params: List[ParamDef], retType: TypeTree, body: Expr) extends ClassOrFunDef:
    def paramNames = params.map(_.name)
  
  case class AbstractClassDef(name: Name) extends ClassOrFunDef
  case class CaseClassDef(name: Name, fields: List[TypeTree], parent: Name) extends ClassOrFunDef
  case class ParamDef(name: Name, tt: TypeTree) extends Definition

  // Types
  trait Type
  case object IntType extends Type:
    override def toString: String = "Int"
  
  case object BooleanType extends Type:
    override def toString: String = "Boolean"
  
  case object StringType extends Type:
    override def toString: String = "String"
  
  case object UnitType extends Type:
    override def toString: String = "Unit"
  
  case class ClassType(qname: QualifiedName) extends Type:
    override def toString: String = printer.printQName(qname)(using false).print
  

  // A wrapper for types that is also a Tree (i.e. has a position)
  case class TypeTree(tpe: Type) extends Tree

  // All is wrapped in a program
  case class Program(modules: List[ModuleDef]) extends Tree

/* A module containing trees where the names have not been resolved.
 * Instantiates Name to String and QualifiedName to a pair of Strings
 * representing (module, name) (where module is optional)
 */
object NominalTreeModule extends TreeModule:
  type Name = String
  case class QualifiedName(module: Option[String], name: String):
    override def toString: String = printer.printQName(this)(using false).print
  val printer = NominalPrinter

/* A module containing trees where the names have been resolved to unique identifiers.
 * Both Name and ModuleName are instantiated to Identifier.
 */
object SymbolicTreeModule extends TreeModule:
  type Name = Identifier
  type QualifiedName = Identifier
  val printer = SymbolicPrinter

