package wacc

import java.io.File
import parsley.Parsley
import parsley.generic.ErrorBridge
import parsley.ap._
import parsley.position._
import cats.data.NonEmptyList

object ast {
  /* ============================    EXPRESSIONS    ============================ */

  sealed trait Expr extends RValue {
    val pos: Position
  }
  sealed trait Expr1 extends Expr
  sealed trait Expr2 extends Expr1
  sealed trait Expr3 extends Expr2
  sealed trait Expr4 extends Expr3
  sealed trait Expr5 extends Expr4
  sealed trait Expr6 extends Expr5

  /* ============================ ATOMIC EXPRESSIONS ============================ */

  case class IntLiter(v: Int)(val pos: Position) extends Expr6
  object IntLiter extends ParserBridgePos1Atom[Int, IntLiter]
  case class BoolLiter(v: Boolean)(val pos: Position) extends Expr6
  object BoolLiter extends ParserBridgePos1Atom[Boolean, BoolLiter]
  case class CharLiter(v: Char)(val pos: Position) extends Expr6
  object CharLiter extends ParserBridgePos1Atom[Char, CharLiter]
  case class StrLiter(v: String)(val pos: Position) extends Expr6
  object StrLiter extends ParserBridgePos1Atom[String, StrLiter]
  case class PairLiter()(val pos: Position) extends Expr6
  object PairLiter extends ParserBridgePos0[PairLiter]
  case class Ident(var v: String, var guid: Int = -1, var ty: types.RenamerType = types.?)(
      val pos: Position
  ) extends Expr6
      with LValue
  object Ident extends ParserBridgePos1Atom[String, Ident] {
    def apply(v: String)(pos: Position): Ident = new Ident(v)(pos)
  }
  case class ArrayElem(name: Ident, indices: NonEmptyList[Expr])(val pos: Position)
      extends Expr6
      with LValue
  object ArrayElem extends ParserBridgePos2Chain[NonEmptyList[Expr], Ident, ArrayElem] {
    def apply(indices: NonEmptyList[Expr], name: Ident)(pos: Position): ArrayElem =
      new ArrayElem(name, indices)(pos)
  }
  // object ArrayElem extends ParserBridgePos1[NonEmptyList[Expr], (File => Ident) => ArrayElem] {
  //   def apply(a: NonEmptyList[Expr])(pos: Position): (File => Ident) => ArrayElem =
  //     name => ArrayElem(name(pos.file), a)(pos)
  // }
  // object ArrayElem extends ParserSingletonBridgePos[(File => NonEmptyList[Expr]) => (File => Ident) => File => ArrayElem] {
  //   // def apply(indices: NonEmptyList[Expr]): (File => Ident) => File => ArrayElem =
  //   //   name => file => new ArrayElem(name(file), )
  //   def apply(indices: Parsley[File => NonEmptyList[Expr]]): Parsley[(File => Ident) => File => ArrayElem] =
  //     // error(ap1(pos.map(con),))

  //   override final def con(pos: (Int, Int)): (File => NonEmptyList[Expr]) =>  => C =
  //     (a, b) => file => this.apply(a(file), b(file))(Position(pos._1, pos._2, file))
  // }
  case class Parens(expr: Expr)(val pos: Position) extends Expr6
  object Parens extends ParserBridgePos1[Expr, Parens]

  /* ============================  UNARY OPERATORS  ============================ */

  sealed trait UnaryOp extends Expr {
    val x: Expr
  }
  case class Negate(x: Expr6)(val pos: Position) extends Expr6 with UnaryOp
  object Negate extends ParserBridgePos1[Expr6, Negate]
  case class Not(x: Expr6)(val pos: Position) extends Expr6 with UnaryOp
  object Not extends ParserBridgePos1[Expr6, Not]
  case class Len(x: Expr6)(val pos: Position) extends Expr6 with UnaryOp
  object Len extends ParserBridgePos1[Expr6, Len]
  case class Ord(x: Expr6)(val pos: Position) extends Expr6 with UnaryOp
  object Ord extends ParserBridgePos1[Expr6, Ord]
  case class Chr(x: Expr6)(val pos: Position) extends Expr6 with UnaryOp
  object Chr extends ParserBridgePos1[Expr6, Chr]

