{-# LANGUAGE ScopedTypeVariables, TypeSynonymInstances, FlexibleInstances, GeneralizedNewtypeDeriving #-}
{- |
   This module provides an interface for the FreeSwitch event socket - <http://wiki.freeswitch.org/wiki/Event_Socket>.

   Developed by David Austin (<mailto:vulpyne+haskelleventsocket@teliax.com>)

   Released to the Open Source Software community by Teliax Inc (<http://www.teliax.com/>)
-}
module Network.EventSocket

    (
-- * Types
-- ** Messages
     EventSocketMessage(..)
-- ** Base types
    ,EventSocketState
    ,EventType(..)
    ,EventSocketError(..)
    ,EventSocketCommand(..)
    ,SessionState(sessUUID,sessVariables)
    ,EvtKVMap
    ,ESUUID
    ,EventSocketT
    ,EventSocketIO(..)
-- ** Classes
-- * Provided functions
-- ** Utility functions
    ,startEventSocket
    ,connectInbound
    ,runEventSocketT
-- ** Settings modification
    ,modifySyncMode
    ,modifyDebugLevel
-- ** Events
    ,registerEventHandler
    ,unregisterEventHandler
    ,EventId
    ,EventHandlerResult(..)
    ,EventHandlerFunc(..)
-- ** Highlevel functions
    ,syncPoint
    ,esRun
    ,esRunActions
    ,getSession
-- ** Midlevel functions
    ,apiAndReply
    ,commandAndReply
    ,withUUID
    ,getUUID
-- ** Low level functions
    ,sendAPI
    ,sendCommand
    ,waitForEvent
    ,eventSocketReadEventsUntil
    ,newEventSocketState
-- * Examples
-- ** Simple example
-- $example
    ) where

import IO (Handle, hClose, hFlush, bracket, hPutStrLn, stderr, hReady)
import Data.Maybe (fromMaybe, fromJust, isJust)
import Data.Char (toLower, isSpace, ord, chr)
import qualified Data.Set as S
import qualified Data.Map as M
import qualified Data.ByteString.Char8 as B
import qualified Control.Exception as Exc (try, finally)

import Control.Arrow ((***))
import Control.Monad.State.Strict (get, gets, put, modify, StateT, MonadState, runStateT)
import Control.Monad.Error (throwError, ErrorT, MonadError, liftM, ap, Error(noMsg), runErrorT)
import Control.Concurrent (forkIO)
import Control.Monad.Trans (MonadIO)
import Control.Applicative (Applicative, pure, (<*>))
import Control.Monad (when, forever, Monad)
import Control.Monad.Trans (lift, liftIO, MonadTrans)


import Network (accept)
import Network.Socket (HostName, PortNumber, Socket, SockAddr(..), SocketOption(..), Family(..), SocketType(..), listen, sClose, inet_addr, setSocketOption, bindSocket, defaultProtocol, socket)


newtype (Monad m, EventSocketIO h) => EventSocketT h m a =
  EventSocketT { unEventSocketT :: ErrorT EventSocketError (StateT (EventSocketState h m) m) a }
  deriving (Monad, MonadIO, MonadState (EventSocketState h m), MonadError EventSocketError)

instance (Monad m, EventSocketIO h) => Functor (EventSocketT h m) where
  fmap = Control.Monad.Error.liftM
  
instance (Monad m, EventSocketIO h) => Applicative (EventSocketT h m) where
  pure = return
  (<*>) = Control.Monad.Error.ap


instance EventSocketIO h => MonadTrans (EventSocketT h) where
  lift = EventSocketT . lift . lift

instance Show (h -> IO B.ByteString) where
  show _ = "((h -> IO ByteString))"
  
instance Show (h -> Int -> IO B.ByteString) where
  show _ = "((h -> Int -> IO ByteString))"

instance Show (h -> B.ByteString -> IO ()) where
  show _ = "((h -> ByteString -> IO ()))"


{- |
   Mainly FreeSwitch events.

   More information for FreeSwitch-specific events: <http://wiki.freeswitch.org/wiki/Event_list>
-}
data EventType
    -- | Sent when the initial negotiation is done.
    = E_Start
    -- | Sent when ending.
    | E_End
    -- | Sent when the Freeswitch event name could not be parsed.
    | E_Other B.ByteString
    -- | FreeSwitch API reply.
    | E_APIResponse
    -- | FreeSwitch command reply.
    | E_CommandReply
    | E_CHANNEL_CREATE
    | E_CHANNEL_DESTROY
    | E_CHANNEL_STATE
    | E_CHANNEL_ANSWER
    | E_CHANNEL_HANGUP
    | E_CHANNEL_HANGUP_COMPLETE
    | E_CHANNEL_EXECUTE
    | E_CHANNEL_EXECUTE_COMPLETE
    | E_CHANNEL_BRIDGE
    | E_CHANNEL_UNBRIDGE
    | E_CHANNEL_PROGRESS
    | E_CHANNEL_PROGRESS_MEDIA
    | E_CHANNEL_OUTGOING
    | E_CHANNEL_PARK
    | E_CHANNEL_UNPARK
    | E_CHANNEL_APPLICATION
    | E_CHANNEL_ORIGINATE
    | E_CHANNEL_UUID
    | E_SHUTDOWN
    | E_MODULE_LOAD
    | E_MODULE_UNLOAD
    | E_RELOADXML
    | E_NOTIFY
    | E_SEND_MESSAGE
    | E_RECV_MESSAGE
    | E_REQUEST_PARMS
    | E_CHANNEL_DATA
    | E_GENERAL
    | E_COMMAND
    | E_SESSION_HEARTBEAT
    | E_CLIENT_DISCONNECTED
    | E_SERVER_DISCONNECTED
    | E_SEND_INFO
    | E_RECV_INFO
    | E_CALL_SECURE
    | E_NAT
    | E_RECORD_START
    | E_RECORD_STOP
    | E_CALL_UPDATE
    | E_API
    | E_BACKGROUND_JOB
    | E_CUSTOM
    | E_RE_SCHEDULE
    | E_HEARTBEAT
    | E_DETECTED_TONE
    | E_ALL
    | E_LOG
    | E_INBOUND_CHAN
    | E_OUTBOUND_CHAN
    | E_STARTUP
    | E_PUBLISH
    | E_UNPUBLISH
    | E_TALK
    | E_NOTALK
    | E_SESSION_CRASH
    | E_DTMF
    | E_MESSAGE
    | E_PRESENCE_IN
    | E_PRESENCE_OUT
    | E_PRESENCE_PROBE
    | E_MESSAGE_WAITING
    | E_MESSAGE_QUERY
    | E_ROSTER
    | E_CODEC
    | E_DETECTED_SPEECH
    | E_PRIVATE_COMMAND
    | E_TRAP
    | E_ADD_SCHEDULE
    | E_DEL_SCHEDULE
    | E_EXE_SCHEDULE
      deriving (Show,Eq,Ord,Read)



{- |
   Event Socket commands. This list is far from complete.

   More information is available: <http://wiki.freeswitch.org/wiki/Command_reference>
-}
data EventSocketCommand
    -- | <http://wiki.freeswitch.org/wiki/Event_Socket#event>
    = CmdEvents { format :: B.ByteString -- ^ Event format, either plain or XML.
                ,  events :: [B.ByteString] -- ^ Event types.
                }
    -- | <http://wiki.freeswitch.org/wiki/Event_Socket#filter>
    | CmdFilter { isDelete :: Bool -- ^ Delete this filter instead of creating.
                , eventHeader :: B.ByteString -- ^ Header to allow.
                , eventValue :: B.ByteString  -- ^ Value to allow.
                }
    -- | Turns on event socket linger, causing the event socket to send all remaining events before closing the connection.
    | CmdLinger
    -- | Sets a channel variable.
    | CmdSet { key :: B.ByteString -- ^ Set key.
             , val :: B.ByteString -- ^ Set value.
             }
    -- | Hangs up the call. (Uses the UUID in the event socket state.)
    | CmdHangup {
        reason :: B.ByteString -- ^ Reason for hangup: <http://wiki.freeswitch.org/wiki/Hangup_causes>
      }
    -- | Sleeps the specified number of milliseconds. <http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_sleep>
    | CmdSleep {
        duration :: Int -- ^ Duration to sleep.
      }
    -- | Answers the call.
    | CmdAnswer
    -- | Plays a sound file and waits for input. <http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_play_and_get_digits>
    | CmdPlayGather {
        minDigits :: Int,
        maxDigits :: Int,
        tries :: Int,
        timeout :: Int,
        terminators :: B.ByteString,
        file :: B.ByteString,
        invalidFile :: B.ByteString,
        varName :: B.ByteString,
        regexp :: B.ByteString
      }
    -- | Bridges a call. <http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_bridgecall>
    | CmdBridge { destinations :: [B.ByteString] }
    -- | Can speak simple output like digits or time. <http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_say>
    | CmdSay { text :: B.ByteString }
    -- | Can speak arbitrary text using a TTS engine. <http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_speak>
    | CmdSpeak { text :: B.ByteString }
    -- | Plays a sound file. <http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_playback>
    | CmdPlay { filename :: B.ByteString }
    -- | Records a sound file. <http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_record>
    | CmdRecord { filename :: B.ByteString, maxLength :: Int }
    -- | Binds a meta application. <http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_bind_meta_app>
    | CmdMetaApp { listenKey, listenTo, respondOn :: Char, appString :: B.ByteString }
    -- | Sync point.
    | CmdSync
    deriving Show


-- | The state of the event socket connection.
data EventSocketIO h => EventSocketState h m
    = EventSocketState {
      -- | Eventsocket should continue running.
        esRunning :: Bool
      -- | Usually a network socket.
      , esHandle :: h
      -- | Hostname for incoming connection.
      , esHostName :: HostName
      -- | Port for incoming connection.
      , esPortNumber :: PortNumber
      -- | Pending events. Used internally.
      , esEvents :: [EventSocketMessage]
      -- | Use \"event-lock: true\" in outgoing commands.
      , esSyncMode :: Bool
      -- | Run event handlers for incoming events.
      , esHandleEvents :: Bool
      -- | Currently used UUID. Set from the initial response to connect, and possibly changed later.
      , esUUID :: B.ByteString
      -- | Sequence number used for generating events or other session-unique identifiers.
      , esSeq :: Int
      -- | Sessions.
      , esSessions :: M.Map B.ByteString (SessionState h m)
      -- | The global session. (Used for global events, etc.)
      , esGlobalSession :: SessionState h m
      -- | Recursion depth, used for detecting nested eventSocketReadEventsUntil calls.
      , esDepth :: Int
      , esDebugLevel :: Int
      } deriving Show

{- |
   Stores a session. Two user accessible fields exist:

   * sessUUID :: 'ESUUID' -- The UUID this session is associated with.

   * sessVariables :: 'EvtKVMap' -- Variables associated with this channel, updated when receiving CHANNEL_DATA events from FreeSwitch.
-}
data EventSocketIO h => SessionState h m
    = SessionState {
        sessUUID :: B.ByteString
      , sessVariables :: EvtKVMap
      , sessEvents :: M.Map EventType (S.Set (EventHandler h m))
      } deriving Show


data EventHandlerResult = EHStopEvents
                        | EHContinue
                        | EHStopReading
                          deriving (Show,Eq)
                                   

type EventId = (Int,B.ByteString,EventType)

data EventSocketIO h => EventHandlerFunc h m = EventHandlerFunc (EventSocketMessage -> EventSocketT h m (Maybe (EventHandlerFunc h m),EventHandlerResult))

unEFunc (EventHandlerFunc f) = f


instance Show (EventHandlerFunc h m) where
  show _ = "((EventSocketCallback))"


instance EventSocketIO h => Ord (EventHandler h m) where
  compare EventHandler { ehPriority = i1 } EventHandler { ehPriority = i2 } = compare i1 i2
  
instance EventSocketIO h => Eq (EventHandler h m) where  
  i1 == i2 = ehID i1 == ehID i2


data EventSocketIO h => EventHandler h m = 
  EventHandler { ehID :: EventId
               , ehPriority :: Int
               , ehHandler :: EventHandlerFunc h m
               } deriving (Show)
  
  
-- | Error type thrown when something goes boom.
data EventSocketError
    = UnknownError
    | NetworkError String
    | ParseError String
    | ProtocolError String
    | GeneralError String
      deriving Show


-- | Basically, an event. Used and returned by some of the lower level Eventsocket routines.
data EventSocketMessage
    = EventSocketMessage {
        -- | Type of the event.
        esmType :: EventType,
        -- | The headers FreeSwitch sent.
        esmHeaders :: EvtKVMap,
        -- | Is this a positive response?
        esmSuccess :: Bool,
        -- | Either a ByteString or a parsed KV map depending on the type of response.
        esmData :: Either B.ByteString EvtKVMap

      }
    deriving (Show,Eq,Ord)


instance Error EventSocketError where
    noMsg = UnknownError

-- | UUIDs are currently just bytestrings.
type ESUUID = B.ByteString

-- | Just a simple map ByteString to ByteString.
type EvtKVMap = M.Map B.ByteString B.ByteString


-- | You may instance this class if you'd like to use a different handle type (or just read/write to the normal Handle in some special way.)
class EventSocketIO h where
  esioBReadLine :: h -> IO B.ByteString -- ^ Read one line from the handle.
  esioBRead :: h -> Int -> IO B.ByteString -- ^ Read a specified amount of bytes from the handle.
  esioBWrite :: h -> B.ByteString -> IO () -- ^ Write to the handle
  esioFlush :: h -> IO () -- ^ Flush the handle.
  esioReady :: h -> IO Bool -- ^ Check if the handle is ready for reading.
  

-- | There exists a default instance for Handle.
instance EventSocketIO Handle where
  esioBReadLine = B.hGetLine
  esioBRead = B.hGet
  esioBWrite = B.hPutStr
  esioFlush = hFlush
  esioReady = hReady

  

-- | Runs the event socket monad transformer.
runEventSocketT :: (MonadIO m, EventSocketIO h) 
                  => EventSocketT h m a -- ^ Monadic action to run.
                  -> EventSocketState h m -- ^ Initial state (possibly from 'newEventSocketState')
                  -> m (Either EventSocketError a, (EventSocketState h m)) -- ^ Returns a tuple with either the final result or error, and the ending state.
runEventSocketT (EventSocketT ma) = runStateT (runErrorT ma)


-- | Basic listen socket creation.
makeListenSocket :: HostName -> PortNumber -> IO Socket
makeListenSocket host port = do
  sock <- socket AF_INET Stream defaultProtocol
  ia <- inet_addr host
  setSocketOption sock ReuseAddr 1
  bindSocket sock (SockAddrInet port ia)
  listen sock 128
  return sock


-- | Basic example accept loop.
acceptLoop :: Socket
           -> (Handle -> HostName -> PortNumber -> IO ())
           -> IO ()
acceptLoop sock f = forever $ do
                      mc <- Exc.try $ accept sock
                      case mc of
                        Left (e :: IOError) -> hPutStrLn stderr $ "[EVENTSOCKET] Error accepting connection: " ++ show e
                        Right (handle, host, port) ->
                            do forkIO $ (f handle host port >> B.hPutStr handle (B.pack "exit\r\n\r\n") >> hClose handle) `Exc.finally` hClose handle
                               return ()



-- | Starts a listen socket for incoming EventSocket connections. You'll generally want to call 'connectInbound' after the handler is triggered.
startEventSocket :: HostName -- ^ Hostname to listen on.
                 -> PortNumber -- ^ Port number to listen on.
                 -> (EventSocketState Handle m -> IO ()) -- ^ Action to run on each incoming connection.
                 -> IO ()
startEventSocket host portid eh = bracket (makeListenSocket host portid) (sClose) (flip acceptLoop f)
      where f handle connhost connport = let st = newEventSocketState handle connhost connport
                                         in eh st


-- | Creates a new EventSocketState. You may use your own handle type and functions for reading and writing data to it.
newEventSocketState :: EventSocketIO h
  => h -- ^ Connection handle.
  -> HostName -- ^ Hostname of incoming connection
  -> PortNumber -- ^ Port of incoming connection.
  -> EventSocketState h m -- ^ New state.
newEventSocketState handle connhost connport = 
  EventSocketState { esRunning = True
                   , esSeq = 0
                   , esHandle = handle
                   , esHostName = connhost
                   , esPortNumber = connport
                   , esEvents = []
                   , esSyncMode = True
                   , esHandleEvents = False
                   , esUUID = B.empty
                   , esSessions = M.empty
                   , esDepth = 0
                   , esGlobalSession = SessionState { sessUUID = B.empty
                                                    , sessEvents = M.empty
                                                    , sessVariables = M.empty 
                                                    }
                   , esDebugLevel = 0
                   }


-- | SyncMode controls whether "Event-Lock: true" is sent with commands.
modifySyncMode :: (Monad m, EventSocketIO h) => (Bool -> Bool) -> EventSocketT h m Bool
modifySyncMode f = modify (\est -> est { esSyncMode = f $ esSyncMode est }) >> get >>= return . esSyncMode


-- | Modifies the debug level. At high values, the event socket library will spit out massive amounts of data to the console.
modifyDebugLevel :: (Monad m, EventSocketIO h) => (Int -> Int) -> EventSocketT h m Int
modifyDebugLevel f = modify (\est -> est { esDebugLevel = f $ esDebugLevel est }) >> get >>= return . esDebugLevel


-- | Given an event ID, it will remove that event if it exists.
unregisterEventHandler :: (Monad m, EventSocketIO h) => EventId -- ^ Event ID to unregister.
                       -> EventSocketT h m ()
unregisterEventHandler eid@(_,uuid,et) = do
  let isglobal = B.null uuid
      deletefromsession sess = sess { sessEvents = M.update delf et $ sessEvents sess }
      delf i = let nset = S.filter ((eid /=) . ehID) i
               in if S.null nset then Nothing else Just nset
  if isglobal
     then modify $ \est -> est { esGlobalSession = deletefromsession $ esGlobalSession est }
     else modify $ \est -> est { esSessions = M.update (Just . deletefromsession) uuid $ esSessions est }


{- |
   Registers an event handler. If the UUID is left blank, it will trigger globally on all events of the appropriate type.

   Some event types (example E_CommandReply, E_APIResponse) do not have a UUID and therefore must be added as an global event if you wish to catch it.

   Since the event may return a new handler function on each invocation, an event handler may maintain private state this way.

   The event handler may return EHContinue to continue processing, EHStopEvents to stop processing events for that message or
   EHStopReading to exit the event reading loop entirely.

   Event handlers are only called from within 'eventSocketReadEventsUntil' (and functions that use this: 'waitForEvent', 'commandAndReply', 'apiAndReply').

   Global events are processed first, then session events.

   You may not recurse into 'eventSocketReadEventUntil' from within an event handler.
-}
registerEventHandler :: (MonadIO m, EventSocketIO h) => B.ByteString -- ^ UUID for event handler, blank for globalr.
                     -> EventType -- ^ Type of event to trigger on.
                     -> Int -- ^ Priority, with lower events processed earlier.
                     -> EventHandlerFunc h m -- ^ Event handler.
                     -> EventSocketT h m EventId -- ^ Returns new Event ID.
                     
registerEventHandler uuid et prio f = do
  st <- get
  let est = st
      putinsess sess eh = sess { sessEvents = M.insertWith insertfunc et (S.singleton eh) $ sessEvents sess }
      insertfunc nv ov = S.insert (head $ S.toList nv) ov
      nid = (esSeq est,uuid,et)
      nh = EventHandler { ehHandler = f
                        , ehID = nid
                        , ehPriority = prio
                        }
      isglobal = B.null uuid
  if isglobal
     then
       let gsess = esGlobalSession est
       in put est { esGlobalSession = putinsess gsess nh, esSeq = esSeq est + 1 }
     else
       let sess = fromMaybe newsess $ M.lookup uuid $ esSessions est
           newsess = SessionState { sessUUID = uuid, sessVariables = M.empty, sessEvents = M.empty }
       in put est { esSessions = M.insert uuid (putinsess sess nh) $ esSessions est, esSeq = esSeq est + 1 }
  return nid



{- |
   Run a monadic action with the specified UUID as the current UUID.
   Once the action completes, the previous UUID is restored iff it is the same as the original UUID.
-}
withUUID :: (Monad m, EventSocketIO h) => B.ByteString -- ^ UUID to use.
         -> (EventSocketT h m a) -- ^ Monadic action to run.
         -> EventSocketT h m a
withUUID uuid f = do
  st <- get
  let olduuid = esUUID st
  retval <- f
  nst <- get
  let newuuid = esUUID nst
  when (newuuid == uuid) $
       put $ nst { esUUID = olduuid }
  return retval


-- | Waits forever for the specified event type.
waitForEvent :: (MonadIO m, EventSocketIO h) => EventType -- ^ Event type to wait for.
             -> EventSocketT h m [EventSocketMessage] -- ^ Returns a list of messages up to and include the the ending event.
waitForEvent evt = do
  dbug 4 $ "[ Reading events until " ++ show evt
  result <- fmap snd $ eventSocketReadEventsUntil $ maybe (return True) (return . (==evt) . esmType)
  dbug 4 $ "] Got events: " ++ show result
  return result


-- | Sends a command and then waits for a response.
commandAndReply :: (MonadIO m, EventSocketIO h) => B.ByteString -- ^ Command name.
                -> B.ByteString -- ^ Command arguments. (Blank if none.)
                -> EventSocketT h m [EventSocketMessage]
commandAndReply cmd args = do
  sendCommand cmd args
  (ok, events) <- eventSocketReadEventsUntil $
                 \arg -> case arg of
                   Nothing -> return True
                   Just msg -> return $ esmType msg == E_CommandReply
  when (not ok) $ 
    throwError . GeneralError $ "Failed to get command reply."
  return events


-- | Sends a command to the event socket.
sendCommand :: (MonadIO m, EventSocketIO h) => B.ByteString -- ^ Command name.
            -> B.ByteString  -- ^ Command arguments.
            -> EventSocketT h m ()
sendCommand cmd args =
    let header uuid = B.concat [B.pack "sendmsg", if B.null uuid then B.empty else B.append (B.singleton ' ') uuid,
                                B.pack "\r\ncall-command: execute\nexecute-app-name: "]
        argstr = B.pack "\nexecute-app-arg: "
        footer = B.pack "\nevent-lock: true\n\n"
        cmdstr locked uuid = B.concat [header uuid, cmd,
                                       if not (B.null args) then B.append argstr args else B.empty,
                                       if locked then footer else B.pack "\r\n\r\n"]
    in do st <- get
          eWrite $ cmdstr (esSyncMode st) (esUUID st)


-- | Sends a raw command (such as an API command) to the event socket and waits for the response.
apiAndReply :: (MonadIO m, EventSocketIO h) => B.ByteString -- ^ API action name.
            -> B.ByteString  -- ^ API action arguments.
            -> EventSocketT h m [EventSocketMessage]
apiAndReply cmd args = do
  sendAPI cmd args
  (ok, events) <- eventSocketReadEventsUntil $
                 \arg -> case arg of
                          Nothing -> return True
                          Just msg -> return $ esmType msg == E_APIResponse
  when (not ok) $ throwError . GeneralError $ "Failed to get API response."
  return events


-- | Sends a raw command to the event socket.
sendAPI :: (MonadIO m, EventSocketIO h) => B.ByteString -- ^ API action name.
        -> B.ByteString -- ^ API action arguments.
        -> EventSocketT h m ()
sendAPI cmd args =
    let cmdstr = B.append (B.intercalate (B.singleton ' ') [cmd,args]) $ B.pack "\r\n\r\n"
    in eWrite cmdstr


-- | Sends the initial connect message and receives a response.
connectInbound :: (MonadIO m, EventSocketIO h) => EventSocketT h m EventSocketMessage
connectInbound = do
  st <- get
  eWrite $ B.pack "connect\r\n\r\n"
  msg <- readEvtKVMap False
  case M.lookup (B.pack "unique-id") msg of
    Nothing -> throwError . ProtocolError $ "Could not find Unique-Id in initial response."
    Just u ->
        let gsess = (esGlobalSession st)
              { sessUUID = u
              , sessVariables = msg
              }
        in modify $ \est -> est { esUUID = u
                                      , esGlobalSession = gsess
                                      , esSessions = M.insertWith' (\_ oi -> oi { sessVariables = msg })  u gsess $ esSessions est
                                      }
  return EventSocketMessage { esmType = E_Start
                            , esmHeaders = msg
                            , esmSuccess = True
                            , esmData = Left B.empty
                            }



-- | Plays a 0 length tone stream to force a CHANNEL_EXECUTE_COMPLETE event as a synchronization point. Will wait forever if events aren't turned on.
syncPoint :: (MonadIO m, EventSocketIO h)
            => EventSocketT h m [EventSocketMessage]
syncPoint = do
  modify $ \st -> st { esSeq = esSeq st + 1 }
  st <- get
  let arg = B.append (B.pack "tone_stream://%(0,0,") (B.pack $ show (esSeq st + 1234567) ++ ")")
  sendCommand (B.pack "playback") arg
  (ok,ms) <- eventSocketReadEventsUntil (f arg)
  when (not ok) $ throwError $ GeneralError $ "eventSocketReadEventsUntil returned failure."
  return ms
    where 
      f arg Nothing = return True
      f arg (Just m) | esmType m /= E_CHANNEL_EXECUTE_COMPLETE = return False
                     | otherwise = let keepreading = isJust $ do
                                         let Right hdrs = esmData m
                                         application <-  M.lookup (B.pack "application") hdrs
                                         applicationdata <- M.lookup (B.pack "application-data") hdrs
                                         applicationresponse <- M.lookup (B.pack "application-response") hdrs
                                         if B.pack "playback" == application &&
                                            arg == applicationdata &&
                                            B.pack "FILE PLAYED" == applicationresponse
                                           then Just True
                                           else Nothing
                                   in return keepreading

{- |
  Reads messages from the event socket until a condition is true.

  The handler is called with Nothing to detect whether messages should be read (for example, check if the socket has data waiting.)

  At that point, the handler returns whether more messages should be read (True for more messages, False to stop reading messages.)
  
  The handler is called on each received message.

  At that the handler returns whether it is done (True for no more messages, False to continue reading messages.)

-}
eventSocketReadEventsUntil :: (MonadIO m, EventSocketIO h) 
                             => (Maybe EventSocketMessage -> EventSocketT h m Bool) -- ^ Condition function.
                             -> EventSocketT h m (Bool, [EventSocketMessage])
eventSocketReadEventsUntil f = do
  st <- get
  when (esDepth st > 1) $ throwError $ GeneralError $ "Not allowed to recurse into EventSocket from event handler."
  ready <- f Nothing
  if ready
     then do
       msg <- readRawMessage >>= buildMessage
       when (esmType msg == E_CHANNEL_DATA) $
            updateSession msg
       evtresult <- runEvents msg
       done <- f $ Just msg
       if done || evtresult == EHStopReading
         then ret True
         else eventSocketReadEventsUntil f
     else ret False
      where
        ret success = do
          rawstate <- get
          put $ rawstate { esEvents = [] }
          return (success, esEvents rawstate)


updateSession :: (MonadIO m, EventSocketIO h) => EventSocketMessage -> EventSocketT h m ()
updateSession msg = do
  do edata <- case esmData msg of
               Left _ -> throwError . ProtocolError $ "CHANNEL_DATA event without key/value data."
               Right d -> return d
     euuid <- case M.lookup (B.pack "unique-id") edata of
               Nothing -> throwError . ProtocolError $ "CHANNEL_DATA event without Unique-Id field in data."
               Just u -> return u
     dbug 5 $ "> Updating session " ++ show euuid ++ ": " ++ show edata
     est <- get
     let sess = (fromMaybe newsess $ M.lookup euuid $ esSessions est) { sessVariables = edata }
         newsess = SessionState { sessUUID = euuid
                                , sessEvents = M.empty
                                , sessVariables = M.empty }
     modify $ \est -> est { esSessions = M.insert euuid sess $ esSessions est
                                , esGlobalSession = if euuid == esUUID est
                                                    then (esGlobalSession est) { sessVariables = edata }
                                                    else esGlobalSession est }

-- | Gets a session if it exists.
getSession :: (MonadIO m, EventSocketIO h)
              => ESUUID -- ^ UUID to look for. If blank, uses the main UUID.
              -> EventSocketT h m (Maybe (SessionState h m)) -- ^ Returns the session wrapped in Maybe.
getSession uuid = do
  est <- get
  let euuid = if B.null uuid then esUUID est else uuid
  return $ M.lookup euuid $ esSessions est


-- | Get the UUID of the main session.
getUUID :: (MonadIO m, EventSocketIO h) => EventSocketT h m ESUUID
getUUID = esUUID `fmap` get


runEvents :: (MonadIO m, EventSocketIO h) => EventSocketMessage -> EventSocketT h m EventHandlerResult
runEvents msg = do
  est <- get
  let globalevents = fromMaybe S.empty $ M.lookup (esmType msg) (sessEvents $ esGlobalSession est)
      sessevents = fromMaybe S.empty $ do
        evtdata <- either (const Nothing) Just $ esmData msg
        uuid <- M.lookup (B.pack "unique-id") evtdata
        sess <- M.lookup uuid $ esSessions est
        M.lookup (esmType msg) $ sessEvents sess
  dbug 4 $ "| session events (" ++ show (esmType msg) ++ ") = " ++ show sessevents
  dbug 4 $ "| global events (" ++ show (esmType msg) ++ ") = " ++ show globalevents
  runEvents' $ S.toAscList globalevents ++ S.toAscList sessevents
      where
        runEvents' [] = return EHContinue
        runEvents' (e:es) =
            do modify $ \st -> st { esDepth = esDepth st + 1 }
               (newfunc,result) <- (unEFunc $ ehHandler e) msg
               modify $ \st -> st { esDepth = esDepth st - 1 }
               updateEvent newfunc e
               if result == EHContinue then runEvents' es else return result


updateEvent :: (Monad m, EventSocketIO h) => Maybe (EventHandlerFunc h m) -> EventHandler h m -> EventSocketT h m ()
updateEvent Nothing _ = return ()
updateEvent (Just newfunc) e = do
  let isglobal = B.null uuid
      eid@(_,uuid,et) = ehID e
      updatefromsession sess = sess { sessEvents = M.update updf et $ sessEvents sess }
      updf i = Just . flip S.map i $ \x -> 
        if ehID x == eid 
        then x { ehHandler = newfunc }
        else x
  if isglobal
     then modify $ \est -> est { esGlobalSession = updatefromsession $ esGlobalSession est }
     else modify $ \est -> est { esSessions = M.update (Just . updatefromsession) uuid $ esSessions est }


buildMessage :: (MonadIO m, EventSocketIO h) => (EvtKVMap, B.ByteString) -> EventSocketT h m EventSocketMessage
buildMessage (kvmap,msgdata) =
    case M.lookup (B.pack "content-type") kvmap of
      Nothing -> throwError . ProtocolError $ "No content-type in message."
      Just ct | B.pack "api/response" == ct ->
                  let (success, dat) =
                          case msgdata of
                            _ | B.null msgdata -> (True, Left msgdata)
                              | '+' == B.head msgdata -> (True, Left msgdata)
                              | '-' == B.head msgdata -> (False, Left msgdata)
                              | otherwise -> (True, Right $ parseKVMap False msgdata)
                  in return EventSocketMessage {
                           esmType = E_APIResponse,
                           esmHeaders = kvmap,
                           esmSuccess = success,
                           esmData = dat
                         }
              | B.pack "command/reply" == ct ->
                  case M.lookup (B.pack "reply-text") kvmap of
                    Nothing -> throwError . ProtocolError $ "No reply-text in command reply."
                    Just rt | B.null rt -> throwError . ProtocolError $ "Empty reply-text in command reply."
                            | '+' == B.head rt -> return True
                            | '-' == B.head rt -> return False
                            | otherwise -> throwError . ProtocolError $ "Could not parse reply-text in command reply."
                  >>= \success -> return EventSocketMessage {
                                                  esmType = E_CommandReply,
                                                  esmHeaders = kvmap,
                                                  esmSuccess = success,
                                                  esmData = Left msgdata
                                                }
              | B.pack "text/event-plain" == ct ->
                  let eventmap = parseKVMap False msgdata
                  in case M.lookup (B.pack "event-name") eventmap of
                       Nothing -> throwError . ProtocolError $ "No event-name in event headers."
                       Just en -> let eventtype = case reads ("E_" ++ B.unpack en) :: [(EventType,String)] of
                                                   [(et,[])] -> et
                                                   _ -> E_Other en
                                 in return EventSocketMessage {
                                                  esmType = eventtype,
                                                  esmHeaders = kvmap,
                                                  esmSuccess = True,
                                                  esmData = Right eventmap
                                                }
              | B.pack "text/disconnect-notice" == ct ->
                  return EventSocketMessage {
                               esmType = E_End,
                               esmHeaders = kvmap,
                               esmSuccess = True,
                               esmData = Left msgdata
                             }
              | otherwise -> throwError . ProtocolError $ "Unrecognized content-type."


readRawMessage :: (MonadIO m, EventSocketIO h) => EventSocketT h m (EvtKVMap, B.ByteString)
readRawMessage = do
  msg <- readEvtKVMap False
  content_length <- case M.lookup (B.pack "content-length") msg of
                     Nothing -> return 0
                     Just amt -> eBSToInt amt
  msgdata <- if content_length > 0 then eRead content_length else return B.empty
  when (content_length /= B.length msgdata) $
       throwError . ProtocolError $ "Content-length mismatch with data received."
  return (msg,msgdata)


readEvtKVMap :: (MonadIO m, EventSocketIO h) => Bool -> EventSocketT h m EvtKVMap
readEvtKVMap raw = do
  h <- gets esHandle
  ls <- readReply' h []
  return . M.fromList $ map linetokv ls
    where readReply' h st = do
              l <- B.dropWhile (=='\r') `fmap` eReadLine
              if B.null l then return st else readReply' h (B.takeWhile (/='\r') l:st)
          linetokv i = B.map toLower *** fromJust . (if raw then Just else urlDecode) . B.dropWhile isSpace . B.drop 1 $ B.break (==':') i


eReadLine :: (MonadIO m, EventSocketIO h) => EventSocketT h m B.ByteString
eReadLine = do
  st <- get
  ml <- liftIO . Exc.try . esioBReadLine $ esHandle st
  case ml of
    Left (e :: IOError) -> throwError . NetworkError $ "Read line IO error: " ++ show e
    Right l -> (dbug 5 $ if B.null l then "<--------------------" else "<-- " ++ show l) >> return l


eRead :: (MonadIO m, EventSocketIO h) => Int -> EventSocketT h m B.ByteString
eRead len = do
  st <- get
  md <- liftIO . Exc.try $ esioBRead (esHandle st) len
  case md of
    Left (e :: IOError) -> throwError . NetworkError $ "Read data IO error: " ++ show e
    Right d -> (dbug 5 $ "<-- " ++ show d) >> return d


eBSToInt :: (MonadIO m, EventSocketIO h) => B.ByteString -> EventSocketT h m Int
eBSToInt s = case B.readInt s of
               Nothing -> throwError . ParseError $ "Cannot convert integer to string."
               Just (i,_) -> return i


eWrite :: (MonadIO m, EventSocketIO h) => B.ByteString -> EventSocketT h m ()
eWrite s = do
  dbug 5 $ "--> " ++ show s
  st <- get
  result <- liftIO . Exc.try $ esioBWrite (esHandle st) s >> esioFlush (esHandle st)
  case result of
    Left (err :: IOError) -> throwError . NetworkError $ "Error writing or flushing: " ++ show err
    Right _ -> return ()


dbug :: (MonadIO m, EventSocketIO h) => Int -> String -> EventSocketT h m ()
dbug lvl msg = do
  est <- get
  when (esDebugLevel est >= lvl) . liftIO . putStrLn $ "[DBG] " ++ msg


parseKVMap :: Bool -> B.ByteString -> EvtKVMap
parseKVMap raw = M.fromList . map (linetokv .fst . B.break (=='\r')) . B.lines
    where
      linetokv i = B.map toLower *** fromJust . (if raw then Just else urlDecode) . B.dropWhile isSpace . B.drop 1 $ B.break (==':') i


urlDecode :: B.ByteString -> Maybe B.ByteString
urlDecode s = maybe Nothing (Just . B.reverse . B.pack) $ urldecode' 0 []
    where
      slen = B.length s
      charat p = B.index s p
      urldecode' pos result | pos == slen = Just result
                            | B.index s pos == '%' = if (pos + 2) >= slen
                                                    then Nothing
                                                    else maybe Nothing (\i -> urldecode' (pos + 3) (i:result)) $
                                                         hdstoint (charat (pos + 1)) (charat (pos + 2))
                            | otherwise = urldecode' (pos + 1) (charat pos:result)
      hdtoint c = let cl = ord $ toLower c
                      c0 = ord '0'
                      c9 = ord '9'
                      ca = ord 'a'
                      cf = ord 'f'
                  in if cl >= c0 && cl <= c9
                     then Just $ cl - c0
                     else if cl >= ca && cl <= cf
                          then Just $ (cl - ca) + 10
                          else Nothing
      hdstoint c1 c2 = do
        mc1 <- hdtoint c1
        mc2 <- hdtoint c2
        return . chr $ (mc1 * 16) + mc2





-- | Runs a sequence of `EventSocketCommand's.
esRunActions :: (MonadIO m, EventSocketIO h)
               => [EventSocketCommand] -- ^ List of commands to perform.
               -> EventSocketT h m [EventSocketMessage] -- ^ Result is all messages returned.
esRunActions acts = concat `fmap` mapM esRun acts


-- | Runs a command of type 'EventSocketCommand'. Behaves roughly the same as 'commandAndReply'.
esRun :: (MonadIO m, EventSocketIO h) => EventSocketCommand -- ^ Command to run.
      -> EventSocketT h m [EventSocketMessage] -- ^ List of messages up to the terminating command reply or API response.

esRun CmdEvents { format = format, events = events } = do
    sendAPI (B.pack "events") $ B.intercalate (B.singleton ' ') [format, B.intercalate (B.singleton ' ') events]
    waitForEvent E_CommandReply

esRun CmdLinger = do
  sendAPI (B.pack "linger") B.empty
  waitForEvent E_CommandReply

esRun CmdFilter { isDelete = isdelete, eventHeader = eventheader, eventValue = eventvalue } = do
  let filtercmd | isdelete = B.unwords [B.pack "delete ", eventheader, eventvalue]
                | otherwise = B.unwords [eventheader, eventvalue]
  commandAndReply (B.pack "filter") filtercmd

esRun (CmdSet k v) = commandAndReply (B.pack "set") $ B.intercalate (B.singleton '=') [k,v]

esRun CmdHangup { reason = v } = sendCommand (B.pack "hangup") v >> waitForEvent E_CHANNEL_HANGUP_COMPLETE

esRun CmdSleep { duration = v } = commandAndReply (B.pack "sleep") . B.pack . show $ v

esRun CmdAnswer = commandAndReply (B.pack "answer") B.empty

esRun c@CmdPlayGather {} =
  let x = B.intercalate (B.singleton ' ') [B.pack . show $ minDigits c,
                                           B.pack . show $ maxDigits c,
                                           B.pack . show $ tries c,
                                           B.pack . show $ timeout c * 1000,
                                           terminators c,
                                           file c,
                                           invalidFile c,
                                           varName c,
                                           regexp c]
  in commandAndReply (B.pack "play_and_get_digits") x

esRun CmdBridge { destinations = ds } = commandAndReply (B.pack "bridge") $ B.intercalate (B.singleton ',') ds

esRun CmdSay { text = text } = commandAndReply (B.pack "say") text

esRun CmdSpeak { text = text } = commandAndReply (B.pack "speak") text

esRun CmdRecord { filename = filename, maxLength = len } =
    commandAndReply (B.pack "record") $ B.intercalate (B.singleton ' ') [filename, B.pack $ show len ]

esRun CmdMetaApp { listenKey = listenkey, listenTo = listento, respondOn = respondon, appString = appstring } =
    commandAndReply (B.pack "bind_meta_app") $ B.unwords [B.singleton listenkey, B.singleton listento, B.singleton respondon,
                                                          appstring]

esRun CmdPlay { filename = fn } = commandAndReply (B.pack "play") fn

esRun CmdSync = syncPoint


{- $example
   This example will accept an incoming Event Socket connection and play a message using the FLITE module.

   It is quite simple and doesn't look at the responses when it sends commands.

   An example of using both the low level and higher level esRun\/esRunActions interface is shown.

>    module TestApp where
>
>    import qualified Data.ByteString.Char8 as B
>    import Control.Monad.State
>    import Control.Monad.Error
>    import Network.EventSocket
>    import IO
>
>    -- Example of using StateT with EventSocketT.
>    type TestMonad a = EventSocketT Handle InnerMonad a
>    type InnerMonad = StateT TestState IO
>
>    data TestState = TestState { meep :: Int } deriving Show
>
>    -- Entry point for when FreeSwitch makes an event socket connection to the program.
>    -- You can also handle your own network connections and use newEventSocketState to create state to use here.
>    testThread :: EventSocketState Handle InnerMonad -> IO ()
>    testThread est = do
>      let inistate = TestState { meep = 1 }
>      result <- runStateT (runEventSocketT testHandler est) inistate
>      putStrLn $ "*** Completion: " ++ show result
>    
>    -- Example of an event handler.
>    testEvent :: (MonadIO m, EventSocketIO h) => EventSocketMessage -> EventSocketT h m (Maybe (EventHandlerFunc h m), EventHandlerResult)
>    testEvent m = do
>      liftIO . putStrLn $ "Got event: " ++ show m
>      return (Just $ EventHandlerFunc testEvent,EHContinue)

>    testEvent :: EventSocketMessage -> TestMonad (Maybe (EventHandlerFunc h m),EventHandlerResult)
>    testEvent m = do
>      liftIO . putStrLn $ "Got event: " ++ show m
>      return (EventHandlerFunc testEvent,EHContinue)
>
>    -- Example of using the low level interface.
>    doCallStuffLowLevel :: TestMonad [EventSocketMessage]
>    doCallStuffLowLevel = do
>      sendAPI (B.pack "linger") B.empty
>      waitForEvent E_CommandReply
>      sendAPI (B.pack "events plain all") B.empty
>      waitForEvent E_CommandReply
>      commandAndReply (B.pack "answer") B.empty
>      commandAndReply (B.pack "set") $ B.pack "tts_engine=flite"
>      commandAndReply (B.pack "set") $ B.pack "tts_voice=kal"
>      commandAndReply (B.pack "speak") $ B.pack "Riveting tale, chap."
>
>    -- Example of using the high level interface.
>    doCallStuffHighLevel :: TestMonad [EventSocketMessage]
>    doCallStuffHighLevel = do
>      esRunActions [ CmdLinger
>                   , CmdEvents { format = B.pack "plain", events = [B.pack "all"] }
>                   , CmdAnswer
>                   , CmdSet { key = B.pack "tts_engine", val = B.pack "flite" }
>                   , CmdSet { key = B.pack "tts_voice", val = B.pack "kal" }
>                   ]
>      esRun CmdSpeak { text = B.pack "Riveting tale, chap." } -- Example of running a single action.
>
>    testHandler :: TestMonad ()
>    testHandler = do
>      st <- lift get -- Must lift to get to our state.
>      registerEventHandler B.empty E_COMMAND 0 $ EventHandlerFunc testEvent -- Global event handler, triggering on all COMMAND events at priority 0.
>      inimsg <- connectInbound
>      when (meep st == 1) . liftIO $ putStrLn "Meep."
>      doCallStuffHighLevel
>      return ()
>
>    main :: IO ()
>    main = do
>      startEventSocket "127.0.0.1" (fromIntegral 8984) testThread
>      return ()


-}