module Abs = Tiger_absyn
module Pos = Tiger_position
module Sym = Tiger_symbol
module Typ = Tiger_env_type

type t =
  | Cycle_in_type_decs of
      { from_id  : Sym.t
      ; from_pos : Pos.t
      ;   to_id  : Sym.t
      ;   to_pos : Pos.t
      }
  | Break_outside_loop of Pos.t
  | Invalid_syntax    of Pos.t
  | Unknown_id        of {id    : Sym.t; pos : Pos.t}
  | Unknown_type      of {ty_id : Sym.t; pos : Pos.t}
  | Id_is_a_function  of {id    : Sym.t; pos : Pos.t}
  | Id_not_a_function of {id    : Sym.t; pos : Pos.t}
  | No_such_field_in_record of {field : Sym.t; record : Typ.t; pos : Pos.t}
  | Exp_not_a_record  of {ty    : Typ.t; pos : Pos.t}
  | Exp_not_an_array  of {ty    : Typ.t; pos : Pos.t}
  | Wrong_type of
      { expected : Typ.t
      ; given    : Typ.t
      ; pos      : Pos.t
      }
  | Wrong_type_of_expression_in_var_dec of
      { var_id   : Sym.t
      ; expected : Typ.t
      ; given    : Typ.t
      ; pos      : Pos.t
      }
  | Wrong_type_used_as_record of
      { ty_id    : Sym.t
      ; ty       : Typ.t
      ; pos      : Pos.t
      }
  | Wrong_type_used_as_array of
      { ty_id    : Sym.t
      ; ty       : Typ.t
      ; pos      : Pos.t
      }
  | Wrong_type_of_field_value of
      { field_id : Sym.t
      ; expected : Typ.t
      ; given    : Typ.t
      ; pos      : Pos.t
      }
  | Wrong_type_of_arg of
      { func     : Sym.t
      ; expected : Typ.t
      ; given    : Typ.t
      ; pos      : Pos.t
      }
  | Wrong_number_of_args of
      { func     : Sym.t
      ; expected : int
      ; given    : int
      ; pos      : Pos.t
      }
  | Invalid_operand_type of
      { oper  : Abs.oper
      ; valid : string list
      ; given : Typ.t
      ; pos   : Pos.t
      }
  | Different_operand_types of
      { oper  : Abs.oper
      ; left  : Typ.t
      ; right : Typ.t
      ; pos   : Pos.t
      }

exception T of t

let raise t =
  raise (T t)

let to_string =
  let s = Printf.sprintf in
  function
  | Invalid_syntax pos ->
      s "Invalid syntax in %s" (Pos.to_string pos)
  | Break_outside_loop pos ->
      s "Break statement is not within a loop. In %s" (Pos.to_string pos)
  | Cycle_in_type_decs  {from_id; from_pos; to_id; to_pos} ->
      s ( "Circular type declaration between %S and %S."
        ^^"Locations: %S (in %s), %S (in %s).")
        (Sym.to_string from_id)
        (Sym.to_string to_id)
        (Sym.to_string from_id)
        (Pos.to_string from_pos)
        (Sym.to_string to_id)
        (Pos.to_string to_pos)
  | Unknown_id  {id; pos} ->
      s "Unknown identifier %S in %s" (Sym.to_string id) (Pos.to_string pos)
  | Unknown_type {ty_id; pos} ->
      s "Unknown type %S in %s" (Sym.to_string ty_id) (Pos.to_string pos)
  | Id_is_a_function {id; pos} ->
      s "Identifier %S is a function, it cannot be used as a variable in %s"
        (Sym.to_string id) (Pos.to_string pos)
  | Id_not_a_function {id; pos} ->
      s "Identifier %S is not a function, it cannot be called in %s"
        (Sym.to_string id) (Pos.to_string pos)
  | No_such_field_in_record {field; record; pos} ->
      s "No field %S in record %S in %s"
        (Sym.to_string field) (Typ.to_string record) (Pos.to_string pos)
  | Exp_not_a_record {ty; pos} ->
      s ( "The expression of type %S is not a record, it cannot be"
        ^^"accessed in %s")
        (Typ.to_string ty) (Pos.to_string pos)
  | Exp_not_an_array {ty; pos} ->
      s ( "The expression of type %S is not an array, it cannot be"
        ^^"accessed in %s")
        (Typ.to_string ty) (Pos.to_string pos)
  | Wrong_type {expected; given; pos} ->
      s "Type error: expected: %S, but given: %S, in %s"
        (Typ.to_string expected)
        (Typ.to_string given)
        (Pos.to_string pos)
  | Wrong_type_of_expression_in_var_dec {var_id; expected; given; pos} ->
      s ( "Wrong type of expression in declaration of %S. "
        ^^"Expected: %S, given: %S. In %s")
        (Sym.to_string var_id)
        (Typ.to_string expected)
        (Typ.to_string given)
        (Pos.to_string pos)
  | Wrong_type_used_as_array {ty_id; ty; pos} ->
      s ( "Identifier %S is bound to type %S, not an array. "
        ^^"It cannot be used in %s")
        (Sym.to_string ty_id) (Typ.to_string ty) (Pos.to_string pos)
  | Wrong_type_used_as_record {ty_id; ty; pos} ->
      s ( "Identifier %S is bound to type %S, not a record. "
        ^^"It cannot be used in %s")
        (Sym.to_string ty_id) (Typ.to_string ty) (Pos.to_string pos)
  | Wrong_type_of_field_value {field_id; expected; given; pos} ->
      s ( "Field %S is declared to be of type %S, but is bound to expression "
        ^^"of type %S in %s")
        (Sym.to_string field_id)
        (Typ.to_string expected)
        (Typ.to_string given)
        (Pos.to_string pos)
  | Wrong_type_of_arg {func; expected; given; pos} ->
      s ( "Incorrect type of argument to function %S, expected: %S, given: %S,"
        ^^" in %s")
        (Sym.to_string func)
        (Typ.to_string expected)
        (Typ.to_string given)
        (Pos.to_string pos)
  | Wrong_number_of_args {func; expected; given; pos} ->
      s ( "Incorrect number of arguments to function %S, "
        ^^"expected: %d, given: %d,"
        ^^" in %s")
        (Sym.to_string func) expected given (Pos.to_string pos)
  | Invalid_operand_type {oper; valid; given; pos} ->
      s ( "Invalid operand type %S for operator %S, which expects only: %s"
        ^^". In %s")
        (Typ.to_string given)
        (Abs.op_show oper)
        (String.concat ", " valid)
        (Pos.to_string pos)
  | Different_operand_types {oper; left; right; pos} ->
      s "Operands of different types (%S %S %S) given in %s"
        (Typ.to_string left)
        (Abs.op_show oper)
        (Typ.to_string right)
        (Pos.to_string pos)