  /* ============================ BINARY OPERATORS ============================ */

  sealed trait BinaryOp(val name: String) extends Expr {
    val x: Expr
    val y: Expr
  }
  case class Add(x: Expr4, y: Expr5)(val pos: Position) extends Expr4 with BinaryOp("addition")
  object Add extends ParserBridgePos2[Expr4, Expr5, Add]
  case class Sub(x: Expr4, y: Expr5)(val pos: Position) extends Expr4 with BinaryOp("subtraction")
  object Sub extends ParserBridgePos2[Expr4, Expr5, Sub]
  case class Mul(x: Expr5, y: Expr6)(val pos: Position)
      extends Expr5
      with BinaryOp("multiplication")
  object Mul extends ParserBridgePos2[Expr5, Expr6, Mul]
  case class Div(x: Expr5, y: Expr6)(val pos: Position) extends Expr5 with BinaryOp("division")
  object Div extends ParserBridgePos2[Expr5, Expr6, Div]
  case class Mod(x: Expr5, y: Expr6)(val pos: Position) extends Expr5 with BinaryOp("modulus")
  object Mod extends ParserBridgePos2[Expr5, Expr6, Mod]
  case class Greater(x: Expr4, y: Expr4)(val pos: Position)
      extends Expr3
      with BinaryOp("strictly greater than")
  object Greater extends ParserBridgePos2[Expr4, Expr4, Greater]
  case class GreaterEq(x: Expr4, y: Expr4)(val pos: Position)
      extends Expr3
      with BinaryOp("greater than or equal to")
  object GreaterEq extends ParserBridgePos2[Expr4, Expr4, GreaterEq]
  case class Less(x: Expr4, y: Expr4)(val pos: Position)
      extends Expr3
      with BinaryOp("strictly less than")
  object Less extends ParserBridgePos2[Expr4, Expr4, Less]
  case class LessEq(x: Expr4, y: Expr4)(val pos: Position)
      extends Expr3
      with BinaryOp("less than or equal to")
  object LessEq extends ParserBridgePos2[Expr4, Expr4, LessEq]
  case class Eq(x: Expr3, y: Expr3)(val pos: Position) extends Expr2 with BinaryOp("equality")
  object Eq extends ParserBridgePos2[Expr3, Expr3, Eq]
  case class Neq(x: Expr3, y: Expr3)(val pos: Position) extends Expr2 with BinaryOp("inequality")
  object Neq extends ParserBridgePos2[Expr3, Expr3, Neq]
  case class And(x: Expr2, y: Expr1)(val pos: Position) extends Expr1 with BinaryOp("logical and")
  object And extends ParserBridgePos2[Expr2, Expr1, And]
  case class Or(x: Expr1, y: Expr)(val pos: Position) extends Expr with BinaryOp("logical or")
  object Or extends ParserBridgePos2[Expr1, Expr, Or]

  /* ============================ TYPES ============================ */

  sealed trait Type
  sealed trait BaseType extends Type with PairElemType
  case class IntType()(val pos: Position) extends BaseType
  object IntType extends ParserBridgePos0[IntType]
  case class BoolType()(val pos: Position) extends BaseType
  object BoolType extends ParserBridgePos0[BoolType]
  case class CharType()(val pos: Position) extends BaseType
  object CharType extends ParserBridgePos0[CharType]
  case class StringType()(val pos: Position) extends BaseType
  object StringType extends ParserBridgePos0[StringType]
  case class ArrayType(elemType: Type, dimensions: Int)(val pos: Position)
      extends Type
      with PairElemType
  object ArrayType extends ParserBridgePos2Chain[Int, Type, ArrayType] {
    def apply(dimensions: Int, elemType: Type)(pos: Position): ArrayType =
      ArrayType(elemType, dimensions)(pos)
  }
  case class PairType(fst: PairElemType, snd: PairElemType)(val pos: Position) extends Type
  object PairType extends ParserBridgePos2[PairElemType, PairElemType, PairType]

