(** * Propositions with a distinguished point representing true *)
(** This allows for something between [bool] and [Prop], where we can
    computationally reduce things like [True /\ True], but can still
    express equality of types. *)
From Coq Require Import Setoid.
Require Import Crypto.Util.Notations.

Declare Scope pointed_prop_scope.
Declare Scope option_pointed_prop_scope.
Delimit Scope pointed_prop_scope with pointed_prop.
Delimit Scope option_pointed_prop_scope with option_pointed_prop.
Inductive pointed_Prop := trivial | inject (_ : Prop).
Bind Scope pointed_prop_scope with pointed_Prop.

Definition to_prop (x : pointed_Prop) : Prop
  := match x with
     | trivial => True
     | inject p => p
     end.

Coercion option_pointed_Prop_of_bool (x : bool) : option pointed_Prop
  := if x then Some trivial else None.
Coercion option_pointed_Prop_of_pointed_Prop (x : pointed_Prop) : option pointed_Prop
  := Some x.

Definition prop_of_option (x : option pointed_Prop) : Prop
  := match x with
     | Some p => to_prop p
     | None => False
     end.

Definition and_pointed_Prop (x y : pointed_Prop) : pointed_Prop
  := match x, y with
     | trivial, y => y
     | x, trivial => x
     | inject p, inject q => inject (p /\ q)
     end.

Definition or_pointed_Prop (x y : pointed_Prop) : pointed_Prop
  := match x, y with
     | trivial, _ => trivial
     | _, trivial => trivial
     | inject p, inject q => inject (p \/ q)
     end.

Definition impl_pointed_Prop (x y : pointed_Prop) : pointed_Prop
  := match x, y with
     | trivial, y => y
     | _, trivial => trivial
     | inject p, inject q => inject (p -> q)
     end.

Definition not_option_pointed_Prop (x : option pointed_Prop) : option pointed_Prop
  := match x with
     | Some trivial => None
     | None => Some trivial
     | Some (inject p) => Some (inject (~p))
     end.
Arguments not_option_pointed_Prop _%option_pointed_prop.

Definition and_option_pointed_Prop (x y : option pointed_Prop) : option pointed_Prop
  := match x, y with
     | Some x, Some y => Some (and_pointed_Prop x y)
     | _, _ => None
     end.
Arguments and_option_pointed_Prop (_ _)%option_pointed_prop.

Definition or_option_pointed_Prop (x y : option pointed_Prop) : option pointed_Prop
  := match x, y with
     | Some x, Some y => Some (or_pointed_Prop x y)
     | Some x, None => Some x
     | None, Some y => Some y
     | None, None => None
     end.
Arguments or_option_pointed_Prop (_ _)%option_pointed_prop.

Definition impl_option_pointed_Prop (x y : option pointed_Prop) : option pointed_Prop
  := match x, y with
     | None, _ => Some trivial
     | Some x, Some y => Some (impl_pointed_Prop x y)
     | Some p, None => not_option_pointed_Prop p
     end.
Arguments impl_option_pointed_Prop (_ _)%option_pointed_prop.

Infix "/\" := and_pointed_Prop : pointed_prop_scope.
Infix "\/" := or_pointed_Prop : pointed_prop_scope.
Infix "->" := impl_pointed_Prop : pointed_prop_scope.
Infix "/\" := and_option_pointed_Prop : option_pointed_prop_scope.
Infix "\/" := or_option_pointed_Prop : option_pointed_prop_scope.
Infix "->" := impl_option_pointed_Prop : option_pointed_prop_scope.
Notation "~ P" := (not_option_pointed_Prop P) : option_pointed_prop_scope.

Ltac inversion_pointed_Prop_step :=
  match goal with
  | [ H : inject _ = inject _ |- _ ] => progress (inversion H; clear H)
  end.
Ltac inversion_pointed_Prop := repeat inversion_pointed_Prop_step.

Create HintDb push_prop_of_option discriminated.
Create HintDb push_to_prop discriminated.
Create HintDb push_eq_trivial discriminated.
Create HintDb push_eq_Some_trivial discriminated.
Global Hint Extern 1 => progress autorewrite with push_prop_of_option in * : push_prop_of_option.
Global Hint Extern 1 => progress autorewrite with push_to_prop in * : push_to_prop.
Global Hint Extern 1 => progress autorewrite with push_eq_trivial in * : push_eq_trivial.
Global Hint Extern 1 => progress autorewrite with push_eq_Some_trivial in * : push_eq_Some_trivial.

