------------------------------------------------------------------------
-- Some properties related to totality
------------------------------------------------------------------------

module Totality where

open import Syntax
open import Semantics.BigStep

open import Data.Nat
open import Data.Empty
open import Data.Product
open import Relation.Nullary

-- Some properties of loop.

loop-⇑ :  {x i}  x ↡[ i ]  loop x ⇑[ i ]
loop-⇑ x↡ ~ Loop (Seqn₂ x↡ (loop-⇑ x↡))

loop-¬⇓ :  {x}  ¬ x ↯[ B ]  ¬ loop x ⇓[ B ]
loop-¬⇓ ¬x↯ (v      , Loop (Seqn₁ _ )) = loop-¬⇓ ¬x↯ (v , )
loop-¬⇓ ¬x↯ (.throw , Loop (Seqn₂ x↯))  = ¬x↯ x↯

loop-¬↡ :  {x i}  ¬ loop x ↡[ i ]
loop-¬↡ (n , Loop (Seqn₁ x↡ y⇓)) = loop-¬↡ (n , y⇓)

-- A utility function.

nat-¬↯ :  {n t}  ¬ ⌜_⌝ {t = t} (nat n) ↯[ B ]
nat-¬↯ ()

-- Partial expressions can sometimes be completely undefined.

¬defined : ∃₂ λ (x : Expr partial) i  ¬ x ⇓[ i ]
¬defined = (loop  nat 0  , B , loop-¬⇓ nat-¬↯)

-- The expressions denoted as total are always potentially
-- terminating...

defined :  (x : Expr total) i  x ⇓[ i ]
defined  v    _ = (_ , Val)
defined (x  y) i with defined x i | defined y i
... | (nat _ , x⇓) | (nat _ , y⇓) = (_ , Add₁ x⇓ y⇓)
... | (nat _ , x⇓) | (throw , y↯) = (_ , Add₃ x⇓ y↯)
... | (throw , x↯) | _            = (_ , Add₂ x↯)
defined (x >> y) i with defined x i | defined y i
... | (nat _ , x⇓) | (nat _ , y⇓) = (_ , Seqn₁ x⇓ y⇓)
... | (nat _ , x⇓) | (throw , y↯) = (_ , Seqn₁ x⇓ y↯)
... | (throw , x↯) | _            = (_ , Seqn₂ x↯)
defined (x catch y) i with defined x i | defined y B
... | (nat _ , x⇓) | _            = (_ , Catch₁ x⇓)
... | (throw , x↯) | (nat _ , y⇓) = (_ , Catch₂ x↯ y⇓)
... | (throw , x↯) | (throw , y↯) = (_ , Catch₂ x↯ y↯)
defined (block   x) _ = map-Σ₂ Block   (defined x B)
defined (unblock x) _ = map-Σ₂ Unblock (defined x U)

-- ...and never non-terminating.

¬undefined :  (x : Expr total) {i}  ¬ x ⇑[ i ]
¬undefined  v        ()
¬undefined (x      y) (Add₁ x⇑)      = ¬undefined x x⇑
¬undefined (x      y) (Add₂ x⇓ y⇑)   = ¬undefined y y⇑
¬undefined (x >>    y) (Seqn₁ x⇑)     = ¬undefined x x⇑
¬undefined (x >>    y) (Seqn₂ x⇓ y⇑)  = ¬undefined y y⇑
¬undefined (x catch y) (Catch₁ x⇑)    = ¬undefined x x⇑
¬undefined (x catch y) (Catch₂ x↯ y⇑) = ¬undefined y y⇑
¬undefined (block   x) (Block   x⇑)   = ¬undefined x x⇑
¬undefined (unblock x) (Unblock x⇑)   = ¬undefined x x⇑

-- Some simple consequences.

cannot-throw⇒can-return :
   {x : Expr total} {i}  ¬ x ↯[ i ]  x ↡[ i ]
cannot-throw⇒can-return {x} {i} ¬x↯ with defined x i
... | (nat n , x↡) = (n , x↡)
... | (throw , x↯) = ⊥-elim (¬x↯ x↯)

cannot-return⇒can-throw :
   {x : Expr total} {i}  ¬ x ↡[ i ]  x ↯[ i ]
cannot-return⇒can-throw {x} {i} ¬x↡ with defined x i
... | (nat n , x↡) = ⊥-elim (¬x↡ (n , x↡))
... | (throw , x↯) = x↯

-- We can be more precise than in "defined" above.

can-throw? :  (x : Expr total) i  Dec (x ↯[ i ])
can-throw? _         U = yes Int
can-throw?  nat m  B = no nat-¬↯
can-throw?  throw  B = yes Val
can-throw? (x  y) B with can-throw? x B | can-throw? y B
... | yes x↯ | _      = yes (Add₂ x↯)
... | no ¬x↯ | yes y↯ = yes (Add₃ (proj₂ (cannot-throw⇒can-return ¬x↯)) y↯)
... | no ¬x↯ | no ¬y↯ = no helper
  where
  helper : ¬ x  y ↯[ B ]
  helper (Add₂ x↯)   = ¬x↯ x↯
  helper (Add₃ _ y↯) = ¬y↯ y↯