  sealed trait PairElemType
  case class UntypedPairType()(val pos: Position) extends PairElemType
  object UntypedPairType extends ParserBridgePos0[UntypedPairType]

  /* ============================ PROGRAM STRUCTURE ============================ */

  case class ImportedFunc(sourceName: Ident, importName: Ident)(val pos: Position)
  object ImportedFunc extends ParserBridgePos2[Ident, Option[Ident], ImportedFunc] {
    def apply(a: Ident, b: Option[Ident])(pos: Position): ImportedFunc =
      new ImportedFunc(a, b.getOrElse(a))(pos)
  }

  case class Import(source: StrLiter, funcs: NonEmptyList[ImportedFunc])(val pos: Position)
  object Import extends ParserBridgePos2[StrLiter, NonEmptyList[ImportedFunc], Import]

  case class PartialProgram(imports: List[Import], self: Program)(val pos: Position)
  object PartialProgram extends ParserBridgePos2[List[Import], Program, PartialProgram]

  case class Program(funcs: List[FuncDecl], main: NonEmptyList[Stmt])(val pos: Position)
  object Program extends ParserBridgePos2[List[FuncDecl], NonEmptyList[Stmt], Program]

  /* ============================ FUNCTION STRUCTURE ============================ */

  case class FuncDecl(
      returnType: Type,
      name: Ident,
      params: List[Param],
      body: NonEmptyList[Stmt]
  )(val pos: Position)
  object FuncDecl
      extends ParserBridgePos2Chain[
        (List[Param], NonEmptyList[Stmt]),
        ((Type, Ident)),
        FuncDecl
      ] {
    def apply(paramsBody: (List[Param], NonEmptyList[Stmt]), retTyName: (Type, Ident))(
        pos: Position
    ): FuncDecl =
      new FuncDecl(retTyName._1, retTyName._2, paramsBody._1, paramsBody._2)(pos)
  }

  case class Param(paramType: Type, name: Ident)(val pos: Position)
  object Param extends ParserBridgePos2[Type, Ident, Param]

  /* ============================ STATEMENTS ============================ */

  sealed trait Stmt {
    val pos: Position
  }
  case class Skip()(val pos: Position) extends Stmt
  object Skip extends ParserBridgePos0[Skip]
  case class VarDecl(varType: Type, name: Ident, value: RValue)(val pos: Position) extends Stmt
  object VarDecl extends ParserBridgePos3[Type, Ident, RValue, VarDecl]
  case class Assign(lhs: LValue, value: RValue)(val pos: Position) extends Stmt
  object Assign extends ParserBridgePos2[LValue, RValue, Assign]
  case class Read(lhs: LValue)(val pos: Position) extends Stmt
  object Read extends ParserBridgePos1[LValue, Read]
  case class Free(expr: Expr)(val pos: Position) extends Stmt
  object Free extends ParserBridgePos1[Expr, Free]
  case class Return(expr: Expr)(val pos: Position) extends Stmt
  object Return extends ParserBridgePos1[Expr, Return]
  case class Exit(expr: Expr)(val pos: Position) extends Stmt
  object Exit extends ParserBridgePos1[Expr, Exit]
  case class Print(expr: Expr, newline: Boolean)(val pos: Position) extends Stmt
  object Print extends ParserBridgePos2[Expr, Boolean, Print]
  case class If(cond: Expr, thenStmt: NonEmptyList[Stmt], elseStmt: NonEmptyList[Stmt])(
      val pos: Position
  ) extends Stmt
  object If extends ParserBridgePos3[Expr, NonEmptyList[Stmt], NonEmptyList[Stmt], If]
  case class While(cond: Expr, body: NonEmptyList[Stmt])(val pos: Position) extends Stmt
  object While extends ParserBridgePos2[Expr, NonEmptyList[Stmt], While]
  case class Block(stmt: NonEmptyList[Stmt])(val pos: Position) extends Stmt
  object Block extends ParserBridgePos1[NonEmptyList[Stmt], Block]

  /* ============================ LVALUES & RVALUES ============================ */

  sealed trait LValue {
    val pos: Position
  }

