open Tezos_utils
open Proto_alpha_utils
open Trace

module Errors = struct
(*
  TODO: those errors should have been caught in the earlier stages on the ligo pipeline
  build_contract is a kind of security net
*)
  let title_type_check_msg () = "generated Michelson contract failed to typecheck"
  let bad_parameter c () =
    let message () =
      let code = Format.asprintf "%a" Michelson.pp c in
      "bad contract parameter type (some michelson types are forbidden as contract parameter):\n"^code in
    error title_type_check_msg message
  let bad_storage c () =
    let message () =
      let code = Format.asprintf "%a" Michelson.pp c in
      "bad storage type (some michelson types are forbidden as contract storage):\n"^code in
    error title_type_check_msg message
  let bad_contract c () =
    let message () =
      let code = Format.asprintf "%a" Michelson.pp c in
      "bad contract type\n"^code in
    error title_type_check_msg message
  let ran_out_of_gas () =
    let message () = "Ran out of gas!" in
    error title_type_check_msg message
  let unknown () =
    let message () =
      "unknown error" in
    error title_type_check_msg message
end

let build_contract : ?disable_typecheck:bool -> Compiler.compiled_expression -> Michelson.michelson result =
  fun ?(disable_typecheck= false) compiled ->
  let%bind ((Ex_ty _param_ty),(Ex_ty _storage_ty)) = Self_michelson.fetch_contract_inputs compiled.expr_ty in
  let%bind param_michelson =
    Trace.trace_tzresult_lwt (simple_error "Could not unparse parameter") @@
    Proto_alpha_utils.Memory_proto_alpha.unparse_ty_michelson _param_ty in
  let%bind storage_michelson =
    Trace.trace_tzresult_lwt (simple_error "Could not unparse storage") @@
    Proto_alpha_utils.Memory_proto_alpha.unparse_ty_michelson _storage_ty in
  let contract = Michelson.contract param_michelson storage_michelson compiled.expr in
  if disable_typecheck then
    ok contract
  else
    let%bind res =
      Trace.trace_tzresult_lwt (simple_error "Could not typecheck the code") @@
      Proto_alpha_utils.Memory_proto_alpha.typecheck_contract contract in
    match res with
    | Type_checked  -> ok contract
    | Err_parameter -> fail @@ Errors.bad_parameter contract ()
    | Err_storage   -> fail @@ Errors.bad_storage contract ()
    | Err_contract  -> fail @@ Errors.bad_contract contract ()
    | Err_gas       -> fail @@ Errors.ran_out_of_gas ()
    | Err_unknown   -> fail @@ Errors.unknown ()

type check_type = Check_parameter | Check_storage
let assert_equal_contract_type : check_type -> Compiler.compiled_expression -> Compiler.compiled_expression -> unit result =
  fun c compiled_prg compiled_param ->
    let%bind (Ex_ty expected_ty) =
      let%bind (c_param_ty,c_storage_ty) = Self_michelson.fetch_contract_inputs compiled_prg.expr_ty in
      match c with
      | Check_parameter -> ok c_param_ty
      | Check_storage -> ok c_storage_ty in
    let (Ex_ty actual_ty) = compiled_param.expr_ty in
    let%bind _ = 
      Trace.trace_tzresult (simple_error "Passed parameter does not match the contract type") @@
      Proto_alpha_utils.Memory_proto_alpha.assert_equal_michelson_type expected_ty actual_ty in
    ok ()
