-- |This is a program which uses OpenGL and the PupEvents framework to
-- demonstrate an example usage pattern. It simply takes a click in
-- the window, sends it to the server (which negates the coordinates),
-- and then paints the original and new Click coordinates with a point.
-- This uses the -threaded option in GHC.
module Main where

import Graphics.Rendering.OpenGL.GL (($=), ($=!), makeGettableStateVar)
import qualified Graphics.Rendering.OpenGL.GL as GL
import qualified Graphics.UI.GLUT as GLUT
import qualified PupEventsPQueue as PQ
import qualified EventsClient as Events
import Control.Concurrent.STM
import Control.Concurrent
import Control.Monad
import System.Environment

-- |Initial OpenGL setup function. The interesting thing here is that
-- we fork a thread using 'checkEvents' to check for events.
main :: IO ()
main = 
    do  args <- getArgs
        let ip = args !! 0
        let priorities = read (args !! 1) :: Int
        (progname, _) <- GLUT.getArgsAndInitialize
        (outqueue, inqueue) <- Events.doClient (Just ip) priorities
        GLUT.createWindow "Hello World"
        GLUT.initialDisplayMode $= [GLUT.DoubleBuffered, GLUT.RGBAMode]
        GLUT.keyboardMouseCallback $=! Just (keyboardMouse inqueue outqueue)
        GLUT.displayCallback $=! render
        forkOS $ checkEvents inqueue
        GL.clearColor $= GL.Color4 0 0 0 1
        GL.blend $= GL.Enabled
        GL.blendFunc $= (GL.SrcAlpha, GL.OneMinusSrcAlpha)
        GL.lineSmooth $= GL.Enabled
        GL.pointSmooth $= GL.Enabled
        GL.polygonSmooth $= GL.Enabled
        GL.clear [GL.ColorBuffer]
        GL.normalize $= GL.Enabled
        GLUT.mainLoop

-- |This is very simple, since all of the drawing is done by Event
-- handlers. We set this function as the display callback and call it
-- after each event has been handled.
render :: IO ()
render = GLUT.swapBuffers

-- |This checks the inqueue returned by the Client module for Events,
-- blocking if it can't find one. We fork this off because if the
-- functionality here resided in the display callback it would block
-- the entire application from doing anything while it was waiting for
-- an event, including calling the display callback. Using this method
-- we handle Events as the come in and call the display callback to
-- swap the buffers when it's done.
checkEvents ::  PQ.PQueue Events.Event -- ^ The queue to check on
                -> IO b
checkEvents pqueue = forever $
    do  event <- atomically $
            do  e <- PQ.getThing pqueue
                case e of
                    Nothing -> retry
                    Just event -> return event
        putStrLn $ "Got event"
        (Events.lookupHandlerClient event) event
        GLUT.postRedisplay Nothing

-- |This is the keyboardMouse callback that gets used by OpenGL. We
-- first normalize the coordinates to a -1,1 range, then we push a
-- Click event to the incoming queue (not all events on the queue have
-- to come from the Server!) and also on the outgoing queue so the
-- server can handle it.
keyboardMouse ::    PQ.PQueue Events.Event -- ^ The incoming events queue
                    -> PQ.PQueue Events.Event -- ^ The outgoing events queue
                    -> GLUT.Key
                    -> GLUT.KeyState
                    -> t
                    -> GLUT.Position
                    -> IO ()
keyboardMouse iqueue oqueue (GLUT.MouseButton GLUT.LeftButton) GLUT.Down _ (GLUT.Position x y) =
    do  (p1, p2) <- normalizeXY (fromIntegral x) (fromIntegral y)
        putStrLn $ "click coords: " ++ show x ++ ", " ++ show y
        putStrLn $ "normalized: " ++ show p1 ++ ", " ++ show p2
        let event = Events.Click p1 p2
        putStrLn $ "KeyboardMouse callback"
        atomically $ PQ.writeThing oqueue (Events.lookupPriorityClient event) event
        atomically $ PQ.writeThing iqueue (Events.lookupPriorityClient event) event
        putStrLn $ "wrote things"
keyboardMouse _ _ _ _ _ _ = return ()

-- |This is a function to normalize coordinates that are given with
-- respect to window dimensions. OpenGL likes it's primitives to have
-- drawing coordinates ranging from -1 to 1 on the x or y axis.
normalizeXY ::  (Fractional t, Fractional t1) => 
                t -- ^ X coordinate
                -> t1 -- ^ Y coordinate
                -> IO (t, t1) -- ^ Normalized coordinates (x, y)
normalizeXY x y = 
    do  (_, (GLUT.Size width height)) <- GLUT.get GLUT.viewport
        let w = fromIntegral width
        let h = fromIntegral height
        return ((x-w/2.0)/(w/2.0), (y-h/2.0)/((-h)/2.0))