  sealed trait RValue {
    val pos: Position
  }
  case class ArrayLiter(elems: List[Expr])(val pos: Position) extends RValue
  object ArrayLiter extends ParserBridgePos1[List[Expr], ArrayLiter]
  case class NewPair(fst: Expr, snd: Expr)(val pos: Position) extends RValue
  object NewPair extends ParserBridgePos2[Expr, Expr, NewPair]
  case class Call(name: Ident, args: List[Expr])(val pos: Position) extends RValue
  object Call extends ParserBridgePos2[Ident, List[Expr], Call]

  sealed trait PairElem extends LValue with RValue
  case class Fst(elem: LValue)(val pos: Position) extends PairElem
  object Fst extends ParserBridgePos1[LValue, Fst]
  case class Snd(elem: LValue)(val pos: Position) extends PairElem
  object Snd extends ParserBridgePos1[LValue, Snd]

  /* ============================ PARSER BRIDGES ============================ */

  case class Position(line: Int, column: Int, file: File)

  trait ParserSingletonBridgePos[+A] extends ErrorBridge {
    protected def con(pos: (Int, Int)): A
    infix def from(op: Parsley[?]): Parsley[A] = error(pos.map(con) <~ op)
    final def <#(op: Parsley[?]): Parsley[A] = this from op
  }

  trait ParserBridgePos0[+A] extends ParserSingletonBridgePos[File => A] {
    def apply()(pos: Position): A

    override final def con(pos: (Int, Int)): File => A =
      file => apply()(Position(pos._1, pos._2, file))
  }

  trait ParserBridgePos1Atom[-A, +B] extends ParserSingletonBridgePos[A => File => B] {
    def apply(a: A)(pos: Position): B
    def apply(a: Parsley[A]): Parsley[File => B] = error(ap1(pos.map(con), a))

    override final def con(pos: (Int, Int)): A => File => B =
      a => file => this.apply(a)(Position(pos._1, pos._2, file))
  }

  trait ParserBridgePos1[-A, +B] extends ParserSingletonBridgePos[(File => A) => File => B] {
    def apply(a: A)(pos: Position): B
    def apply(a: Parsley[File => A]): Parsley[File => B] = error(ap1(pos.map(con), a))

    override final def con(pos: (Int, Int)): (File => A) => File => B =
      a => file => this.apply(a(file))(Position(pos._1, pos._2, file))
  }

  trait ParserBridgePos2Chain[-A, -B, +C]
      extends ParserSingletonBridgePos[(File => A) => (File => B) => File => C] {
    def apply(a: A, b: B)(pos: Position): C
    def apply(a: Parsley[File => A]): Parsley[(File => B) => File => C] = error(
      ap1(pos.map(con), a)
    )

    override final def con(pos: (Int, Int)): (File => A) => (File => B) => File => C =
      a => b => file => this.apply(a(file), b(file))(Position(pos._1, pos._2, file))
  }

  trait ParserBridgePos2[-A, -B, +C]
      extends ParserSingletonBridgePos[(File => A, File => B) => File => C] {
    def apply(a: A, b: B)(pos: Position): C
    def apply(a: Parsley[File => A], b: => Parsley[File => B]): Parsley[File => C] = error(
      ap2(pos.map(con), a, b)
    )

    override final def con(pos: (Int, Int)): (File => A, File => B) => File => C =
      (a, b) => file => this.apply(a(file), b(file))(Position(pos._1, pos._2, file))
  }

  trait ParserBridgePos3[-A, -B, -C, +D]
      extends ParserSingletonBridgePos[(File => A, File => B, File => C) => File => D] {
    def apply(a: A, b: B, c: C)(pos: Position): D
    def apply(
        a: Parsley[File => A],
        b: => Parsley[File => B],
        c: => Parsley[File => C]
    ): Parsley[File => D] = error(
      ap3(pos.map(con), a, b, c)
    )

    override final def con(pos: (Int, Int)): (File => A, File => B, File => C) => File => D =
      (a, b, c) => file => apply(a(file), b(file), c(file))(Position(pos._1, pos._2, file))
  }
}
