module Lang.CPS.Monads where

import FP
import MAAM
import Lang.CPS.Semantics
import Lang.CPS.StateSpace
import Lang.CPS.Pretty ()

-- Path Sensitive
type PSΣ' val   ψ = (ID :.: ListSet) :.: (,) (𝒮 val   ψ)

newtype PSΣ val   ψ a = PSΣ { runPSΣ :: ListSet (a, 𝒮 val   ψ) }
  deriving (PartialOrder, JoinLattice, Pretty)
instance Morphism2 (PSΣ val   ψ) (PSΣ' val   ψ)  where
  morph2 = Compose . Compose . ID . map swap . runPSΣ
instance Morphism2 (PSΣ' val   ψ) (PSΣ val   ψ) where
  morph2 = PSΣ . map swap . runID . runCompose . runCompose
instance Isomorphism2 (PSΣ val   ψ) (PSΣ' val   ψ) where
instance (TimeC , TimeC ) => Inject (PSΣ val   Ψ) where
  inj = PSΣ . inj . (,initial)

newtype PSΣ𝒫 val   ψ a = PSΣ𝒫 { runPSΣ𝒫 :: Set (a, 𝒮 val   ψ) }
  deriving (PartialOrder, JoinLattice, Pretty)
instance (Ord (val   ψ), Ord ( ψ), Ord ( ψ), Ord a) => Morphism (PSΣ val   ψ a) (PSΣ𝒫 val   ψ a) where
  morph (PSΣ a𝓈s) = PSΣ𝒫 $ toSet $ toList a𝓈s
instance (Ord (val   ψ), Ord ( ψ), Ord ( ψ), Ord a) => Morphism (PSΣ𝒫 val   ψ a) (PSΣ val   ψ a) where
  morph (PSΣ𝒫 a𝓈s) = PSΣ $ fromList $ toList a𝓈s
instance (Ord (val   ψ), Ord ( ψ), Ord ( ψ), Ord a) => Isomorphism (PSΣ val   ψ a) (PSΣ𝒫 val   ψ a)

newtype PS val   ψ a = FSPS 
  { runPS :: IsoMonadStep (PSΣ val   ψ) (PSΣ' val   ψ) 
                 (StateT (𝒮 val   ψ) (ListSetT ID)) a 
  } deriving 
    ( Unit, Functor, Product, Applicative, Bind, Monad
    , MonadZero, MonadPlus
    , MonadStateE (𝒮 val   ψ), MonadStateI (𝒮 val   ψ), MonadState (𝒮 val   ψ)
    , MonadStep (PSΣ val   ψ)
    )
instance (TimeC , TimeC , ValC   val) => Analysis val   (PS val   Ψ)
instance (TimeC , TimeC , ValC   val) => Execution (PSΣ val   Ψ) (PSΣ𝒫 val   Ψ) (PS val   Ψ)

-- Path Insensitive
data PI𝒮   ψ = PI𝒮
  { pilτ ::  ψ
  , pidτ ::  ψ
  , piρ :: Env   ψ
  } deriving (Eq, Ord)
makePrettyUnion ''PI𝒮
instance (Initial ( ψ), Initial ( ψ)) => Initial (PI𝒮   ψ) where
  initial = PI𝒮 initial initial initial
instance Morphism (𝒮 val   ψ) (PI𝒮   ψ, Store val   ψ) where
  morph (𝒮   ρ σ) = (PI𝒮   ρ, σ)
instance Morphism (PI𝒮   ψ, Store val   ψ) (𝒮 val   ψ) where
  morph (PI𝒮   ρ, σ) = 𝒮   ρ σ
instance Isomorphism (𝒮 val   ψ) (PI𝒮   ψ, Store val   ψ)

-- Flow Sensitive Path Insensitive
type FSΣ' val   ψ = (ListSet :.: ID :.: (,) (Store val   ψ)) :.: (,) (PI𝒮   ψ)

newtype FSΣ val   ψ a = FSΣ { runFSΣ :: ListSet (a, 𝒮 val   ψ) }
  deriving (PartialOrder, JoinLattice, Pretty)
instance Morphism2 (FSΣ val   ψ) (FSΣ' val   ψ) where
  morph2 = Compose . Compose . map (Compose . ID . (\ (a, 𝒮   ρ σ) -> (σ, (PI𝒮   ρ, a)))) . runFSΣ
instance Morphism2 (FSΣ' val   ψ) (FSΣ val   ψ) where
  morph2 = FSΣ . map ((\ (σ, (PI𝒮   ρ, a)) -> (a, 𝒮   ρ σ)) . runID . runCompose) . runCompose . runCompose
instance Isomorphism2 (FSΣ val   ψ) (FSΣ' val   ψ) where
instance (TimeC , TimeC ) => Inject (FSΣ val   Ψ) where
  inj = FSΣ . inj . (,initial)

newtype FSΣ𝒫 val   ψ a = FSΣ𝒫 { runFSΣ𝒫 :: Set (a, 𝒮 val   ψ) }
  deriving (PartialOrder, JoinLattice, Pretty)
instance (Ord (val   ψ), Ord ( ψ), Ord ( ψ), Ord a) => Morphism (FSΣ val   ψ a) (FSΣ𝒫 val   ψ a) where
  morph (FSΣ a𝓈s) = FSΣ𝒫 $ toSet $ toList a𝓈s
instance (Ord (val   ψ), Ord ( ψ), Ord ( ψ), Ord a) => Morphism (FSΣ𝒫 val   ψ a) (FSΣ val   ψ a) where
  morph (FSΣ𝒫 a𝓈s) = FSΣ $ fromList $ toList a𝓈s
instance (Ord (val   ψ), Ord ( ψ), Ord ( ψ), Ord a) => Isomorphism (FSΣ val   ψ a) (FSΣ𝒫 val   ψ a)

newtype FS val   ψ a = FS 
  { runFS :: IsoMonadStep (FSΣ val   ψ) (FSΣ' val   ψ)
                 (AddStateT (𝒮 val   ψ) (PI𝒮   ψ) (ListSetT (StateT (Store val   ψ) ID))) a 
  } deriving 
    ( Unit, Functor, Product, Applicative, Bind, Monad
    , MonadZero, MonadPlus
    , MonadStateE (𝒮 val   ψ), MonadStateI (𝒮 val   ψ), MonadState (𝒮 val   ψ)
    , MonadStep (FSΣ val   ψ)
    )
instance (TimeC , TimeC , ValC   val) => Analysis val   (FS val   Ψ)
instance (TimeC , TimeC , ValC   val) => Execution (FSΣ val   Ψ) (FSΣ𝒫 val   Ψ) (FS val   Ψ)

-- Flow Insensitive Path Insensitive
type FIΣ' val   ψ = ((ID :.: (,) (Store val   ψ)) :.: ListSet) :.: (,) (PI𝒮   ψ)

newtype FIΣ val   ψ a = FIΣ { runFIΣ :: (ListSet (a, PI𝒮   ψ), Store val   ψ) }
  deriving (PartialOrder, JoinLattice, Pretty)
instance Morphism2 (FIΣ val   ψ) (FIΣ' val   ψ) where
  morph2 = Compose . Compose . Compose . ID . mapSnd (map swap) . swap . runFIΣ
instance Morphism2 (FIΣ' val   ψ) (FIΣ val   ψ) where
  morph2 = FIΣ . swap . mapSnd (map swap) . runID . runCompose . runCompose . runCompose
instance Isomorphism2 (FIΣ val   ψ) (FIΣ' val   ψ) where

newtype FIΣ𝒫 val   ψ a = FIΣ𝒫 { runFIΣ𝒫 :: (Set (a, PI𝒮   ψ), Store val   ψ) }
  deriving (PartialOrder, JoinLattice, Pretty)
instance (Ord (val   ψ), Ord ( ψ), Ord ( ψ), Ord a) => Morphism (FIΣ val   ψ a) (FIΣ𝒫 val   ψ a) where
  morph (FIΣ (a𝓈s, σ)) = FIΣ𝒫 (toSet $ toList a𝓈s, σ)
instance (Ord (val   ψ), Ord ( ψ), Ord ( ψ), Ord a) => Morphism (FIΣ𝒫 val   ψ a) (FIΣ val   ψ a) where
  morph (FIΣ𝒫 (a𝓈s, σ)) = FIΣ (fromList $ toList a𝓈s, σ)
instance (Ord (val   ψ), Ord ( ψ), Ord ( ψ), Ord a) => Isomorphism (FIΣ val   ψ a) (FIΣ𝒫 val   ψ a)

instance (TimeC , TimeC ) => Inject (FIΣ val   Ψ) where
  inj = FIΣ . (,initial) . inj . (,initial)

newtype FI val   ψ a = FIPI 
  { runFI :: IsoMonadStep (FIΣ val   ψ) (FIΣ' val   ψ)
                 (AddStateT (𝒮 val   ψ) (PI𝒮   ψ) (ListSetT (StateT (Store val   ψ) ID))) a 
  } deriving 
    ( Unit, Functor, Product, Applicative, Bind, Monad
    , MonadZero, MonadPlus
    , MonadStateE (𝒮 val   ψ), MonadStateI (𝒮 val   ψ), MonadState (𝒮 val   ψ)
    , MonadStep (FIΣ val   ψ)
    )
instance (TimeC , TimeC , ValC   val) => Analysis val   (FI val   Ψ)
instance (TimeC , TimeC , ValC   val) => Execution (FIΣ val   Ψ) (FIΣ𝒫 val   Ψ) (FI val   Ψ)