import BidirTT.Value

namespace BidirTT

abbrev EvalM := Except String

mutual
  partial def eval : Env → Tm → EvalM Val
    | env, .var i =>
        match env[i]? with
        | some v => pure v
        | none =>
            throw s!"bad de Bruijn index {i} in environment of size {env.length}"
    | env, .lam t      => pure (.lam (.mk env t))
    | env, .app t u    => do
        let vt ← eval env t
        let vu ← eval env u
        vApp vt vu
    | env, .pi a b     => do
        let va ← eval env a
        pure (.pi va (.mk env b))
    | env, .sig a b    => do
        let va ← eval env a
        pure (.sig va (.mk env b))
    | env, .pair t u   => do
        let vt ← eval env t
        let vu ← eval env u
        pure (.pair vt vu)
    | env, .fst t      => do
        let vt ← eval env t
        vFst vt
    | env, .snd t      => do
        let vt ← eval env t
        vSnd vt
    | _,   .nat        => pure .nat
    | _,   .zero       => pure .zero
    | env, .succ t     => do
        let vt ← eval env t
        pure (.succ vt)
    | env, .natElim m z s n => do
        let vm ← eval env m
        let vz ← eval env z
        let vs ← eval env s
        let vn ← eval env n
        vNatElim vm vz vs vn
    | _,   .unit       => pure .unit
    | _,   .triv       => pure .triv
    | env, .unitElim m t u => do
        let vm ← eval env m
        let vt ← eval env t
        let vu ← eval env u
        vUnitElim vm vt vu
    | _,   .empty      => pure .empty
    | env, .emptyElim m e => do
        let vm ← eval env m
        let ve ← eval env e
        vEmptyElim vm ve
    | env, .id a t u   => do
        let va ← eval env a
        let vt ← eval env t
        let vu ← eval env u
        pure (.id va vt vu)
    | _,   .refl       => pure .refl
    | env, .idElim m r y p => do
        let vm ← eval env m
        let vr ← eval env r
        let vy ← eval env y
        let vp ← eval env p
        vIdElim vm vr vy vp
    | _,   .univ i     => pure (.univ i.normalise)
    | env, .letE _ t u => do
        let vt ← eval env t
        eval (vt :: env) u

  partial def vApp : Val → Val → EvalM Val
    | .lam c,   u => cApp c u
    | .neu t,   u => pure (.neu (.app t u))
    | _,        _ => throw "bad application head during evaluation"

  partial def vFst : Val → EvalM Val
    | .pair a _ => pure a
    | .neu t     => pure (.neu (.fst t))
    | _          => throw "bad fst projection during evaluation"

  partial def vSnd : Val → EvalM Val
    | .pair _ b => pure b
    | .neu t    => pure (.neu (.snd t))
    | _         => throw "bad snd projection during evaluation"

  partial def vNatElim : Val → Val → Val → Val → EvalM Val
    | _, z, _, .zero   => pure z
    | m, z, s, .succ n => do
        let ih ← vNatElim m z s n
        let step ← vApp s n
        vApp step ih
    | m, z, s, .neu n  => pure (.neu (.natElim m z s n))
    | _, _, _, _       => throw "bad Nat eliminand during evaluation"

  partial def vUnitElim : Val → Val → Val → EvalM Val
    | _, t, .triv  => pure t
    | m, t, .neu u => pure (.neu (.unitElim m t u))
    | _, _, _      => throw "bad Unit eliminand during evaluation"

  partial def vEmptyElim : Val → Val → EvalM Val
    | m, .neu e => pure (.neu (.emptyElim m e))
    | _, _      => throw "bad Empty eliminand during evaluation"

  partial def vIdElim : Val → Val → Val → Val → EvalM Val
    | _, r, _, .refl => pure r
    | m, r, y, .neu p => pure (.neu (.idElim m r y p))
    | _, _, _, _      => throw "bad Id eliminand during evaluation"

  partial def cApp : Closure → Val → EvalM Val
    | .mk env body, v => eval (v :: env) body
end

mutual
  partial def quoteNeutral : Lvl → Neutral → EvalM Tm
    | l, .var x =>
        if x < l then
          pure (.var (l - x - 1))
        else
          throw s!"bad level {x} while quoting at level {l}"
    | l, .app t u => do
        let qt ← quoteNeutral l t
        let qu ← quote l u
        pure (.app qt qu)
    | l, .fst t => do
        let qt ← quoteNeutral l t
        pure (.fst qt)
    | l, .snd t => do
        let qt ← quoteNeutral l t
        pure (.snd qt)
    | l, .natElim m z s n => do
        let qm ← quote l m
        let qz ← quote l z
        let qs ← quote l s
        let qn ← quoteNeutral l n
        pure (.natElim qm qz qs qn)
    | l, .unitElim m t u => do
        let qm ← quote l m
        let qt ← quote l t
        let qu ← quoteNeutral l u
        pure (.unitElim qm qt qu)
    | l, .emptyElim m e => do
        let qm ← quote l m
        let qe ← quoteNeutral l e
        pure (.emptyElim qm qe)
    | l, .idElim m r y p => do
        let qm ← quote l m
        let qr ← quote l r
        let qy ← quote l y
        let qp ← quoteNeutral l p
        pure (.idElim qm qr qy qp)

  partial def quote : Lvl → Val → EvalM Tm
    | l, .neu n    => quoteNeutral l n
    | l, .lam c    => do
        let body ← cApp c (.neu (.var l))
        let qb ← quote (l + 1) body
        pure (.lam qb)
    | l, .pi a c   => do
        let qa ← quote l a
        let body ← cApp c (.neu (.var l))
        let qb ← quote (l + 1) body
        pure (.pi qa qb)
    | l, .sig a c  => do
        let qa ← quote l a
        let body ← cApp c (.neu (.var l))
        let qb ← quote (l + 1) body
        pure (.sig qa qb)
    | l, .pair a b => do
        let qa ← quote l a
        let qb ← quote l b
        pure (.pair qa qb)
    | _, .nat      => pure .nat
    | _, .zero     => pure .zero
    | l, .succ t   => do
        let qt ← quote l t
        pure (.succ qt)
    | _, .unit     => pure .unit
    | _, .triv     => pure .triv
    | _, .empty    => pure .empty
    | l, .id a t u => do
        let qa ← quote l a
        let qt ← quote l t
        let qu ← quote l u
        pure (.id qa qt qu)
    | _, .refl     => pure .refl
    | _, .univ i   => pure (.univ i.normalise)