can-throw? (x >> y) B with can-throw? x B | can-throw? y B
... | yes x↯ | _      = yes (Seqn₂ x↯)
... | no ¬x↯ | yes y↯ = yes (Seqn₁ (proj₂ (cannot-throw⇒can-return ¬x↯)) y↯)
... | no ¬x↯ | no ¬y↯ = no helper
  where
  helper : ¬ x >> y ↯[ B ]
  helper (Seqn₁ _ y↯) = ¬y↯ y↯
  helper (Seqn₂ x↯)   = ¬x↯ x↯
can-throw? (x catch y) B with can-throw? x B | can-throw? y B
... | yes x↯ | yes y↯ = yes (Catch₂ x↯ y↯)
... | yes x↯ | no ¬y↯ = no helper
  where
  helper : ¬ x catch y ↯[ B ]
  helper (Catch₂ _ y↯) = ¬y↯ y↯
... | no ¬x↯ | _      = no helper
  where
  helper : ¬ x catch y ↯[ B ]
  helper (Catch₂ x↯ _) = ¬x↯ x↯
can-throw? (block x) B with can-throw? x B
... | yes x↯ = yes (Block x↯)
... | no ¬x↯ = no helper
  where
  helper : ¬ block x ↯[ B ]
  helper (Block x↯) = ¬x↯ x↯
can-throw? (unblock x) B with can-throw? x U
... | yes x↯ = yes (Unblock x↯)
... | no ¬x↯ = no helper
  where
  helper : ¬ unblock x ↯[ B ]
  helper (Unblock x↯) = ¬x↯ x↯

can-return? :  (x : Expr total) i  Dec (x ↡[ i ])
can-return?  nat m  i = yes (m , Val)
can-return?  throw  i = no helper
  where
  helper : ¬ ⌜_⌝ {t = total} throw ↡[ i ]
  helper (_ , ())
can-return? (x  y) i with can-return? x i | can-return? y i
... | yes x⇓ | yes y⇓ = yes (zip-Σ _+_ Add₁ x⇓ y⇓)
... | no ¬x⇓ | _      = no helper
  where
  helper : ¬ x  y ↡[ i ]
  helper (._ , Add₁ x⇓ _) = ¬x⇓ (_ , x⇓)
... | _      | no ¬y⇓ = no helper
  where
  helper : ¬ x  y ↡[ i ]
  helper (._ , Add₁ _ y⇓) = ¬y⇓ (_ , y⇓)
can-return? (x >> y) i with can-return? x i | can-return? y i
... | yes x⇓ | yes y⇓ = yes (zip-Σ  m n  n) Seqn₁ x⇓ y⇓)
... | no ¬x⇓ | _      = no helper
  where
  helper : ¬ x >> y ↡[ i ]
  helper (_ , Seqn₁ x⇓ _) = ¬x⇓ (_ , x⇓)
... | _      | no ¬y⇓ = no helper
  where
  helper : ¬ x >> y ↡[ i ]
  helper (_ , Seqn₁ _ y⇓) = ¬y⇓ (_ , y⇓)
can-return? (x catch y) i with can-return? x i | can-return? y B
... | yes x⇓ | _      = yes (map-Σ₂ Catch₁ x⇓)
... | no ¬x⇓ | yes y⇓ = yes (map-Σ₂ (Catch₂ (cannot-return⇒can-throw ¬x⇓)) y⇓)
... | no ¬x⇓ | no ¬y⇓ = no helper
  where
  helper : ¬ x catch y ↡[ i ]
  helper (_ , Catch₁ x⇓)   = ¬x⇓ (_ , x⇓)
  helper (_ , Catch₂ _ y⇓) = ¬y⇓ (_ , y⇓)
can-return? (block x) i with can-return? x B
... | yes x⇓ = yes (map-Σ₂ Block x⇓)
... | no ¬x⇓ = no helper
  where
  helper : ¬ block x ↡[ i ]
  helper (_ , Block x⇓) = ¬x⇓ (_ , x⇓)
can-return? (unblock x) i with can-return? x U
... | yes x⇓ = yes (map-Σ₂ Unblock x⇓)
... | no ¬x⇓ = no helper
  where
  helper : ¬ unblock x ↡[ i ]
  helper (_ , Unblock x⇓) = ¬x⇓ (_ , x⇓)

-- Wraps up the results of some of the functions above.

data _Or_ (P Q : Set) : Set where
  both   : ( p :   P) ( q :   Q)  P Or Q
  first  : ( p :   P) (¬q : ¬ Q)  P Or Q
  second : (¬p : ¬ P) ( q :   Q)  P Or Q

decidable :  (x : Expr total) i  (x ↡[ i ]) Or (x ↯[ i ])
decidable x i with can-return? x i | can-throw? x i
decidable x i | yes x↡ | yes x↯ = both    x↡  x↯
decidable x i | yes x↡ | no ¬x↯ = first   x↡ ¬x↯
decidable x i | no ¬x↡ | yes x↯ = second ¬x↡  x↯
decidable x i | no ¬x↡ | no ¬x↯ with defined x i
decidable x i | no ¬x↡ | no ¬x↯ | (nat n , x↡) = ⊥-elim (¬x↡ (n , x↡))
decidable x i | no ¬x↡ | no ¬x↯ | (throw , x↯) = ⊥-elim (¬x↯ x↯)

-- One could also decide whether or not an expression can yield a
-- _specific_ natural number. However, I think I have already taken
-- this exercise a bit too far.