{-|
Module      : EventLoop.EventProcessor
Description : Low-level eventloop framework. Uses 'IOMessage's to communicate.
Copyright   : (c) Sebastiaan la Fleur, 2014
License     : BSD3
Maintainer  : sebastiaan.la.fleur@gmail.com
Stability   : experimental
Portability : All

Low-level eventloop framework. Uses 'IOMessage's to communicate.
This module can be used to express your own 'EventLoop.Input.InputEvent's and 'EventLoop.Output.OutputEvent's.
For instance, we have a keyboard and mouse 'EventLoop.Input.InputEvent' example included in this module\'s implementation.
But it might be that you want to define your own 'EventLoop.Input.InputEvent' structure. In that case, you can define your own
'FromJSON' class to express how the literal 'IOMessage' should be parsed into your own 'EventLoop.Input.InputEvent' datatype.
The same goes for 'EventLoop.Output.OutputEvent' of course. An 'IOMessage' is a 'String' literal that the client sends. The accompanied
standard webpage uses JSON as a protocol to express these 'String' literals. 
-}
module EventLoop.EventProcessor(eventloop, IOMessage, readRequest, sendResponse) where

import qualified Network.WebSockets as WS
import qualified Data.Text as T
import Data.String (String)
import Data.Char (isLower, isDigit)
import Control.Monad (sequence)

import EventLoop.Json
import EventLoop.Config

-- | Type of the message that is used to communicate with the IO device. In this example implementation a webbrowser is used and the messages use JSON encoding.
type IOMessage = JSONMessage

{- |
 Low-level function to call when dealing with IOMessages directly. 
 If you want to use the example implementation using the 'EventLoop.Input.InputEvent' and 'EventLoop.Output.OutputEvent'
 , you should use the 'EventLoop.Main.start' function.
-}
eventloop :: (a -> IOMessage -> ([IOMessage], a)) -- ^ The eventhandler eh that maps incoming 'IOMessage's to outgoing 'IOMessage's. It uses a variable of type a to store information in between calls to the event handler.
          -> a                                    -- ^ The begin store. It should be changed in the eventhandler function.
          -> IO ()
eventloop eh beginState = WS.server ipadres port $ doEvents eh beginState 

{- |
 This function maps all incoming messages over the eventhandler and sends off the resulting output messages.
 Uses the connection to communicate with the IO device. Uses the Standard Output Mutex to print information
 to stdout in a safe multi-threaded way as each connection starts a thread that uses the 'doEvents' 
 function.
-}
doEvents :: (a -> IOMessage -> ([IOMessage], a)) -- ^ The eventhandler eh that maps incoming 'IOMessage's to outgoing 'IOMessage's. It uses a variable of type a to store information in between calls to the event handler.
         -> a                                    -- ^ The begin store. It should be changed in the eventhandler function.
         -> WS.Connection                        -- ^ Connection to the IO device.
         -> WS.StdOutMutex                       -- ^ Standard Output Mutex for printing information in a safe multi-threaded way to the standard out.
         -> WS.ConnectionSendMutex               -- ^ Connection Send Mutex for sending 'IOMessage's to the IO device in a safe multi-threaded way.
         -> IO ()
doEvents eh state conn stdoutM connSendM = do
                    request <- readRequest conn stdoutM
                    let
                        (resp, state') = eh state request 
                        sendActions = map (sendResponse connSendM conn stdoutM)  resp
                    sequence sendActions
                    doEvents eh state' conn stdoutM connSendM

{- |
 This functions reads a request from the 'WS.Connection' and parses it into an 'IOMessage'.
-}                    
readRequest :: WS.Connection -> WS.StdOutMutex -> IO IOMessage                
readRequest conn stdoutM = do
                            msg <- WS.receiveData conn :: IO T.Text
                            let
                                string = T.unpack msg
                                request = stringToJsonObject string
                            --WS.safePutStr stdoutM string
                            return request

{- |
 This function takes an 'IOMessage' and parses it into a response. Then it sends the response to the 'WS.Connection'. 
-}                            
sendResponse :: WS.ConnectionSendMutex -> WS.Connection -> WS.StdOutMutex -> IOMessage -> IO ()
sendResponse mu conn stdoutM response = do
                                        let
                                            string = show response
                                            text = T.pack string
                                        --WS.safePutStr stdoutM string
                                        WS.safeSendText mu conn text