prelude
import init.logic init.classical

universe variables u

-- For n-ary support
structure [class] is_associative {A : Type u} (op : A → A → A) :=
(op_assoc : ∀ x y z : A, op (op x y) z = op x (op y z))

attribute [instance]
definition and_is_associative : is_associative and :=
is_associative.mk (λ x y z, propext (@and.assoc x y z))

attribute [instance]
definition or_is_associative : is_associative or :=
is_associative.mk (λ x y z, propext (@or.assoc x y z))

-- Basic congruence theorems over equality (using propext)
attribute [congr]
theorem imp_congr_ctx_eq {P₁ P₂ Q₁ Q₂ : Prop} (H₁ : P₁ = P₂) (H₂ : P₂ → (Q₁ = Q₂)) : (P₁ → Q₁) = (P₂ → Q₂) :=
propext (imp_congr_ctx (iff.of_eq H₁) (assume p₂, iff.of_eq (H₂ p₂)))

attribute [congr]
theorem forall_congr_eq {A : Type u} (P Q : A → Prop) (H : ∀ a, P a = Q a) : ((∀ a, P a) = (∀ a, Q a)) :=
propext (forall_congr (assume a, iff.of_eq (H a)))

-- Congruence theorems for flattening
namespace simplifier
variables {A : Type u}

theorem congr_bin_op {op op' : A → A → A} (H : op = op') (x y : A) : op x y = op' x y :=
congr_fun (congr_fun H x) y

theorem congr_bin_arg1 {op : A → A → A} {x x' y : A} (Hx : x = x') : op x y = op x' y :=
congr_fun (congr_arg op Hx) y

theorem congr_bin_arg2 {op : A → A → A} {x y y' : A} (Hy : y = y') : op x y = op x y' :=
congr_arg (op x) Hy

theorem congr_bin_args {op : A → A → A} {x x' y y' : A} (Hx : x = x') (Hy : y = y') : op x y = op x' y' :=
congr (congr_arg op Hx) Hy

theorem assoc_subst {op op' : A → A → A} (H : op = op') (H_assoc : ∀ x y z, op (op x y) z = op x (op y z))
  : (∀ x y z, op' (op' x y) z = op' x (op' y z)) :=
H ▸ H_assoc

end simplifier