let is_unknown_id t =
  match t with
  | Unknown_id _ ->
      true
  | Break_outside_loop _
  | Cycle_in_type_decs _
  | Invalid_syntax _
  | Unknown_type _
  | Id_is_a_function _
  | Id_not_a_function _
  | No_such_field_in_record _
  | Exp_not_a_record _
  | Exp_not_an_array _
  | Wrong_type _
  | Wrong_type_of_expression_in_var_dec _
  | Wrong_type_used_as_array _
  | Wrong_type_used_as_record _
  | Wrong_type_of_field_value _
  | Wrong_type_of_arg _
  | Wrong_number_of_args _
  | Invalid_operand_type _
  | Different_operand_types _ ->
      false

let is_unknown_type t =
  match t with
  | Unknown_type _ ->
      true
  | Break_outside_loop _
  | Cycle_in_type_decs _
  | Unknown_id _
  | Invalid_syntax _
  | Id_is_a_function _
  | Id_not_a_function _
  | No_such_field_in_record _
  | Exp_not_a_record _
  | Exp_not_an_array _
  | Wrong_type _
  | Wrong_type_of_expression_in_var_dec _
  | Wrong_type_used_as_array _
  | Wrong_type_used_as_record _
  | Wrong_type_of_field_value _
  | Wrong_type_of_arg _
  | Wrong_number_of_args _
  | Invalid_operand_type _
  | Different_operand_types _ ->
      false

let is_wrong_type t =
  match t with
  | Wrong_type _ ->
      true
  | Break_outside_loop _
  | Cycle_in_type_decs _
  | Unknown_type _
  | Unknown_id _
  | Invalid_syntax _
  | Id_is_a_function _
  | Id_not_a_function _
  | No_such_field_in_record _
  | Exp_not_a_record _
  | Exp_not_an_array _
  | Wrong_type_of_expression_in_var_dec _
  | Wrong_type_used_as_array _
  | Wrong_type_used_as_record _
  | Wrong_type_of_field_value _
  | Wrong_type_of_arg _
  | Wrong_number_of_args _
  | Invalid_operand_type _
  | Different_operand_types _ ->
      false

