Require Import Crypto.Util.Decidable.
Require Import Crypto.Util.SideConditions.CorePackages.
Require Import Crypto.Util.Tactics.HeadUnderBinders.

Definition vm_decide_package (P : Prop) := P.
Definition cbv_minus_then_vm_decide_package {T} (ident : T) (P : Prop) := P.
Definition vm_compute_reflexivity_package (P : Prop) := P.
Inductive cast_bias := LHS | RHS.
Definition vm_compute_evar_package_gen {bias : cast_bias} {T} (v : T) :=
  @evar_package T v.
Notation vm_compute_evar_package_vm_small := (@vm_compute_evar_package_gen LHS).
Notation vm_compute_evar_package_vm_large := (@vm_compute_evar_package_gen RHS).
Notation vm_compute_evar_package := vm_compute_evar_package_vm_small.
Definition native_compute_evar_package_gen {bias : cast_bias} {T} (v : T) :=
  @evar_package T v.
Notation native_compute_evar_package_native_small := (@native_compute_evar_package_gen LHS).
Notation native_compute_evar_package_native_large := (@native_compute_evar_package_gen RHS).
Notation native_compute_evar_package := native_compute_evar_package_native_small.
Definition vm_compute_cbv_evar_package_gen {bias : cast_bias} {T} (v : T) :=
  @evar_package T v.
Notation vm_compute_cbv_evar_package_vm_small := (@vm_compute_cbv_evar_package_gen LHS).
Notation vm_compute_cbv_evar_package_vm_large := (@vm_compute_cbv_evar_package_gen RHS).
Notation vm_compute_cbv_evar_package := vm_compute_cbv_evar_package_vm_small.
Definition vm_cast_evar_package {s} (v : s) d := @evard_package s d v.
Definition cast_evar_package {s} (v : s) d := @evard_package s d v.
Definition vm_compute_cast_evar_package {s} (v : s) d := @evard_package s d v.

Definition optional_evar_Prop_package {T} (P : T -> Prop) (alt_pkg : Type)
  := @evar_Prop_package
       (option T)
       (fun v => match v with
                 | Some v => P v
                 | None => True
                 end).

Definition None_evar_Prop_package' {alt_pkg T P} : @optional_evar_Prop_package T P alt_pkg
  := {| val := None ; evar_package_pf := I |}.
Notation None_evar_Prop_package := (@None_evar_Prop_package' unit).

Notation optional_evar_package pkg_type
  := (optional_evar_Prop_package
        (ltac:(lazymatch eval hnf in pkg_type%type with evar_Prop_package ?P => exact P end))
        pkg_type)
       (only parsing).

Definition option_evar_rel_package {A} (v : option A) B (R : B -> A -> Prop) (alt_pkg : A -> Type)
  := @evar_Prop_package
       (option B)
       (fun v' => match v', v with
                  | Some v', Some v => R v' v
                  | None, None => True
                  | Some _, None | None, Some _ => False
                  end).

Notation optional_evar_rel_package pkg_type x
  := (option_evar_rel_package
        x
        _
        (fun a b
         => ltac:(lazymatch eval hnf in (pkg_type%function b) with
                  | evar_Prop_package ?P
                    => let P := (eval cbv beta in (P a)) in
                       exact P
                  end))
        pkg_type)
       (only parsing).

Definition unoption_evar_rel_package {A v B R alt_pkg}
           (f : forall v, alt_pkg v -> @evar_rel_package A v B R)
  : forall (pkg : match v with
                  | Some v => alt_pkg v
                  | None => True
                  end),
    @option_evar_rel_package A v B R alt_pkg
  := match v
           return match v with
                  | Some v => alt_pkg v
                  | None => True
                  end -> @option_evar_rel_package A v B R alt_pkg
     with
     | Some v => fun pkg => {| val := Some (val (f _ pkg));
                               evar_package_pf := evar_package_pf (f _ pkg) |}
     | None => fun _ => None_evar_Prop_package
     end.

Ltac autosolve autosolve_tac else_tac :=
  lazymatch goal with
  | [ |- match ?x with Some _ => _ | None => _ end ]
    => let x' := (eval hnf in x) in
       progress change x with x';
       cbv beta iota;
       autosolve_tac else_tac
  | [ |- vm_decide_package (_ = true) ] => abstract vm_cast_no_check (eq_refl true)
  | [ |- vm_decide_package (_ = false) ] => abstract vm_cast_no_check (eq_refl false)
  | [ |- vm_decide_package ?P ] => cbv beta delta [vm_decide_package]; vm_decide
  | [ |- cbv_minus_then_vm_decide_package ?ident ?P ] => cbv -[ident]; vm_decide
  | [ |- vm_compute_reflexivity_package ?P ] => vm_compute; reflexivity
  | [ |- vm_compute_evar_package_vm_small ?v ]
    => let v' := (eval vm_compute in v) in
       (exists v'); abstract vm_cast_no_check (eq_refl v')
  | [ |- vm_compute_evar_package_vm_large ?v ]
    => let v' := (eval vm_compute in v) in
       (exists v'); abstract vm_cast_no_check (eq_refl v)
  | [ |- vm_compute_cbv_evar_package_vm_small ?v ]
    => let v' := (eval vm_compute in v) in
       let v' := (eval cbv in v) in
       (exists v'); abstract vm_cast_no_check (eq_refl v')
  | [ |- vm_compute_cbv_evar_package_vm_large ?v ]
    => let v' := (eval vm_compute in v) in
       let v' := (eval cbv in v) in
       (exists v'); abstract vm_cast_no_check (eq_refl v)
  | [ |- native_compute_evar_package_native_small ?v ]
    => let v' := (eval native_compute in v) in
       (exists v'); abstract native_cast_no_check (eq_refl v')
  | [ |- native_compute_evar_package_native_large ?v ]
    => let v' := (eval native_compute in v) in
       (exists v'); abstract native_cast_no_check (eq_refl v)
  | [ |- vm_cast_evar_package ?v ?d ]
    => unshelve eexists (v <: d);
       [ vm_compute; reflexivity
       | cbv beta;
         let lhs := lazymatch goal with |- ?lhs = _ => lhs end in
         abstract exact_no_check (eq_refl lhs) ]
  | [ |- cast_evar_package (s:=?s) ?v ?d ]
    => exact (@Build_evard_package
                s d v
                v eq_refl eq_refl)
  | [ |- vm_compute_cast_evar_package (s:=?s) ?v ?d ]
    => let v' := (eval vm_compute in v) in
       exact (@Build_evard_package
                s d v
                v' eq_refl eq_refl)
  | [ |- @option_evar_rel_package ?A ?v ?B ?R ?alt_pkg ]
    => refine (@unoption_evar_rel_package A v B R alt_pkg (fun _ x => x) _);
       autosolve_tac else_tac
  | _ => else_tac ()
  end.
