type op = Add | Sub | Mult | Div | Equal | Neq | Less | Leq | Greater | Geq | Mod

type stmt =
    Block of stmt list
  | Expr of expr
  | For of expr * expr * expr * stmt list
  | Foreach of string * stmt list
  | Return of expr

and expr = 
    Literal of int
  | LitFloat of float
  | LitBool of bool
  | LitString of string
  | Id of string
  | Binop of expr * op * expr
  | Assign of expr * expr
  | Call of string * expr list 
  | IfThenElse of expr * expr * expr * stmt list * stmt list
  | ArrayAccess of string * expr  
  | MemberAccess of string * string
  | MethodCall of string * string * expr list
  | CSVInput
(*  | If of expr * expr * expr *)
  | Noexpr

type progType =
    Int
  | Float
  | String
  | Boolean
  | Record of string
  | Table of string

type varDecl = {
    vname : string;
    varType : progType;
    varArraySize: int option;
    varAssign: expr list option;
}

type funcDecl = {
    name : string;
    formals : varDecl list;
    locals : varDecl list;    (* Needs Initializer *)
    body : stmt list;
    retType : progType;
}

type tableColumnFunc = {
    locals: varDecl list;
    statements: stmt list;
    lastExpr: expr;
  }

type tableColumn = {
    tblColName : string;
    tblColType: progType;
    tblColFunc: tableColumnFunc;
}

type tableDecl = {
    tableName : string;
    iterClass : string;
    columns: tableColumn list;
}

type objDecl = {
     objName : string;
     objFields : (string * progType) list;
}

type csvDecl = {
    csvInputClass: string;
}

type program = {
    varDecls : varDecl list;
    funcDecls : funcDecl list;
    objDecls : objDecl list;
    tableDecls : tableDecl list;
    stmts : stmt list;
    csvDecls: csvDecl list;
  }
    

let rec string_of_expr = function
    Literal(l) -> string_of_int l
  | LitFloat(l) -> string_of_float l
  | LitBool(l) -> string_of_bool l
  | LitString(l) -> l
  | Id(s) -> s
  | Binop(e1,op,e2) -> string_of_binop (e1,op,e2)
  | Assign(var,exp) -> string_of_expr var ^ "init " ^ string_of_expr exp ^ "\n"
  | Call(name,args) -> name ^ "(" ^ String.concat "," 
    (List.map string_of_expr args) ^ ")\n"
  | IfThenElse(pred,e2,e3,sl1,sl2) -> "if( " ^ string_of_expr pred ^ ") then( "
    ^ string_of_expr e2 ^ " ) else ( " ^ string_of_expr e3 ^ " )\n"
  | ArrayAccess(name,e) -> name ^ "[" ^ string_of_expr e ^ "]"
  | MemberAccess(name,field) -> "name: " ^ name ^ " field " ^ field
  | MethodCall(name,field,args) -> "name: " ^ name ^ " field " ^ field ^ "(" ^ 
    String.concat "," (List.map string_of_expr args) ^ ")\n"
  | CSVInput -> "CSVInput"
  | Noexpr -> ""

and string_of_binop (e1,op,e2) = 
  let string_e2 = " " ^ string_of_expr e2 ^ "\n" in 
  string_of_expr e1 ^ " " ^
  match op with
    Add -> "+" ^ string_e2
  | Sub -> "-" ^ string_e2
  | Mult -> "*" ^ string_e2
  | Mod -> "%" ^ string_e2
  | Div -> "/" ^ string_e2
  | Equal -> "==" ^ string_e2
  | Neq -> "!=" ^ string_e2
  | Less -> "<" ^ string_e2
  | Leq -> "<=" ^ string_e2
  | Greater -> ">" ^ string_e2
  | Geq -> ">=" ^string_e2

let rec string_of_stmt = function
    Block(stmts) -> 
      "{\n" ^ String.concat " " (List.map string_of_stmt stmts) ^ "}\n"
  | Expr(e) -> string_of_expr e ^ ";\n";
  | For(e1,e2,e3,stmts) -> "For (" ^ string_of_expr e1 ^ "; " ^ string_of_expr e2 ^ "; "
    ^ string_of_expr e3 ^ ")" ^ String.concat " " (List.map string_of_stmt stmts)
  | Foreach(iden,stmts) -> "Foreach " ^ iden ^ " { " ^ String.concat " " (List.map string_of_stmt stmts) ^ "} \n"
  | Return(e) -> "return " ^ string_of_expr e ^ "; \n"

let string_of_progType = function
    Int -> "Int"
  | String -> "String"
  | Float -> "Float"
  | Boolean -> "Boolean"
  | Record(name) -> name
  | Table(name) -> name

let string_of_varDecl dec = 
  let nameType = "vname " ^ dec.vname ^ " type " 
  ^ string_of_progType dec.varType in
  let assignment = 
    match dec.varArraySize with
      Some(size) -> " Array size " ^ string_of_int size
    | None -> "" in
  let init_assign = 
    match dec.varAssign with
      Some(li) -> String.concat "," (List.map string_of_expr li )
    | None -> " "  in
  nameType ^ assignment ^ init_assign
                  

let string_of_funcDecl fdecl =
  fdecl.name ^ "(" ^ String.concat ", " (List.map string_of_varDecl fdecl.formals) 
  ^ ")\n{\n" ^
  String.concat "" (List.map string_of_varDecl fdecl.locals) ^
  String.concat "" (List.map string_of_stmt fdecl.body) ^
  "}\n"

let string_of_objectDecl o =
  let string_of_fieldName (_name,_type) = _name ^ string_of_progType _type in
  "Object: " ^ o.objName ^ "\n" ^
  String.concat "\n" (List.map string_of_fieldName o.objFields) ^ "\n"

let string_of_tableDecl tbl =
  
  let string_of_columnFunc fn = String.concat "\n" (List.map string_of_varDecl fn.locals)
    ^ "\n" ^ String.concat "\n" (List.map string_of_stmt fn.statements) ^
    "\n" ^ string_of_expr fn.lastExpr in

  let string_of_column col = col.tblColName ^ " -> " ^ string_of_progType col.tblColType  ^ " : " ^ string_of_columnFunc col.tblColFunc in

  "Table " ^ tbl.tableName ^ "--Object-> " ^ tbl.iterClass ^ " -> " ^
  String.concat "\n" (List.map string_of_column tbl.columns)
  

let string_of_program p =
  String.concat "" (List.map string_of_varDecl p.varDecls) ^ "\n" ^
  String.concat "\n" (List.map string_of_funcDecl p.funcDecls) ^ "\n" ^
  String.concat " " (List.map string_of_objectDecl p.objDecls) ^ "\n" ^
  String.concat " " (List.map string_of_tableDecl p.tableDecls) ^ "\n" ^
  String.concat ";" (List.map string_of_stmt p.stmts) ^ "\n"
