{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleInstances #-}
module UniqueLogic.ST.TF.System (
   -- * Preparation
   Variable,
   globalVariable,
   -- * Handle duplicates
   C, update,
   simpleUpdate, -- should be private in future
   updateIfNew, -- should be private or with special type
   updateAndCheck,
   Fragile(break),
   Value, ValueConstraint, valueConstraint,
   -- * Posing statements
   T,
   localVariable,
   constant,
   assignment2,
   assignment3,
   Apply, arg, runApply, runApplyMaybe,
   -- * Solution
   solve, solveDepthFirst, solveBreadthFirst,
   query,
   ) where

import qualified Control.Monad.Trans.Except as ME
import qualified Control.Monad.Trans.Writer as MW
import qualified Control.Monad.Trans.Class  as MT
import qualified UniqueLogic.ST.TF.MonadTrans as UMT
import qualified Data.Sequence as Seq
import qualified Data.Foldable as Fold
import qualified Data.Ref as Ref
import Control.Monad.Trans.Writer (WriterT, )
import Control.Monad.Trans.Maybe (MaybeT(MaybeT), runMaybeT, mapMaybeT, )
import Control.Monad.Trans.Identity (IdentityT, )
import Control.Monad (when, liftM, liftM2, ap, guard, join, )
import Control.Applicative (Applicative, liftA2, pure, (<*>), )
import Data.Sequence (Seq, (|>), ViewL((:<)), )
import Data.Functor.Compose (Compose(Compose))

import Data.Maybe (isNothing, )
import Data.Monoid (Monoid, mempty, mappend, mconcat, )
import Data.Semigroup (Semigroup, (<>), )

import Prelude hiding (break)


data Variable w s a =
   Variable {
      varUpdate :: MaybeT s a -> Update w s,
      dependsRef :: Ref.T s (Updates w s),
      valueRef :: Ref.T s (Maybe a)
   }

type Update w s = UMT.Wrap w s (Updates w s)
newtype Updates w s = Updates {unpackUpdates :: Seq (Update w s)}

instance Semigroup (Updates w s) where
   Updates x <> Updates y = Updates $ x <> y

instance Monoid (Updates w s) where
   mempty = Updates Seq.empty
   mappend (Updates x) (Updates y) = Updates $ mappend x y

addUpdate :: Update w s -> Updates w s -> Updates w s
addUpdate x (Updates xs) = Updates $ xs |> x


type Updater w s a =
        Ref.T s (Updates w s) -> Ref.T s (Maybe a) ->
        MaybeT (UMT.Wrap w s) a -> Update w s

type SimpleUpdater w s a =
        Ref.T s (Updates w s) -> Ref.T s (Maybe a) ->
        MaybeT s a -> Update w s

newtype T w s a =
   Cons {run :: WriterT [Ref.T s (Updates w s)] s a}

instance (Ref.C s) => Functor (T w s) where
   fmap f (Cons x) = Cons (liftM f x)

instance (Ref.C s) => Applicative (T w s) where
   pure = Cons . return
   (<*>) = ap

instance (Ref.C s) => Monad (T w s) where
   return = Cons . return
   Cons x >>= k  = Cons $ run . k =<< x


lift :: (Monad s) => s a -> T w s a
lift = Cons . MT.lift

globalVariable ::
   (UMT.C w, Value w a, Ref.C s) =>
   SimpleUpdater w s a -> s (Variable w s a)
globalVariable triggerUpdate = object triggerUpdate Nothing

localVariable :: (C w, Value w a, Ref.C s) => T w s (Variable w s a)
localVariable = lift $ globalVariable simpleUpdate

constant ::
   (C w, Value w a, Ref.C s) =>
   a -> T w s (Variable w s a)
constant a =
   do v <- lift $ object simpleUpdate $ Just a
      Cons $ MW.tell [dependsRef v]
      return v

object ::
   (Ref.C s) =>
   SimpleUpdater w s a ->
   Maybe a -> s (Variable w s a)
object updater ma = do
   al <- Ref.new mempty
   av <- Ref.new ma
   return $ Variable (updater al av) al av


solve, solveDepthFirst, solveBreadthFirst ::
   (UMT.C w, Ref.C s) =>
   T w s a -> w s a
solve = solveDepthFirst

data Order = DepthFirst | BreadthFirst
   deriving (Eq, Enum)

solveDepthFirst   = solveOrder DepthFirst
solveBreadthFirst = solveOrder BreadthFirst

solveOrder ::
   (UMT.C w, Ref.C s) =>
   Order -> T w s a -> w s a
solveOrder order (Cons m) = UMT.unwrap $ do
   let resolve updates =
          case Seq.viewl updates of
             Seq.EmptyL -> return ()
             currentUpdate :< remUpdates -> do
                Updates newUpdates <- currentUpdate
                resolve $
                   case order of
                      DepthFirst -> mappend newUpdates remUpdates
                      BreadthFirst -> mappend remUpdates newUpdates

   (a, w) <- UMT.lift $ MW.runWriterT m
   resolve . unpackUpdates . mconcat =<< mapM (UMT.lift . Ref.read) w
   return a

query :: Variable w s a -> s (Maybe a)
query = Ref.read . valueRef


updateIfNew :: (C w, Ref.C s) => Updater w s a
updateIfNew al av act = do
   as <- UMT.lift $ Ref.read av
   fmap Fold.fold $ runMaybeT $ do
      guard $ isNothing as
      MT.lift . UMT.lift . Ref.write av . Just =<< act
      MT.lift $ UMT.lift $ Ref.read al


class Inconsistency e where
   inconsistency :: e

instance Inconsistency e => Fragile (ME.ExceptT e) where
   break = UMT.wrap $ ME.throwE inconsistency

class C t => Fragile t where
   break :: Monad m => UMT.Wrap t m a

updateAndCheck ::
   (UMT.C w, Ref.C s) =>
   (a -> a -> UMT.Wrap w s ()) ->
   Updater w s a
updateAndCheck customBreak al av act = do
   maold <- UMT.lift $ Ref.read av
   manew <- runMaybeT act
   case manew of
      Nothing -> return mempty
      Just anew -> do
         UMT.lift . Ref.write av . Just $ anew
         case maold of
            Just aold -> customBreak aold anew >> return mempty
            Nothing -> UMT.lift $ Ref.read al


class C w => Value w a where
   data ValueConstraint w a :: *
   valueConstraint ::
      Ref.T s (Updates w s) -> Ref.T s (Maybe a) -> ValueConstraint w a

class UMT.C w => C w where
   update :: (Value w a, Ref.C s) => Updater w s a

instance Value IdentityT a where
   data ValueConstraint IdentityT a = IdentityConstraint
   valueConstraint _ _ = IdentityConstraint

instance C IdentityT where
   update = updateIfNew

instance (Monoid w) => Value (MW.WriterT w) a where
   data ValueConstraint (MW.WriterT w) a = WriterConstraint
   valueConstraint _ _ = WriterConstraint

instance (Monoid w) => C (MW.WriterT w) where
   update = updateIfNew

instance (Inconsistency e, Eq a) => Value (ME.ExceptT e) a where
   data ValueConstraint (ME.ExceptT e) a =
           Eq a => ExceptionConstraint
   valueConstraint _ _ = ExceptionConstraint

instance (Inconsistency e) => C (ME.ExceptT e) where
   update al av act =
      case valueConstraint al av of
         ExceptionConstraint ->
            updateAndCheck (\aold anew -> when (aold /= anew) break) al av act

simpleUpdate :: (C w, Value w a, Ref.C s) => SimpleUpdater w s a
simpleUpdate al av = update al av . mapMaybeT UMT.lift


readSTRefM :: Ref.T s (Maybe a) -> MaybeT s a
readSTRefM = MaybeT . Ref.read


assignment2 ::
   (UMT.C w, Ref.C s) =>
   (a -> b) ->
   Variable w s a -> Variable w s b ->
   T w s ()
assignment2 f (Variable _ al av) b =
   let triggerUpdate =
          varUpdate b $ liftM f $ readSTRefM av
   in  lift $
       Ref.modify al (addUpdate triggerUpdate)

assignment3 ::
   (UMT.C w, Ref.C s) =>
   (a -> b -> c) ->
   Variable w s a -> Variable w s b -> Variable w s c ->
   T w s ()
assignment3 f (Variable _ al av) (Variable _ bl bv) c =
   let triggerUpdate =
          varUpdate c $
          liftM2 f (readSTRefM av) (readSTRefM bv)
   in  lift $
       Ref.modify al (addUpdate triggerUpdate) >>
       Ref.modify bl (addUpdate triggerUpdate)


newtype Apply w s a =
   Apply (Compose (MW.Writer [Ref.T s (Updates w s)]) (MaybeT s) a)


{- |
This function allows to generalize 'assignment2' and 'assignment3' to more arguments.
You could achieve the same with nested applications of @assignment3 (,)@.
-}
arg :: Variable w s a -> Apply w s a
arg (Variable _update al av) =
   Apply $ Compose $ MW.writer (readSTRefM av, [al])

{-
ToDo: reactivate when AMP has settled

instance (Ref.C s) => Functor (Apply w s) where
   fmap f (Apply a) = Apply $ fmap f a

instance (Ref.C s) => Applicative (Apply w s) where
   pure a = Apply $ pure a
   Apply f <*> Apply a = Apply $ f <*> a
-}

instance (Ref.C s) => Functor (Apply w s) where
   fmap f (Apply (Compose a)) = Apply $ Compose $ fmap (liftM f) a

instance (Ref.C s) => Applicative (Apply w s) where
   pure a = Apply $ Compose $ pure $ return a
   Apply (Compose f) <*> Apply (Compose a) = Apply $ Compose $ liftA2 ap f a


runApply ::
   (UMT.C w, Ref.C s) =>
   Apply w s a -> Variable w s a -> T w s ()
runApply (Apply (Compose w)) a =
   uncurry (runUpdate a) $ MW.runWriter w

runApplyMaybe ::
   (UMT.C w, Ref.C s) =>
   Apply w s (Maybe a) -> Variable w s a -> T w s ()
runApplyMaybe (Apply (Compose w)) a =
   case MW.runWriter w of
      (mf, refs) ->
         runUpdate a (MaybeT $ liftM join $ runMaybeT mf) refs

runUpdate ::
   (Ref.C s) =>
   Variable w s a -> MaybeT s a ->
   [Ref.T s (Updates w s)] -> T w s ()
runUpdate a f refs =
   lift $ Fold.forM_ refs $ flip Ref.modify (addUpdate $ varUpdate a f)