module Lang.LamIf.Val where

import FP
import Lang.LamIf.StateSpace
import Lang.LamIf.Syntax

-- Concrete
data CVal lτ dτ = LitC Lit | CloC (Clo lτ dτ) | TupC (PicoVal lτ dτ, PicoVal lτ dτ) | BotC
  deriving (Eq, Ord)
makePrisms ''CVal

instance (Eq lτ, Eq dτ) => PartialOrder (CVal lτ dτ) where
  pcompare BotC _ = PLT
  pcompare _ BotC = PGT
  pcompare v1 v2 | v1 == v2  = PEQ
                 | otherwise = PUN

instance (Ord lτ, Ord dτ) => Val lτ dτ (CVal lτ dτ) where
  lit :: Lit -> CVal lτ dτ
  lit = LitC
  clo :: Clo lτ dτ -> CVal lτ dτ
  clo = CloC
  binop :: BinOp -> CVal lτ dτ -> CVal lτ dτ -> CVal lτ dτ
  binop Add (LitC (I n1)) (LitC (I n2)) = LitC $ I $ n1 + n2
  binop Sub (LitC (I n1)) (LitC (I n2)) = LitC $ I $ n1 - n2
  binop GTE (LitC (I n1)) (LitC (I n2)) = LitC $ B $ n1 >= n2
  binop _ _ _ = BotC
  tup :: (PicoVal lτ dτ, PicoVal lτ dτ) -> CVal lτ dτ
  tup = TupC
  elimBool :: CVal lτ dτ -> Set Bool
  elimBool (LitC (B b)) = single b
  elimBool _ = empty
  elimClo :: CVal lτ dτ -> Set (Clo lτ dτ)
  elimClo (CloC c) = single c
  elimClo _ = empty
  elimTup :: CVal lτ dτ -> Set (PicoVal lτ dτ, PicoVal lτ dτ)
  elimTup (TupC t) = single t
  elimTup _ = empty

-- Abstract
data AVal lτ dτ = LitA Lit | IA | BA | CloA (Clo lτ dτ) | TupA (PicoVal lτ dτ, PicoVal lτ dτ) | BotA
  deriving (Eq, Ord)
makePrisms ''AVal

instance (Eq lτ, Eq dτ) => PartialOrder (AVal lτ dτ) where
  pcompare BotA _ = PLT
  pcompare _ BotA = PGT
  pcompare (LitA (I _)) IA = PLT
  pcompare IA (LitA (I _)) = PGT
  pcompare (LitA (B _)) BA = PLT
  pcompare BA (LitA (B _)) = PGT
  pcompare v1 v2 | v1 == v2 = PEQ
                 | otherwise = PUN

instance (Ord lτ, Ord dτ) =>  Val lτ dτ (AVal lτ dτ) where
  lit :: Lit -> AVal lτ dτ
  lit = LitA
  clo :: Clo lτ dτ -> AVal lτ dτ
  clo = CloA
  binop :: BinOp -> AVal lτ dτ -> AVal lτ dτ -> AVal lτ dτ
  binop Add v1 v2 | v1 ⊑ IA && v2 ⊑ IA = IA
  binop Sub v1 v2 | v1 ⊑ IA && v2 ⊑ IA = IA
  binop GTE v1 v2 | v1 ⊑ IA && v2 ⊑ IA = BA
  binop _ _ _ = BotA
  tup :: (PicoVal lτ dτ, PicoVal lτ dτ) -> AVal lτ dτ
  tup = TupA
  elimBool :: AVal lτ dτ -> Set Bool
  elimBool (LitA (B b)) = single b
  elimBool BA = fromList [True, False]
  elimBool _ = empty
  elimClo :: AVal lτ dτ -> Set (Clo lτ dτ)
  elimClo (CloA c) = single c
  elimClo _ = empty
  elimTup :: AVal lτ dτ -> Set (PicoVal lτ dτ, PicoVal lτ dτ)
  elimTup (TupA t) = single t
  elimTup _ = empty

-- Lifting to Powerset
newtype Power val lτ dτ = Power { runPower :: Set (val lτ dτ) }
  deriving 
    ( Eq, Ord, PartialOrder, Bot, Join, JoinLattice, Difference
    , Iterable (val lτ dτ), Container (val lτ dτ), Buildable (val lτ dτ)
    )

instance (Ord lτ, Ord dτ) => Val lτ dτ (Power CVal lτ dτ) where
  lit = Power . single . lit
  clo = Power . single . clo
  binop o vP1 vP2 = Power $ do
    v1 <- runPower vP1
    v2 <- runPower vP2
    single $ binop o v1 v2
  tup = Power . single . tup
  elimBool = extend elimBool . runPower
  elimClo = extend elimClo . runPower
  elimTup = extend elimTup . runPower

instance (Ord lτ, Ord dτ) => Val lτ dτ (Power AVal lτ dτ) where
  lit = Power . single . lit
  clo = Power . single . clo
  binop o vP1 vP2 = Power $ do
    v1 <- runPower vP1
    v2 <- runPower vP2
    single $ binop o v1 v2
  tup = Power . single . tup
  elimBool = extend elimBool . runPower
  elimClo = extend elimClo . runPower
  elimTup = extend elimTup . runPower