Lemma to_prop_and P Q : to_prop (P /\ Q)%pointed_prop
                        <-> (to_prop P /\ to_prop Q).
Proof. destruct P, Q; simpl; tauto. Qed.
#[global]
Hint Rewrite to_prop_and : push_to_prop.

Lemma to_prop_or P Q : to_prop (P \/ Q)%pointed_prop
                       <-> (to_prop P \/ to_prop Q).
Proof. destruct P, Q; simpl; tauto. Qed.
#[global]
Hint Rewrite to_prop_or : push_to_prop.

Lemma to_prop_impl P Q : to_prop (impl_pointed_Prop P Q)%pointed_prop
                         <-> (to_prop P -> to_prop Q).
Proof. destruct P, Q; simpl; tauto. Qed.
#[global]
Hint Rewrite to_prop_impl : push_to_prop.

Lemma prop_of_option_and P Q : prop_of_option (P /\ Q)%option_pointed_prop
                               <-> (prop_of_option P /\ prop_of_option Q).
Proof. destruct P, Q; simpl; autorewrite with push_to_prop; tauto. Qed.
#[global]
Hint Rewrite prop_of_option_and : push_prop_of_option.

Lemma prop_of_option_or P Q : prop_of_option (P \/ Q)%option_pointed_prop
                               <-> (prop_of_option P \/ prop_of_option Q).
Proof. destruct P, Q; simpl; autorewrite with push_to_prop; tauto. Qed.
#[global]
Hint Rewrite prop_of_option_or : push_prop_of_option.

Lemma prop_of_option_impl P Q : prop_of_option (impl_option_pointed_Prop P Q)%option_pointed_prop
                               <-> (prop_of_option P -> prop_of_option Q).
Proof. destruct P as [ [|P] |], Q as [ [|Q] |]; simpl; tauto. Qed.
#[global]
Hint Rewrite prop_of_option_impl : push_prop_of_option.

Lemma prop_of_option_not P : prop_of_option (~P)%option_pointed_prop
                               <-> (~prop_of_option P).
Proof. destruct P as [ [|] | ]; simpl; tauto. Qed.
#[global]
Hint Rewrite prop_of_option_not : push_prop_of_option.

Lemma eq_trivial_and P Q : (P /\ Q)%pointed_prop = trivial
                           <-> (P = trivial /\ Q = trivial).
Proof. destruct P, Q; simpl; intuition congruence. Qed.
#[global]
Hint Rewrite eq_trivial_and : push_eq_trivial.

Lemma eq_trivial_or P Q : (P \/ Q)%pointed_prop = trivial
                          <-> (P = trivial \/ Q = trivial).
Proof. destruct P, Q; simpl; intuition congruence. Qed.
#[global]
Hint Rewrite eq_trivial_or : push_eq_trivial.

Lemma eq_trivial_impl P Q : (impl_pointed_Prop P Q)%pointed_prop = trivial
                         <-> Q = trivial.
Proof. destruct P, Q; simpl; intuition congruence. Qed.
#[global]
Hint Rewrite eq_trivial_impl : push_eq_trivial.

Lemma eq_Some_trivial_and P Q : (P /\ Q)%option_pointed_prop = Some trivial
                               <-> (P = Some trivial /\ Q = Some trivial).
Proof. destruct P as [ []|], Q as [ []|]; simpl; intuition congruence. Qed.
#[global]
Hint Rewrite eq_Some_trivial_and : push_eq_Some_trivial.

Lemma eq_Some_trivial_or P Q : (P \/ Q)%option_pointed_prop = Some trivial
                               <-> (P = Some trivial \/ Q = Some trivial).
Proof. destruct P as [ []|], Q as [ []|]; simpl; intuition congruence. Qed.
#[global]
Hint Rewrite eq_Some_trivial_or : push_eq_Some_trivial.

Lemma eq_Some_trivial_impl P Q : (impl_option_pointed_Prop P Q)%option_pointed_prop = Some trivial
                               <-> (P = None \/ Q = Some trivial).
Proof. destruct P as [ [|P] |], Q as [ [|Q] |]; simpl; intuition congruence. Qed.
#[global]
Hint Rewrite eq_Some_trivial_impl : push_eq_Some_trivial.

Lemma eq_Some_trivial_not P : (~P)%option_pointed_prop = Some trivial
                               <-> P = None.
Proof. destruct P as [ [|] | ]; simpl; intuition congruence. Qed.
#[global]
Hint Rewrite eq_Some_trivial_not : push_eq_Some_trivial.
