{-# LANGUAGE RankNTypes, ExistentialQuantification #-}
{-# OPTIONS_HADDOCK prune, not-home #-}
-- | Provides types and encoding/decoding code. Types should be identical to those provided
--   in the Discord API documentation.
module Network.Discord.Types
  ( module Network.Discord.Types
  , module Network.Discord.Types.Prelude
  , module Network.Discord.Types.Channel
  , module Network.Discord.Types.Events
  , module Network.Discord.Types.Gateway
  , module Network.Discord.Types.Guild
  ) where

    import Data.Proxy
    import Control.Monad.State (StateT)

    import Control.Concurrent.STM
    import Network.WebSockets (Connection)
    import System.IO.Unsafe (unsafePerformIO)

    import Network.Discord.Types.Channel
    import Network.Discord.Types.Events
    import Network.Discord.Types.Gateway
    import Network.Discord.Types.Guild
    import Network.Discord.Types.Prelude

    -- | Provides a list of possible states for the client gateway to be in.
    data StateEnum = Create | Start | Running | InvalidReconnect | InvalidDead

    -- | Stores details needed to manage the gateway and bot
    data DiscordState = forall a . (Client a) => DiscordState {
        getState       :: StateEnum         -- ^ Current state of the gateway
      , getClient      :: a                 -- ^ Currently running bot client
      , getWebSocket   :: Connection        -- ^ Stored WebSocket gateway
      , getSequenceNum :: TMVar Integer     -- ^ Heartbeat sequence number
      , getRateLimits  :: TVar [(Int, Int)] -- ^ List of rate-limited endpoints
      }

    -- | Convenience type alias for the monad most used throughout most Discord.hs operations
    type DiscordM = StateT DiscordState IO
    
    -- | The Client typeclass holds the majority of the user-customizable state,
    --   including merging states resulting from async operations.
    class Client c where
      -- | Provides authorization token associated with the client
      getAuth :: c -> Auth
      -- | Function for resolving state differences due to async operations.
      --   Developers are responsible for preventing race conditions.
      --   Remember as `merge newState oldState`
      merge :: c  -- ^ Modified state
            -> c  -- ^ Initial state
            -> c  -- ^ Merged state
      merge _ st = st -- By default, we simply discard the modified state (we don't
                      -- store state by default)

      -- | Control access to state. In cases where state locks aren't needed, this
      --   is most likely the best solution. This implementation most likely
      --   needs an accompanying {-\# NOINLINE getTMClient \#-} pragma to ensure
      --   that a single state is shared between events
      getTMClient :: TVar c
      getTMClient = unsafePerformIO $ newTVarIO undefined
      {-# NOINLINE getTMClient #-}

      -- | In some cases, state locks are needed to prevent race conditions or 
      --   TVars are an unwanted solution. In these cases, both getClient and
      --   modifyClient should be implemented.
      getSTMClient :: Proxy c  -- ^ Type witness for the client
        -> STM c
      getSTMClient _ = readTVar getTMClient
      
      -- | modifyClient is used by mergeClient to merge application states before
      --   and after an event handler is run
      {-# NOINLINE getSTMClient #-}
      modifyClient :: (c -> c) -> STM ()
      modifyClient f = modifyTVar getTMClient f
      
      -- | Merges application states before and after an event handler
      mergeClient :: c -> STM ()
      mergeClient client = modifyClient $ merge client