open Ast
open Errors

let rec sizeof env = function
      Int -> 4
    | Double -> 8
    | List(list_typ) -> 100 * (sizeof env list_typ)
    | String -> 100
    | Struct(str) -> let ( _ , size, _ ) =  (sizeof_struct env (Struct(str))) in size

and sizeof_struct env typ =
    let typ_list = (get_struct_type_list env typ) in
        List.fold_left add_struct_size (env, 0, 0) typ_list

and add_struct_size (env, total_size, max_single_size) typ = 
    begin match typ with 
          List(list_typ) -> get_new_size_list env (sizeof env list_typ) total_size max_single_size
        | String -> (env, total_size + 100, max_single_size)
        | Struct(str) -> get_new_size env (sizeof env typ) total_size max_single_size
        | _ -> get_new_size env (sizeof env typ) total_size max_single_size
    end

and get_new_size env member_size total_size max_single_size =
    let new_max_single_size = 
        if (member_size > max_single_size) then member_size else max_single_size in
        let rem = total_size mod member_size in
            let new_total_size = total_size + ((member_size - rem) mod member_size) + member_size in
                let total_rem = new_total_size mod new_max_single_size in
                    (env, new_total_size + ((new_max_single_size - total_rem) mod new_max_single_size), new_max_single_size)

and get_new_size_list env type_size total_size max_single_size =
    let new_max_single_size = 
        if (type_size > max_single_size) then type_size else max_single_size in
        let rem = total_size mod type_size in
            (env, total_size + rem + type_size * 100, new_max_single_size)

and check_const_type = function
          Const_int(int) -> Int
	| Const_dbl(dbl) -> Double
        | Const_str(str) -> String

and check_const_expr_type = function
      Const_expr(const) -> check_const_type const
    | Const_expr_neg(const) -> check_const_type const

and get_struct_type_list (scope : symbol_table) typ = 
    try let ( _ , member_list ) = find_struct_by_type scope typ in
        List.fold_left (fun ret_list member -> List.rev ((snd member) :: List.rev ret_list)) [] member_list         
    with Not_found -> raise(Error("undefined struct"))

and find_variable (scope : symbol_table) name =
          try
              List.find (fun (s, _, _ ) -> s = name) scope.variables
          with Not_found ->
              match scope.parent with
                    Some(parent) -> find_variable parent name
                  | _ -> raise Not_found

and check_variable (scope : symbol_table) name =
    let result = fst (List.fold_left (fun (result, name) var -> 
        let (var_name, _ , _ ) = var in if(name = var_name) then (true, name)
            else (result, name)) (false, name) scope.variables)
    in if(result) then result
    else match scope.parent with
         Some(parent) -> check_variable parent name
       | _ -> result

and get_func_arg_typs args_list = 
    List.fold_left (fun ret_list arg -> List.rev ((snd arg) :: List.rev ret_list)) [] args_list

and find_function (scope : symbol_table) name typ_list =
    try
        List.find (fun ((s, _ ), args ) -> 
            let arg_typs = get_func_arg_typs args in (s = name) && (Pervasives.compare arg_typs typ_list = 0)) scope.functions
          with Not_found ->
              match scope.parent with
                    Some(parent) -> find_function parent name typ_list
                  | _ -> raise Not_found

and check_function (scope : symbol_table) name typ_list =
    let result = fst (List.fold_left (fun (result, name) func -> 
        let ((func_name, _ ) , args ) = func in
            let arg_typs = get_func_arg_typs args in 
                if((func_name = name) && (Pervasives.compare arg_typs typ_list = 0)) then (true, name)
                else (result, name)) (false, name) scope.functions)
    in if(result) then result
    else match scope.parent with
         Some(parent) -> check_function parent name typ_list
       | _ -> result

and find_struct_by_type (scope : symbol_table) typ =
          try
              List.find (fun (s, _ ) -> Pervasives.compare (Struct s) typ = 0) scope.structs
          with Not_found ->
              match scope.parent with
                    Some(parent) -> find_struct_by_type parent typ
                  | _ -> raise Not_found

and check_struct (scope : symbol_table) name =
    let result = fst (List.fold_left (fun (result, name) struc -> 
        let (struct_name, _ ) = struc in if(name = struct_name) then (true, name)
            else (result, name)) (false, name) scope.structs)
    in if(result) then result
    else match scope.parent with
         Some(parent) -> check_struct parent name
       | _ -> result

and find_struct_member struct_decl_list name =
          try
              List.find (fun (s, _ ) -> s = name) struct_decl_list
          with Not_found -> raise Not_found

and match_struct_members member_list typ_list index =
    if((List.length member_list) <> (List.length typ_list)) then false
    else (
        let ( _ , member_typ) = (List.nth member_list index) in
        (Pervasives.compare member_typ (List.nth typ_list index) = 0) && 
        (if(index = (List.length member_list) - 1) then true
         else (match_struct_members member_list typ_list (index+1))))

