module Tactic.Nat.Simpl.Lemmas where

open import Prelude
open import Tactic.Nat.NF
open import Tactic.Nat.Exp
open import Data.Bag
open import Tactic.Nat.Auto
open import Data.Nat.Properties.Core
open import Data.List.Properties
open import Tactic.Nat.Auto.Lemmas

product1-sound :  xs  product1 xs  product xs
product1-sound [] = refl
product1-sound (x  xs)
  rewrite sym (cong  x  foldl _*_ x xs) (mul-1-r x))
        | foldl-assoc _*_ mul-assoc x 1 xs
        | foldl-foldr _*_ 1 mul-assoc  _  refl) mul-1-r xs
        = refl

map-eq :  {c b} {A : Set c} {B : Set b} (f g : A  B) 
           (∀ x  f x  g x)   xs  map f xs  map g xs
map-eq f g f=g [] = refl
map-eq f g f=g (x  xs) rewrite f=g x | map-eq f g f=g xs = refl

fst-*** :  {a b} {A₁ A₂ : Set a} {B₁ B₂ : Set b}
            (f : A₁  B₁) (g : A₂  B₂) (p : A₁ × A₂) 
            fst ((f *** g) p)  f (fst p)
fst-*** f g (x , y) = refl

snd-*** :  {a b} {A₁ A₂ : Set a} {B₁ B₂ : Set b}
            (f : A₁  B₁) (g : A₂  B₂) (p : A₁ × A₂) 
            snd ((f *** g) p)  g (snd p)
snd-*** f g (x , y) = refl

eta :  {a b} {A : Set a} {B : Set b} (p : A × B)  p  (fst p , snd p)
eta (x , y) = refl

private
  shuffle₁ : (a b c : Nat)  a + (b + c)  b + (a + c)
  shuffle₁ a b c = auto