end

private def andThen (lhs : EvalM Bool) (rhs : Unit → EvalM Bool) : EvalM Bool := do
  if (← lhs) then
    rhs ()
  else
    pure false

mutual
  partial def convNeutral : Lvl → Neutral → Neutral → EvalM Bool
    | _, .var x, .var y => pure (x == y)
    | l, .app t u, .app t' u' =>
        andThen (convNeutral l t t') fun _ => conv l u u'
    | l, .fst t, .fst t' =>
        convNeutral l t t'
    | l, .snd t, .snd t' =>
        convNeutral l t t'
    | l, .natElim m z s n, .natElim m' z' s' n' =>
        andThen (conv l m m') fun _ => do
          let sameZ ← conv l z z'
          if sameZ then
            let sameS ← conv l s s'
            if sameS then
              convNeutral l n n'
            else
              pure false
          else
            pure false
    | l, .unitElim m t u, .unitElim m' t' u' =>
        andThen (conv l m m') fun _ => do
          let sameT ← conv l t t'
          if sameT then
            convNeutral l u u'
          else
            pure false
    | l, .emptyElim m e, .emptyElim m' e' =>
        andThen (conv l m m') fun _ => convNeutral l e e'
    | l, .idElim m r y p, .idElim m' r' y' p' =>
        andThen (conv l m m') fun _ => do
          let sameR ← conv l r r'
          if sameR then
            let sameY ← conv l y y'
            if sameY then
              convNeutral l p p'
            else
              pure false
          else
            pure false
    | _, _, _ => pure false

  partial def conv : Lvl → Val → Val → EvalM Bool
    | _, .univ i,     .univ j     => Level.eqv i j
    | l, .pi a c,     .pi a' c'   =>
        andThen (conv l a a') fun _ => do
          let b  ← cApp c (.neu (.var l))
          let b' ← cApp c' (.neu (.var l))
          conv (l + 1) b b'
    | l, .sig a c,    .sig a' c'  =>
        andThen (conv l a a') fun _ => do
          let b  ← cApp c (.neu (.var l))
          let b' ← cApp c' (.neu (.var l))
          conv (l + 1) b b'
    | _, .nat,        .nat        => pure true
    | _, .zero,       .zero       => pure true
    | l, .succ n,     .succ n'    => conv l n n'
    | _, .unit,       .unit       => pure true
    | _, .triv,       .triv       => pure true
    | _, .empty,      .empty      => pure true
    | l, .id a t u,   .id a' t' u' =>
        andThen (conv l a a') fun _ => do
          let sameT ← conv l t t'
          if sameT then
            conv l u u'
          else
            pure false
    | _, .refl,       .refl       => pure true
    | l, .lam c,      .lam c'     => do
        let body  ← cApp c (.neu (.var l))
        let body' ← cApp c' (.neu (.var l))
        conv (l + 1) body body'
    | l, .lam c,      t           => do
        let body  ← cApp c (.neu (.var l))
        let apped ← vApp t (.neu (.var l))
        conv (l + 1) body apped
    | l, t,           .lam c      => do
        let apped ← vApp t (.neu (.var l))
        let body  ← cApp c (.neu (.var l))
        conv (l + 1) apped body
    | l, .pair a b,   .pair a' b' =>
        andThen (conv l a a') fun _ => conv l b b'
    | l, .pair a b,   p           =>
        andThen
          (do
            let fstp ← vFst p
            conv l a fstp)
          fun _ => do
            let sndp ← vSnd p
            conv l b sndp
    | l, p,           .pair a b   =>
        andThen
          (do
            let fstp ← vFst p
            conv l fstp a)
          fun _ => do
            let sndp ← vSnd p
            conv l sndp b
    | l, .neu n,      .neu n'     => convNeutral l n n'
    | _, _,           _           => pure false
end

partial def sub : Lvl → Val → Val → EvalM Bool
  | _, .univ i,     .univ j     => Level.leq i j
  | l, .pi a c,     .pi a' c'   =>
      andThen (sub l a' a) fun _ => do
        let b  ← cApp c (.neu (.var l))
        let b' ← cApp c' (.neu (.var l))
        sub (l + 1) b b'
  | l, .sig a c,    .sig a' c'  =>
      andThen (sub l a a') fun _ => do
        let b  ← cApp c (.neu (.var l))
        let b' ← cApp c' (.neu (.var l))
        sub (l + 1) b b'
  | l, t,           t'          => conv l t t'

end BidirTT