and sequency env struc member_name = 
    let ( _ , member_list) = find_struct_by_type env struc in
        let ( index, _, _) = (List.fold_left check_member_sequency (0, false, member_name) member_list)
        in index

and check_member_sequency (index, found, member_name) member =
    if(found) then (index, found, member_name)
    else
        let (name, _ ) = member in
            if(name = member_name) then (index + 1, true, member_name)
            else (index + 1, false, member_name)

and member2type struc member_name env =
    let ( _ , member_list) = find_struct_by_type env struc in
		let(s,t) = List.find (fun (str, typ) -> str = member_name) member_list
			in t

and merge_list snd_list fst_list = 
    List.fold_left (fun list list_element -> List.rev (list_element :: List.rev list)) snd_list fst_list

and struct_decl_to_stmts id member_list const_expr_list =
    let (stmt_list, _, _, _) = (List.fold_left struct_decl_to_stmt ([], id, member_list, 0) const_expr_list)
    in stmt_list

and struct_decl_to_stmt (stmt_list, id, member_list, index) const_expr =
    let member = (List.nth member_list index) in
        let (member_name, _ ) = member in
            let lval = Lval_id(id) in 
                let lval_struct = Lval_struct(lval, member_name) in
                    let expr_const = const_expr_to_expr const_expr in
                            let expr = Expr_asgop(lval_struct, "=", expr_const) in
                                let stmt = Stmt(expr) in 
                                    ((List.rev (stmt :: List.rev stmt_list)), id, member_list, (index+1))

and point_decl_to_stmts str const_expr_list = 
    let lval = Lval_id(str) in
        let lval_x = Lval_struct(lval, "x") in
            let const_expr_x = (List.nth const_expr_list 0) in 
                let expr_const_x = const_expr_to_expr const_expr_x in 
                    let expr_x = Expr_asgop(lval_x, "=", expr_const_x) in
                        let lval_y = Lval_struct(lval, "y") in 
                            let const_expr_y = (List.nth const_expr_list 1) in
                                let expr_const_y = const_expr_to_expr const_expr_y in
                                    let expr_y = Expr_asgop(lval_y, "=", expr_const_y) in
                                        let stmt_x = Stmt(expr_x) in
                                            let stmt_y = Stmt(expr_y) in
                                                let tmp_list = [stmt_y] in
                                                    stmt_x :: tmp_list

and shape_decl_to_stmts str const_expr_list = 
    let lval = Lval_id(str) in
        let lval_size = Lval_struct(lval, "size") in
            let const_expr_size = (List.nth const_expr_list 0) in 
                let expr_const_size = const_expr_to_expr const_expr_size in 
                    let expr_size = Expr_asgop(lval_size, "=", expr_const_size) in
                        let lval_x = Lval_struct(lval, "center_x") in 
                            let const_expr_x = (List.nth const_expr_list 1) in
                                let expr_const_x = const_expr_to_expr const_expr_x in
                                    let expr_x = Expr_asgop(lval_x, "=", expr_const_x) in
                                        let lval_y = Lval_struct(lval, "center_y") in
                                            let const_expr_y = (List.nth const_expr_list 2) in
                                                let expr_const_y = const_expr_to_expr const_expr_y in
                                                    let expr_y = Expr_asgop(lval_y, "=", expr_const_y) in
                                                        let stmt_size = Stmt(expr_size) in
                                                            let stmt_x = Stmt(expr_x) in
                                                                let stmt_y = Stmt(expr_y) in
                                                                    let tmp_list = [stmt_y] in
                                                                        let tmp_list = stmt_x :: tmp_list in
                                                                            stmt_size :: tmp_list

and const_expr_to_expr = function
      Const_expr(const) -> Expr_const(const)
    | Const_expr_neg(const) -> 
        let expr = Expr_const(const) in
            Expr_op("-", expr)

and get_lval_id = function
      Lval_id(str) -> str
    | Lval_arr(lval, expr) -> get_lval_id lval
    | Lval_struct(lval, str) -> get_lval_id lval

and type_to_string = function
      Int -> "int"
    | Double -> "double"
    | List(type_spec) -> "list<" ^ (type_to_string type_spec) ^ "> "
    | String -> "string"
    | Struct(str) -> "struct " ^ str

and add_gl_calls stmt_list = 
    let ret_list = [] in
        let expr_setup = Expr_func("setup", []) in
        let stmt_setup = Stmt(expr_setup) in
        let ret_list = (stmt_setup :: ret_list)in
            List.fold_left (fun list stmt ->
                begin match stmt with
                      Stmt_ret(expr) -> 
                          let expr_run = Expr_func("byebye", []) in
                          let stmt_run = Stmt(expr_run) in
                          let list = List.rev (stmt_run :: List.rev list) in
                          List.rev (stmt :: List.rev list) 
                    | _ -> List.rev (stmt :: List.rev list) 
                end) ret_list stmt_list
