type op = Add | Sub | Mult | Div | Equal | Neq | Less | Leq | Greater | Geq | Exp | And | Or

type uop = Neg | Not

type typ =
	Int | Bool | Void | Img | String | Float
	| ArrayTyp of typ * int | MatrixTyp of typ * int * int

type bind = typ * string

type expr =
	StringLiteral of string
	| IntLiteral of int
	| FloatLiteral of float
	| ID of string
	| BoolLit of bool
	| Call of string * expr list
	| Binop of expr * op * expr
	| Unop of uop * expr
	| Assign of expr * expr
	| ArrayLit of expr list
	| MatrixLit of expr list list
	| Noexpr
	| Null
	| ArrayAccess of string * expr
	| MatrixAccess of string * expr * expr
	| Dereference of string
	| Reference of string
	| MovePointer of string

type stmt =
	  Block of stmt list
	| Expr of expr
	| Return of expr
	| Call of string * expr list
	| If of expr * stmt * stmt
	| While of expr * stmt
	| For of expr * expr * expr * stmt

type func_decl = {
	typ : typ;
	fname : string;
	formals : bind list;
	locals : bind list;
	body : stmt list;
}

type program = bind list * func_decl list


let string_of_op = function
	Add -> "+"
	| Sub -> "-"
	| Mult -> "*"
	| Div -> "/"
	| Equal -> "=="
	| Neq -> "!="
	| Less -> "<"
	| Leq -> "<="
	| Greater -> ">"
	| Geq -> ">="
	| Exp -> "^"
	| And -> "&&"
	| Or -> "||"

let rec string_of_typ = function
	Int -> "int"
	| Float -> "float"
	| Img -> "img"
	| Bool -> "bool"
	| Void -> "void"
	| String -> "String"
        | ArrayTyp(r, l1) -> (match r with
                        Int -> string_of_typ r ^ "[" ^ string_of_int l1 ^ "]"
                        | String -> "String" ^ "[" ^ string_of_int l1 ^ "]"
                        | Float -> "float" ^ "[" ^ string_of_int l1 ^ "]"
                        | _ -> raise(Failure("Illegal expression in row primitive")))
        (*| MatrixTyp(t, l1, l2) -> string_of_typ t ^ "[" ^ string_of_int l1 ^ "][" ^ string_of_int l2 ^ "]" *)
        | MatrixTyp(t, l1, l2) -> (match t with
                        Int -> "int" ^ "[" ^ string_of_int l1 ^ "][" ^ string_of_int l2 ^ "]"
                      | Float -> "float" ^ "[" ^ string_of_int l1 ^ "][" ^ string_of_int l2 ^ "]"
                      | _ -> raise( Failure("Illegal expression in matrix primitive")))



let string_of_uop = function
    Neg -> "-"

let string_of_array r =
  (*let rec string_of_array_literal = function
  	[] -> "]"
    | [hd] -> (match hd with
	  	  IntLiteral(i) -> string_of_int i
				| StringLiteral(l) -> string_of_int 5) ^ string_of_array_literal []
    | hd :: tl -> (match hd with
			IntLiteral(i) -> string_of_int i ^ ", "
			| StringLiteral(l) -> string_of_int 5 ^ ", ") ^ string_of_array_literal tl
		in "[" ^ string_of_array_literal r *)
  let rec string_of_array_literal = function
        [] -> "]"
    | [hd] -> (match hd with
                  IntLiteral(i) -> string_of_int i
                  | FloatLiteral(f) -> string_of_float f
                  | StringLiteral(l) -> string_of_int 5
                  | _ -> raise(Failure("Illegal expression in row primitive")) ) ^ string_of_array_literal []
    | hd :: tl -> (match hd with
                        IntLiteral(i) -> string_of_int i ^ ", "
                        | FloatLiteral(f) -> string_of_float f ^ ", "
                        | StringLiteral(l) -> string_of_int 5 ^ ", ") ^ string_of_array_literal tl
                in "[" ^ string_of_array_literal r


let string_of_matrix m =
	let rec string_of_matrix_literal = function
	    [] -> "}"
	  | [hd] -> (match hd with
	              ArrayLit(r) -> string_of_array r) ^ string_of_matrix_literal []
	  | hd::tl -> (match hd with
                ArrayLit(r) -> string_of_array r ^ ", ") ^ string_of_matrix_literal tl
	in
	"{" ^ string_of_matrix_literal m

let rec string_of_expr = function
	IntLiteral(l) -> string_of_int l
	| BoolLit(true) -> "true"
	| BoolLit(false) -> "false"
	| FloatLiteral(l) -> string_of_float l
	| StringLiteral(l) -> string_of_int 5 (* sus, if theres an issue check*)
	| ID(s) -> s
	| Dereference(s) -> "&" ^ (s)
	| Binop(e1, o, e2) ->
	    string_of_expr e1 ^ " " ^ string_of_op o ^ " " ^ string_of_expr e2
	| Unop(o, e) -> string_of_uop o ^ string_of_expr e
	| ArrayLit(r) -> string_of_array r
        | MatrixLit(_)-> "matrix literal"
	| Call(f, el) ->
	  f ^ "(" ^ String.concat ", " (List.map string_of_expr el) ^ ")"
	| Assign(v, e) -> string_of_expr v ^ " = " ^ string_of_expr e
	| ArrayAccess(r, e) -> r ^ "[" ^ string_of_expr e ^ "]"
	| MatrixAccess(m, e1, e2) -> m ^ "[" ^ string_of_expr e1 ^ "][" ^ string_of_expr e2 ^ "]"
	| MovePointer(s) -> "++" ^ (s)
	| Reference (s) -> "**" ^ (s)


let rec string_of_stmt = function
	Block(stmts) ->
	  "{\n" ^ String.concat "" (List.map string_of_stmt stmts) ^ "}\n"
	| Expr(expr) -> string_of_expr expr ^ ";\n"
	| Return(expr) -> "return " ^ string_of_expr expr ^ ";\n"
	| If(e, s, Block([])) -> "if (" ^ string_of_expr e ^ ")\n" ^ string_of_stmt s
	| If(e, s1, s2) ->  "if (" ^ string_of_expr e ^ ")\n" ^
	  string_of_stmt s1 ^ "else\n" ^ string_of_stmt s2
	| While(e, s) -> "while (" ^ string_of_expr e ^ ") " ^ string_of_stmt s
	| For(e1, e2, e3, s) ->
      "for (" ^ string_of_expr e1  ^ " ; " ^ string_of_expr e2 ^ " ; " ^
      string_of_expr e3  ^ ") " ^ string_of_stmt s


let string_of_vdecl (t, id) = string_of_typ t ^ " " ^ id ^ ";\n"

let string_of_fdecl fdecl =
	string_of_typ fdecl.typ ^ " " ^
	fdecl.fname ^ "(" ^ String.concat ", " (List.map snd fdecl.formals) ^
	")\n{\n" ^
	String.concat "" (List.map string_of_vdecl fdecl.locals) ^
	String.concat "" (List.map string_of_stmt fdecl.body) ^
	"}\n"

let string_of_program (vars, funcs) =
	String.concat "" (List.map string_of_vdecl vars) ^ "\n" ^
	String.concat "\n" (List.map string_of_fdecl funcs)
