\import Algebra.Group
\import Algebra.Monoid
\import Algebra.Solver \using (>>=-consistent)
\import Algebra.Solver.Monoid
\import Arith.Fin.Order
\import Data.Bool
\import Data.List
\import Function.Meta
\import Logic
\import Meta
\import Paths
\import Paths.Meta
\import Set

\func GroupSolverModel (G : Group) : SubstSolverModel G \cowith
  | Term => Term
  | NF n => List (\Sigma Bool (Fin n))
  | normalize => normalize
  | interpret {n : Nat} (env : Fin n -> G) (t : Term n) : G \elim t {
    | var x => env x
    | :ide => G.ide
    | :inverse t => inverse (interpret env t)
    | t :* s => interpret env t * interpret env s
  }
  | interpretNF env l => interpretNF' env (reduce l)
  | interpretNF-consistent => interpretNF'_reduce *> interpretNF'-consistent
  | nfVar v => (true,v) :: nil
  | >>= => >>=
  | >>=-consistent => interpretNF'_reduce *> interpretNF'_>>= *> pmap (interpretNF' __ _) (ext \lam v => inv interpretNF'_reduce) *> inv interpretNF'_reduce
  \where {
    \data Term (n : Nat)
      | var (Fin n)
      | :ide
      | :inverse (Term n)
      | \infixl 7 :* (t s : Term n)

    \private \func inverseNF {V : \Set} (l acc : List (\Sigma Bool V)) : List (\Sigma Bool V) \elim l
      | nil => acc
      | x :: l => inverseNF l ((not x.1, x.2) :: acc)

    \private \func normalize {n : Nat} (t : Term n) : List (\Sigma Bool (Fin n)) \elim t
      | var v => (true,v) :: nil
      | :ide => nil
      | :inverse t => inverseNF (normalize t) nil
      | t :* s => normalize t ++ normalize s

    \private \func reduce {n : Nat} (l : List (\Sigma Bool (Fin n))) : List (\Sigma Bool (Fin n)) \elim l
      | nil => nil
      | x :: tail => \case reduce tail \with {
        | nil => x :: nil
        | y :: l => \case decideEq x.1 y.1, decideEq x.2 y.2 \with {
          | yes e, _ => x :: y :: l
          | no q, yes e => l
          | no q, no q' => x :: y :: l
        }
      }

    \private \func interpretNF' {V : \Set} (env : V -> G) (l : List (\Sigma Bool V)) : G \elim l
      | nil => G.ide
      | x :: nil => if x.1 (env x.2) (inverse (env x.2))
      | x :: l => if x.1 (env x.2) (inverse (env x.2)) * interpretNF' env l
      \where \protected {
        \func simple (env : V -> G) (l : List (\Sigma Bool V)) : G \elim l
          | nil => G.ide
          | x :: l => if x.1 (env x.2) (inverse (env x.2)) * simple env l

        \lemma =simple {l : List (\Sigma Bool V)} : interpretNF' env l = simple env l \elim l
          | nil => idp
          | x :: nil => inv ide-right
          | x :: y :: l => pmap (_ *) =simple
      }

    \private \lemma interpretNF'_:: {V : \Set} {env : V -> G} {x : \Sigma Bool V} {l : List (\Sigma Bool V)}
      : interpretNF' env (x :: l) = if x.1 (env x.2) (inverse (env x.2)) * interpretNF' env l \elim l
      | nil => inv ide-right
      | y :: l => idp

    \private \lemma interpretNF'_reduce {n : Nat} {env : Fin n -> G} {l : List (\Sigma Bool (Fin n))} : interpretNF' env (reduce l) = interpretNF' env l
      => interpretNF'.=simple *> simple_reduce l *> inv interpretNF'.=simple
      \where
        \private \lemma simple_reduce (l : List (\Sigma Bool (Fin n))) : interpretNF'.simple env (reduce l) = interpretNF'.simple env l \elim l
          | nil => idp
          | x :: tail => mcases {1} {arg addPath} \with {
            | nil, p => pmap (_ *) (inv (pmap (interpretNF'.simple env) p) *> simple_reduce tail)
            | y :: l, p => mcases (pmap (_ *) $ inv (pmap (interpretNF'.simple env) p) *> simple_reduce tail) \with {
              | no q, yes e => rewrite (inv $ simple_reduce tail, p) $ inv (pmap (`* _) (later $ cases (x.1, y.1, q) \with {
                | false, true, _ => rewrite e inverse-left
                | true, false, _ => rewrite e inverse-right
                | false, false, q => absurd (q idp)
                | true, true, q => absurd (q idp)
              }) *> ide-left) *> *-assoc
            }
          }

    \private \lemma interpretNF'_++ {V : \Set} {env : V -> G} {l l' : List (\Sigma Bool V)}
      : interpretNF' env (l ++ l') = interpretNF' env l * interpretNF' env l'
      => interpretNF'.=simple *> _simple *> inv (pmap2 (*) interpretNF'.=simple interpretNF'.=simple)
      \where
        \protected \lemma _simple {l : List (\Sigma Bool V)} : interpretNF'.simple env (l ++ l') = interpretNF'.simple env l * interpretNF'.simple env l' \elim l
          | nil => inv ide-left
          | x :: l => pmap (_ *) _simple *> inv *-assoc

    \private \lemma interpretNF'_inverseNF {V : \Set} {env : V -> G} {l acc : List (\Sigma Bool V)}
      : interpretNF' env (inverseNF l acc) = inverse (interpretNF' env l) * interpretNF' env acc
      => interpretNF'.=simple *> _simple *> inv (pmap2 (inverse __ * __) interpretNF'.=simple interpretNF'.=simple)
      \where
        \protected \lemma _simple {l acc : List (\Sigma Bool V)} : interpretNF'.simple env (inverseNF l acc) = inverse (interpretNF'.simple env l) * interpretNF'.simple env acc \elim l
          | nil => inv $ pmap (`* _) G.inverse_ide *> ide-left
          | (b,v) :: l => _simple *> inv *-assoc *> pmap (`* _) \case \elim b \with {
            | false => inv $ G.inverse_* *> pmap (_ *) G.inverse-isInv
            | true => inv G.inverse_*
          }

    \private \lemma interpretNF'-consistent {n : Nat} {env : Fin n -> G} {t : Term n} : interpretNF' env (normalize t) = interpret env t \elim t
      | var v => idp
      | :ide => idp
      | :inverse t => interpretNF'_inverseNF *> ide-right *> pmap inverse interpretNF'-consistent
      | t :* s => interpretNF'_++ *> pmap2 (*) interpretNF'-consistent interpretNF'-consistent

    \func \infixl 2 >>= {U V : \Set} (l : List (\Sigma Bool U)) (k : U -> List (\Sigma Bool V)) : List (\Sigma Bool V) \elim l
      | nil => nil
      | (true,u) :: l => k u ++ (l >>= k)
      | (false,u) :: l => inverseNF (k u) nil ++ (l >>= k)

    \private \func interpretNF'_>>= {m n : Nat} {env : Fin n -> G} {l : List (\Sigma Bool (Fin m))} {k : Fin m -> List (\Sigma Bool (Fin n))}
      : interpretNF' env (l >>= k) = interpretNF' (\lam v => interpretNF' env (k v)) l
      => interpretNF'.=simple *> _simple *> inv (interpretNF'.=simple *> pmap (interpretNF'.simple __ l) (ext \lam v => interpretNF'.=simple))
      \where
        \private \func _simple {l : List (\Sigma Bool (Fin m))} : interpretNF'.simple env (l >>= k) = interpretNF'.simple (\lam v => interpretNF'.simple env (k v)) l \elim l
          | nil => idp
          | (true,u) :: l => interpretNF'_++._simple *> pmap (_ *) _simple
          | (false,u) :: l => interpretNF'_++._simple *> pmap2 (*) (interpretNF'_inverseNF._simple *> ide-right) _simple
  }

\func AddGroupSolverModel (A : AddGroup) : SubstSolverModel A
  => GroupSolverModel (AddGroup.toGroup A)