module _ {Atom : Set} {{_ : Ord Atom}} where

  NFEqS : NF Atom × NF Atom  Env Atom  Set
  NFEqS (nf₁ , nf₂) ρ =  nf₁ ⟧ns ρ   nf₂ ⟧ns ρ

  NFEq : NF Atom × NF Atom  Env Atom  Set
  NFEq (nf₁ , nf₂) ρ =  nf₁ ⟧n ρ   nf₂ ⟧n ρ

  ts-sound :  x (ρ : Env Atom)   x ⟧ts ρ   x ⟧t ρ
  ts-sound (0 , x) ρ = mul-0-r (product1 (map ρ x))
  ts-sound (1 , x) ρ = product1-sound (map ρ x)
  ts-sound (suc (suc i) , x) ρ
    rewrite sym (product1-sound (map ρ x))
          = auto

  private
    et : Env Atom  Nat × Tm Atom  Nat
    et  = flip ⟦_⟧t

    ets : Env Atom  Nat × Tm Atom  Nat
    ets = flip ⟦_⟧ts

    plus-nf : Nat  Env Atom  NF Atom  Nat
    plus-nf = λ a ρ xs  a +  xs ⟧n ρ

  ns-sound :  nf (ρ : Env Atom)   nf ⟧ns ρ   nf ⟧n ρ
  ns-sound [] ρ = refl
  ns-sound (x  nf) ρ
    rewrite sym (foldl-map-fusion _+_ (ets ρ) (ets ρ x) nf)
          | ts-sound x ρ
          | map-eq (ets ρ) (et ρ) (flip ts-sound ρ) nf
          | sym (foldl-foldr _+_ 0 add-assoc  _  refl) add-0-r (map (et ρ) nf))
          | sym (foldl-assoc _+_ add-assoc (et ρ x) 0 (map (et ρ) nf))
          | add-0-r (et ρ x)
          = refl

  private
    lem-sound :  a b ρ f g (xs : NF Atom × NF Atom) 
            a +  fst ((f *** g) xs) ⟧n ρ  b +  snd ((f *** g) xs) ⟧n ρ 
            a +  f (fst xs) ⟧n ρ          b +  g (snd xs) ⟧n ρ
    lem-sound a b ρ f g xs H =
      cong (plus-nf a ρ) (fst-*** f g xs)
      ʳ⟨≡⟩ H
       ⟨≡⟩ cong (plus-nf b ρ) (snd-*** f g xs)

  cancel-sound′ :  a b nf₁ nf₂ (ρ : Env Atom) 
                    a +  fst (cancel nf₁ nf₂) ⟧n ρ  b +  snd (cancel nf₁ nf₂) ⟧n ρ 
                    a +  nf₁ ⟧n ρ  b +  nf₂ ⟧n ρ
  cancel-sound′ a b [] []        ρ H = H
  cancel-sound′ a b [] (x  nf₂) ρ H = H
  cancel-sound′ a b (x  nf₁) [] ρ H = H
  cancel-sound′ a b ((i , x)  nf₁) ((j , y)  nf₂) ρ H
    with compare x y
  ... | less _ = add-assoc a _ _ ⟨≡⟩ cancel-sound′ (a + et ρ (i , x)) b nf₁ ((j , y)  nf₂) ρ
                   (add-assoc a _ _ ʳ⟨≡⟩ lem-sound a b ρ (_∷_ (i , x)) id (cancel nf₁ ((j , y)  nf₂)) H)
  ... | greater _ =
          cancel-sound′ a (b + et ρ (j , y)) ((i , x)  nf₁) nf₂ ρ
            (lem-sound a b ρ id (_∷_ (j , y)) (cancel ((i , x)  nf₁) nf₂) H ⟨≡⟩ add-assoc b _ _)
          ⟨≡⟩ʳ add-assoc b _ _
  cancel-sound′ a b ((i , x)  nf₁) ((j , .x)  nf₂) ρ H | equal refl
    with compare i j
  cancel-sound′ a b ((i , x)  nf₁) ((.(suc k + i) , .x)  nf₂) ρ H | equal refl | less (diff! k) =
    shuffle₁ a (et ρ (i , x)) _
    ⟨≡⟩ cong (et ρ (i , x) +_) (cancel-sound′ a (b + et ρ (suc k , x)) nf₁ nf₂ ρ
          (lem-sound a b ρ id (_∷_ (suc k , x)) (cancel nf₁ nf₂) H ⟨≡⟩ add-assoc b _ _))
    ⟨≡⟩ auto
  cancel-sound′ a b ((.(suc k + j) , x)  nf₁) ((j , .x)  nf₂) ρ H | equal refl | greater (diff! k) =
    sym (shuffle₁ b (et ρ (j , x)) _
         ⟨≡⟩ cong (et ρ (j , x) +_) (sym (cancel-sound′ (a + et ρ (suc k , x)) b nf₁ nf₂ ρ
               (add-assoc a _ _ ʳ⟨≡⟩ lem-sound a b ρ (_∷_ (suc k , x)) id (cancel nf₁ nf₂) H)))
         ⟨≡⟩ auto)
  cancel-sound′ a b ((i , x)  nf₁) ((.i , .x)  nf₂) ρ H | equal refl | equal refl =
    shuffle₁ a (et ρ (i , x)) _
    ⟨≡⟩ cong (et ρ (i , x) +_) (cancel-sound′ a b nf₁ nf₂ ρ H)
    ⟨≡⟩ shuffle₁ (et ρ (i , x)) b _

  cancel-sound-s′ :  a b nf₁ nf₂ (ρ : Env Atom) 
                         a +  fst (cancel nf₁ nf₂) ⟧ns ρ  b +  snd (cancel nf₁ nf₂) ⟧ns ρ 
                         a +  nf₁ ⟧ns ρ  b +  nf₂ ⟧ns ρ
  cancel-sound-s′ a b nf₁ nf₂ ρ eq =
    (a +_) $≡ ns-sound nf₁ ρ ⟨≡⟩
    cancel-sound′ a b nf₁ nf₂ ρ
      ((a +_) $≡ ns-sound (fst (cancel nf₁ nf₂)) ρ ʳ⟨≡⟩
       eq ⟨≡⟩ (b +_) $≡ ns-sound (snd (cancel nf₁ nf₂)) ρ) ⟨≡⟩ʳ
    (b +_) $≡ ns-sound nf₂ ρ

  cancel-sound :  nf₁ nf₂ ρ  NFEqS (cancel nf₁ nf₂) ρ  NFEq (nf₁ , nf₂) ρ
  cancel-sound nf₁ nf₂ ρ H rewrite cong  p  NFEqS p ρ) (eta (cancel nf₁ nf₂)) =
    (cancel-sound′ 0 0 nf₁ nf₂ ρ
      (ns-sound (fst (cancel nf₁ nf₂)) ρ
         ʳ⟨≡⟩ H ⟨≡⟩
       ns-sound (snd (cancel nf₁ nf₂)) ρ))

  private
    prod : Env Atom  List Atom  Nat
    prod ρ x = product (map ρ x)

  private
    lem-complete :  a b ρ f g (xs : NF Atom × NF Atom) 
            a +  f (fst xs) ⟧n ρ          b +  g (snd xs) ⟧n ρ 
            a +  fst ((f *** g) xs) ⟧n ρ  b +  snd ((f *** g) xs) ⟧n ρ
    lem-complete a b ρ f g xs H =
      cong (plus-nf a ρ) (fst-*** f g xs)
       ⟨≡⟩  H
       ⟨≡⟩ʳ cong (plus-nf b ρ) (snd-*** f g xs)

  cancel-complete′ :  a b nf₁ nf₂ (ρ : Env Atom) 
                       a +  nf₁ ⟧n ρ  b +  nf₂ ⟧n ρ 
                       a +  fst (cancel nf₁ nf₂) ⟧n ρ  b +  snd (cancel nf₁ nf₂) ⟧n ρ
  cancel-complete′ a b [] [] ρ H = H
  cancel-complete′ a b [] (x  nf₂) ρ H = H
  cancel-complete′ a b (x  nf₁) [] ρ H = H
  cancel-complete′ a b ((i , x)  nf₁) ((j , y)  nf₂) ρ H with compare x y
  ... | less lt =
    lem-complete a b ρ (_∷_ (i , x)) id (cancel nf₁ ((j , y)  nf₂))
      (add-assoc a _ _
       ⟨≡⟩ cancel-complete′ (a + et ρ (i , x)) b nf₁ ((j , y)  nf₂) ρ
             (add-assoc a _ _ ʳ⟨≡⟩ H))
  ... | greater _ =
    lem-complete a b ρ id (_∷_ (j , y)) (cancel ((i , x)  nf₁) nf₂)
      (cancel-complete′ a (b + et ρ (j , y)) ((i , x)  nf₁) nf₂ ρ
        (H ⟨≡⟩ add-assoc b _ _)
       ⟨≡⟩ʳ add-assoc b _ _)
  cancel-complete′ a b ((i , x)  nf₁) ((j , .x)  nf₂) ρ H | equal refl with compare i j
  cancel-complete′ a b ((i , x)  nf₁) ((.(suc (k + i)) , .x)  nf₂) ρ H | equal refl | less (diff! k) =
    lem-complete a b ρ id (_∷_ (suc k , x)) (cancel nf₁ nf₂)
      (cancel-complete′ a (b + suc k * prod ρ x) nf₁ nf₂ ρ
        (add-inj₂ (i * prod ρ x) _ _
          (shuffle₁ (i * prod ρ x) a _
            ⟨≡⟩ H ⟨≡⟩ auto))
      ⟨≡⟩ʳ add-assoc b _ _)
  cancel-complete′ a b ((.(suc (k + j)) , x)  nf₁) ((j , .x)  nf₂) ρ H | equal refl | greater (diff! k) =
    lem-complete a b ρ (_∷_ (suc k , x)) id (cancel nf₁ nf₂)
      (add-assoc a _ _ ⟨≡⟩
       cancel-complete′ (a + suc k * prod ρ x) b nf₁ nf₂ ρ
         (add-inj₂ (j * prod ρ x) _ _
           (sym (shuffle₁ (j * prod ρ x) b _ ⟨≡⟩ʳ
                 auto ʳ⟨≡⟩ H))))
  cancel-complete′ a b ((i , x)  nf₁) ((.i , .x)  nf₂) ρ H | equal refl | equal refl =
    cancel-complete′ a b nf₁ nf₂ ρ
      (add-inj₂ (i * prod ρ x) _ _
        (shuffle₁ a (i * prod ρ x) _ ʳ⟨≡⟩ H ⟨≡⟩ shuffle₁ b (i * prod ρ x) _))

  cancel-complete-s′ :  a b nf₁ nf₂ (ρ : Env Atom) 
                         a +  nf₁ ⟧ns ρ  b +  nf₂ ⟧ns ρ 
                         a +  fst (cancel nf₁ nf₂) ⟧ns ρ  b +  snd (cancel nf₁ nf₂) ⟧ns ρ
  cancel-complete-s′ a b nf₁ nf₂ ρ eq =
    (a +_) $≡ ns-sound (fst (cancel nf₁ nf₂)) ρ ⟨≡⟩
    cancel-complete′ a b nf₁ nf₂ ρ
      ((a +_) $≡ ns-sound nf₁ ρ ʳ⟨≡⟩
       eq ⟨≡⟩ (b +_) $≡ ns-sound nf₂ ρ) ⟨≡⟩ʳ
    (b +_) $≡ ns-sound (snd (cancel nf₁ nf₂)) ρ

  cancel-complete :  nf₁ nf₂ ρ  NFEq (nf₁ , nf₂) ρ  NFEqS (cancel nf₁ nf₂) ρ
  cancel-complete nf₁ nf₂ ρ H rewrite cong  p  NFEqS p ρ) (eta (cancel nf₁ nf₂)) =
         ns-sound (fst (cancel nf₁ nf₂)) ρ
    ⟨≡⟩  cancel-complete′ 0 0 nf₁ nf₂ ρ H
    ⟨≡⟩ʳ ns-sound (snd (cancel nf₁ nf₂)) ρ