let is_wrong_number_of_args t =
  match t with
  | Wrong_number_of_args _ ->
      true
  | Break_outside_loop _
  | Cycle_in_type_decs _
  | Wrong_type _
  | Unknown_type _
  | Unknown_id _
  | Invalid_syntax _
  | Id_is_a_function _
  | Id_not_a_function _
  | No_such_field_in_record _
  | Exp_not_a_record _
  | Exp_not_an_array _
  | Wrong_type_of_expression_in_var_dec _
  | Wrong_type_used_as_array _
  | Wrong_type_used_as_record _
  | Wrong_type_of_field_value _
  | Wrong_type_of_arg _
  | Invalid_operand_type _
  | Different_operand_types _ ->
      false

let is_invalid_syntax t =
  match t with
  | Invalid_syntax _ ->
      true
  | Break_outside_loop _
  | Cycle_in_type_decs _
  | Wrong_type _
  | Unknown_type _
  | Unknown_id _
  | Id_is_a_function _
  | Id_not_a_function _
  | No_such_field_in_record _
  | Exp_not_a_record _
  | Exp_not_an_array _
  | Wrong_type_of_expression_in_var_dec _
  | Wrong_type_used_as_array _
  | Wrong_type_used_as_record _
  | Wrong_type_of_field_value _
  | Wrong_type_of_arg _
  | Wrong_number_of_args _
  | Invalid_operand_type _
  | Different_operand_types _ ->
      false

let is_not_a_record t =
  match t with
  | Exp_not_a_record _ ->
      true
  | Break_outside_loop _
  | Cycle_in_type_decs _
  | Invalid_syntax _
  | Wrong_type _
  | Unknown_type _
  | Unknown_id _
  | Id_is_a_function _
  | Id_not_a_function _
  | No_such_field_in_record _
  | Exp_not_an_array _
  | Wrong_type_of_expression_in_var_dec _
  | Wrong_type_used_as_array _
  | Wrong_type_used_as_record _
  | Wrong_type_of_field_value _
  | Wrong_type_of_arg _
  | Wrong_number_of_args _
  | Invalid_operand_type _
  | Different_operand_types _ ->
      false

let is_not_an_array t =
  match t with
  | Exp_not_an_array _ ->
      true
  | Break_outside_loop _
  | Cycle_in_type_decs _
  | Exp_not_a_record _
  | Invalid_syntax _
  | Wrong_type _
  | Unknown_type _
  | Unknown_id _
  | Id_is_a_function _
  | Id_not_a_function _
  | No_such_field_in_record _
  | Wrong_type_of_expression_in_var_dec _
  | Wrong_type_used_as_array _
  | Wrong_type_used_as_record _
  | Wrong_type_of_field_value _
  | Wrong_type_of_arg _
  | Wrong_number_of_args _
  | Invalid_operand_type _
  | Different_operand_types _ ->
      false

let is_no_such_field_in_record t =
  match t with
  | No_such_field_in_record _ ->
      true
  | Break_outside_loop _
  | Cycle_in_type_decs _
  | Exp_not_an_array _
  | Exp_not_a_record _
  | Invalid_syntax _
  | Wrong_type _
  | Unknown_type _
  | Unknown_id _
  | Id_is_a_function _
  | Id_not_a_function _
  | Wrong_type_of_expression_in_var_dec _
  | Wrong_type_used_as_array _
  | Wrong_type_used_as_record _
  | Wrong_type_of_field_value _
  | Wrong_type_of_arg _
  | Wrong_number_of_args _
  | Invalid_operand_type _
  | Different_operand_types _ ->
      false

let is_cycle_in_type_dec t =
  match t with
  | Cycle_in_type_decs _ ->
      true
  | Break_outside_loop _
  | No_such_field_in_record _
  | Exp_not_an_array _
  | Exp_not_a_record _
  | Invalid_syntax _
  | Wrong_type _
  | Unknown_type _
  | Unknown_id _
  | Id_is_a_function _
  | Id_not_a_function _
  | Wrong_type_of_expression_in_var_dec _
  | Wrong_type_used_as_array _
  | Wrong_type_used_as_record _
  | Wrong_type_of_field_value _
  | Wrong_type_of_arg _
  | Wrong_number_of_args _
  | Invalid_operand_type _
  | Different_operand_types _ ->
      false

let is_break_outside_loop t =
  match t with
  | Break_outside_loop _ ->
      true
  | Cycle_in_type_decs _
  | No_such_field_in_record _
  | Exp_not_an_array _
  | Exp_not_a_record _
  | Invalid_syntax _
  | Wrong_type _
  | Unknown_type _
  | Unknown_id _
  | Id_is_a_function _
  | Id_not_a_function _
  | Wrong_type_of_expression_in_var_dec _
  | Wrong_type_used_as_array _
  | Wrong_type_used_as_record _
  | Wrong_type_of_field_value _
  | Wrong_type_of_arg _
  | Wrong_number_of_args _
  | Invalid_operand_type _
  | Different_operand_types _ ->
      false
