{-|
Module      : Game.GoreAndAsh.Sync.API
Description : Monadic API of core module
Copyright   : (c) Anton Gushcha, 2015-2016
License     : BSD3
Maintainer  : ncrashed@gmail.com
Stability   : experimental
Portability : POSIX
-}
module Game.GoreAndAsh.Sync.API(
    SyncMonad(..)
  ) where

import Control.Monad.State.Strict
import Control.Wire
import Data.Proxy 
import Data.Serialize (encode, Serialize)
import Data.Text
import Data.Word 
import Prelude hiding (id, (.))
import qualified Data.HashMap.Strict as H 
import qualified Data.Sequence as S 

import Game.GoreAndAsh.Actor
import Game.GoreAndAsh.Actor.TypeRep
import Game.GoreAndAsh.Logging 
import Game.GoreAndAsh.Network
import Game.GoreAndAsh.Sync.Module
import Game.GoreAndAsh.Sync.State

-- | Low level API for module
-- Need at least one network channel to operate. If you open more than one channel,
-- the module would use chanel id 1 as service channel, therefore count of channels
-- on client and server should match (server won't response on channel 1 if it doesn't
-- have it).
class MonadIO m => SyncMonad m where 
  -- | Find actor id by it stable type representation
  getSyncIdM :: HashableTypeRep -> m (Maybe Word64)
  -- | Find actor type representation by it id
  getSyncTypeRepM :: Word64 -> m (Maybe HashableTypeRep)
  -- | Generate and register new id for given actor type representation
  registerSyncIdM :: LoggingMonad m => HashableTypeRep -> m Word64
  -- | Register new type rep with given id, doesn't overide existing records
  addSyncTypeRepM :: LoggingMonad m => HashableTypeRep -> Word64 -> m ()

  -- | Send message as soon as network id of actor is resolved
  syncScheduleMessageM :: (NetworkMonad m, LoggingMonad m, NetworkMessage i, Serialize (NetworkMessageType i))
    => Peer -- ^ Which peer we sending to
    -> ChannelID -- ^ Which channel we are sending within
    -> i -- ^ ID of actor
    -> MessageType -- ^ Strategy of the message (reliable, unordered etc.)
    -> NetworkMessageType i -- ^ Message to send
    -> m ()

  -- | Switch on/off detailed logging of the module
  syncSetLoggingM :: Bool -> m ()

  -- | Setups behavior model in synchronizing of actor ids
  -- Note: clients should be slaves and server master
  syncSetRoleM :: SyncRole -> m ()

  -- | Returns current behavior model in synchronizing of actor ids
  -- Note: clients should be slaves and server master
  syncGetRoleM :: m SyncRole

  -- | Send request for given peer for id of given actor
  syncRequestIdM :: forall proxy i . (ActorMonad m, NetworkMonad m, LoggingMonad m, NetworkMessage i) 
    => Peer -> proxy i -> m ()

instance {-# OVERLAPPING #-} MonadIO m => SyncMonad (SyncT s m) where
  getSyncIdM !tr = do 
    sstate <- SyncT get 
    return . H.lookup tr . syncIdMap $! sstate

  getSyncTypeRepM !w = do 
    sstate <- SyncT get 
    return . H.lookup w . syncIdMapRev $! sstate

  registerSyncIdM !tr = do 
    sstate <- SyncT get
    let (w64, s') = registerSyncIdInternal tr sstate
    syncLog s' $ "Registering new actor type " <> pack (show tr) <> " with id " <> pack (show w64)
    SyncT . put $! s'
    return w64

  addSyncTypeRepM !tr !i = do 
    sstate <- SyncT get 
    syncLog sstate $ "Registering new actor type " <> pack (show tr) <> " with id " <> pack (show i)
    SyncT . put $! addSyncTypeRepInternal tr i sstate

  syncScheduleMessageM peer ch i mt msg = do 
    sstate <- SyncT get 
    let name = getActorName i 
        serviceMsg = Message ReliableMessage $! encode (0 :: Word64, encode $! SyncServiceRequestId name )
        actorId = fromIntegral (toCounter i) :: Word64
        v = (name, ch, \netid -> Message mt $! encode (netid, encode (actorId, encode msg)))
    serviceChan <- getServiceChannel 
    peerSendM peer serviceChan serviceMsg
    SyncT . put $! sstate {
        syncScheduledMessages = case H.lookup peer . syncScheduledMessages $! sstate of 
          Nothing -> H.insert peer (S.singleton v) . syncScheduledMessages $! sstate
          Just msgs -> H.insert peer (msgs S.|> v) . syncScheduledMessages $! sstate
      }

  syncSetLoggingM f = do 
    sstate <- SyncT get 
    SyncT . put $! sstate {
        syncLogging = f
      }

  syncSetRoleM r = do 
    sstate <- SyncT get
    SyncT . put $! sstate {
        syncRole = r 
      }

  syncGetRoleM = syncRole <$> SyncT get 

  syncRequestIdM peer p = do
    s <- SyncT get
    syncLog s $ "request id of actor " <> pack (show $ actorFingerprint p)
    s' <- syncRequestIdInternal peer p s
    SyncT . put $! s'

instance {-# OVERLAPPABLE #-} (MonadIO (mt m), SyncMonad m, ActorMonad m, NetworkMonad m, LoggingMonad m,  MonadTrans mt) => SyncMonad (mt m) where 
  getSyncIdM = lift . getSyncIdM
  getSyncTypeRepM = lift . getSyncTypeRepM
  registerSyncIdM = lift . registerSyncIdM
  addSyncTypeRepM a b = lift $ addSyncTypeRepM a b
  syncScheduleMessageM peer ch i mt msg  = lift $ syncScheduleMessageM peer ch i mt msg
  syncSetLoggingM = lift . syncSetLoggingM
  syncSetRoleM = lift . syncSetRoleM
  syncGetRoleM = lift syncGetRoleM
  syncRequestIdM a b = lift $ syncRequestIdM a b 

-- | Return typename of actor using it type representation
getActorName :: forall i . ActorMessage i => i -> String 
getActorName _ = show $ actorFingerprint (Proxy :: Proxy i)