From Coq Require Import List.
Require Import Crypto.Util.Notations.
Import ListNotations.

Module Tag.
  Record tagged :=
    { keyT : Type;
      key : keyT;
      valueT : Type;
      value : valueT;
      local : bool }.

  Definition Context := list tagged.

  Definition add_gen {K V} (key' : K) (value' : V) (local' : bool) (ctx : Context) : Context
    := cons {| key := key' ; value := value' ; local := local' |} ctx.

  Definition add {K V} (key' : K) (value' : V) (ctx : Context) : Context
    := add_gen key' value' false ctx.

  Definition add_local {K V} (key' : K) (value' : V) (ctx : Context) : Context
    := add_gen key' value' true ctx.

  Definition strip_local (ctx : Context) : Context
    := List.filter (fun '{| key := k ; value := v ; local := loc |}
                    => negb loc)
                   ctx.

  Definition get_local (ctx : Context) : Context
    := List.filter (fun '{| key := k ; value := v ; local := loc |}
                    => loc)
                   ctx.

  Ltac subst_from_list ls :=
    lazymatch eval hnf in ls with
    | {| value := ?id |} :: ?ls
      => try subst id; subst_from_list ls
    | nil => idtac
    end.

  Ltac subst_local ctx :=
    let loc := (eval cbv [get_local List.filter add add_local add_gen]
                 in (get_local ctx)) in
    subst_from_list loc.

  Ltac strip_local ctx :=
    let upd := (eval cbv [strip_local negb List.filter] in strip_local) in
    eval cbv [add add_local add_gen] in (upd ctx).

  Ltac strip_subst_local ctx :=
    let ret := strip_local ctx in
    let dummy := match goal with
                 | _ => subst_local ctx
                 end in
    ret.

  Ltac update ctx key value :=
    constr:(add key value ctx).

  Ltac local_update ctx key value :=
    constr:(add_local key value ctx).

  Ltac get_gen_cont ctx key' tac_found tac_not_found allow_unfound :=
    lazymatch (eval hnf in ctx) with
    | context[{| key := key' ; value := ?value' |}]
      => tac_found value'
    | context[add_gen key' ?value' _ _] => tac_found value'
    | context[add_local key' ?value' _] => tac_found value'
    | context[add key' ?value' _] => tac_found value'
    | _ => lazymatch allow_unfound with
           | true => tac_not_found ()
           end
    end.

  Ltac update_by_tac_if_not_exists ctx key value_tac :=
    get_gen_cont
      ctx key
      ltac:(fun value' => ctx)
             ltac:(fun _ => let value := value_tac () in
                            update ctx key value)
                    true.

  Ltac update_if_not_exists ctx key value :=
    update_by_tac_if_not_exists ctx key ltac:(fun _ => value).

  Ltac get_gen ctx key' default :=
    get_gen_cont ctx key' ltac:(fun v => v) default true.

  Ltac get_error ctx key' :=
    get_gen_cont ctx key' ltac:(fun v => v) ltac:(fun _ => constr:(I)) false.
  Ltac get_default ctx key' := get_gen ctx key' ltac:(fun _ => constr:(I)).

  Ltac get ctx key' := get_error ctx key'.

  Notation get ctx key' := ltac:(let v := get ctx key' in exact v) (only parsing).

  Notation empty := (@nil tagged).

  Module Export TagNotations.
    Declare Scope tagged_scope.
    Delimit Scope tagged_scope with tagged.
    Bind Scope tagged_scope with tagged.
    Notation "t[ k ~> v ]" := {| key := k ; value := v ; local := _ |} : tagged_scope.
    Notation "l[ k ~> v ]" := {| key := k ; value := v ; local := true |} : tagged_scope.
  End TagNotations.
End Tag.

Export Tag.TagNotations.
