{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# language RankNTypes #-}

module Eve.Internal.States
  ( States
  , HasStates(..)
  , HasEvents
  , stateLens
  , makeStateLens
  ) where

import Control.Lens
import Data.Map
import Unsafe.Coerce
import Data.Maybe
import Data.Typeable
import Data.Default

-- | A wrapper to allow storing types of states in the same place.
data StateWrapper =
  forall s. (Typeable s) =>
              StateWrapper s

-- | A map of state types to their current value.
type States = Map TypeRep StateWrapper

-- | Represents a state which can itself store more states.
-- 'states' is a lens which points to a given state's 'States' map.
class HasStates s  where
  states :: Lens' s States

-- | A typeclass to ensure people don't dispatch events to states which shouldn't
--   accept them.
--
-- To allow dispatching events in an action over your state simply define the
-- empty instance:
--
-- > instance HasEvents MyState where
-- > -- Don't need anything here.
class (Typeable s, HasStates s) =>
      HasEvents s

-- | A polymorphic lens which accesses stored states.
-- It returns the default value ('def') if a state has not yet been set.
stateLens
  :: forall a e.
    (Typeable a, Default a, HasStates e)
  => Lens' e a
stateLens = lens getter setter
  where
    getter s =
      fromMaybe def $ s ^. states . at (typeRep (Proxy :: Proxy a)) . mapping coerce
    setter s new =
      set (states . at (typeRep (Proxy :: Proxy a)) . mapping coerce) (Just new) s
    coerce = iso (\(StateWrapper x) -> unsafeCoerce x) StateWrapper

-- | A utility which creates a state-nested version of a lens.
-- If you pass this function a lens from your state to one of its fields,
-- it will return a lens which can be used within an 'App' or 'Action'.
--
-- The resulting lens will be of type: @newLens :: HasStates s => Lens' s MyState@
-- Or if you prefer, you may wish to specify the state it operates over more specifically
-- to prevent using the lens where it was not originally planned. For instance:
-- @newLens :: Lens' AppState MyState@
--
-- > data SimpleState = SimpleState
-- >   { _myString :: String
-- >   }
-- > makeLenses ''SimpleState
-- >
-- > instance Default SimpleState where
-- >   def = SimpleState "default"
-- >
-- > myStringStateLens :: HasStates s => Lens' s String
-- > myStringStateLens = makeStateLens myString
-- >
-- > myAction :: App ()
-- > myAction = do
-- >   myStringStateLens .= "Hi!"
-- >   str <- use myStringStateLens
-- >   liftIO $ print str
-- > -- "Hi!"
--
-- For more complex Prisms or Traversals you can write your own using
-- 'stateLens'
makeStateLens :: (HasStates s, Typeable myState, Default myState) => Lens' myState a -> Lens' s a
makeStateLens = (stateLens .)