{-# LANGUAGE OverloadedStrings, TemplateHaskell, GADTs, KindSignatures #-}

module Graphics.Blank
        (
         -- * Starting blank-canvas
          blankCanvas
        -- * Graphics 'Context'
        , Context       -- abstact
        , send
        , events
         -- * Drawing pictures using the Canvas DSL
        , Canvas        -- abstact
        , module Graphics.Blank.Generated
        , readEvent
        , tryReadEvent
        , size
        -- * Events
        , Event(..)
        , EventName(..)
        , EventQueue
        , readEventQueue
        , tryReadEventQueue
        ) where

import Control.Concurrent
import Control.Monad.IO.Class (liftIO)
import Network.Wai.Handler.Warp (run)
import Network.Wai (Middleware,remoteHost, responseLBS)
import qualified Network.HTTP.Types as H
import Network.Socket (SockAddr(..))
import Web.Scotty as S
--import Network.Wai.Middleware.RequestLogger -- Used when debugging
--import Network.Wai.Middleware.Static
import qualified Data.Text.Lazy as T

import qualified Data.Map as Map

import Graphics.Blank.Events
import Graphics.Blank.Context
import Graphics.Blank.Canvas
import Graphics.Blank.Generated
import Paths_blank_canvas

-- | blankCanvas is the main entry point into blank-canvas.
-- A typical invocation would be
--
-- >module Main where
-- >
-- >import Graphics.Blank
-- >
-- >main = blankCanvas 3000 $ \ context -> do
-- >        send context $ do
-- >                moveTo(50,50)
-- >                lineTo(200,100)
-- >                lineWidth 10
-- >                strokeStyle "red"
-- >                stroke()
-- >


blankCanvas :: Int -> (Context -> IO ()) -> IO ()
blankCanvas port actions = do
   dataDir <- getDataDir
--   print dataDir

   uVar <- newMVar 0
   let getUniq :: IO Int
       getUniq = do
              u <- takeMVar uVar
              putMVar uVar (u + 1)
              return u

   contextDB <- newMVar $ (Map.empty :: Map.Map Int Context)
   let newContext :: (Float,Float) -> IO Int
       newContext (w,h) = do
            uq <- getUniq
            picture <- newEmptyMVar
            callbacks <- newMVar $ Map.empty
            let cxt = Context (w,h) picture callbacks uq
            db <- takeMVar contextDB
            putMVar contextDB $ Map.insert uq cxt db
            -- Here is where we actually spawn the user code
            _ <- forkIO $ actions cxt
            return uq

   app <- scottyApp $ do
--        middleware logStdoutDev
        middleware local_only

--        middleware $ staticRoot $ TS.pack $ (dataDir ++ "/static")

        get "/" $ file $ dataDir ++ "/static/index.html"
        get "/jquery.js" $ file $ dataDir ++ "/static/jquery.js"
        get "/jquery-json.js" $ file $ dataDir ++ "/static/jquery-json.js"

        post "/start" $ do
            req <- jsonData
            uq  <- liftIO $ newContext req
            text (T.pack $ "session = " ++ show uq ++ ";redraw();")

        post "/event/:num" $ do
            addHeader "Cache-Control" "max-age=0, no-cache, private, no-store, must-revalidate"
            num <- param "num"
--            liftIO $ print (num :: Int)
            NamedEvent nm event <- jsonData
            db <- liftIO $ readMVar contextDB
            case Map.lookup num db of
               Nothing -> json ()
               Just (Context _ _ callbacks _) -> do
                   db' <- liftIO $ readMVar callbacks
--                   liftIO $ print (nm,event)
                   case Map.lookup nm db' of
                       Nothing -> json ()
                       Just var -> do liftIO $ writeEventQueue var event
                                      json ()

        get "/canvas/:num" $ do
            addHeader "Cache-Control" "max-age=0, no-cache, private, no-store, must-revalidate"
            -- do something and return a new list of commands to the client
            num <- param "num"
--            liftIO $ print (num :: Int)
            let tryPicture picture n = do
                    res <- liftIO $ tryTakeMVar picture
                    case res of
                     Just js -> do
--                            liftIO $ print js
                            text $ T.pack js
                     Nothing | n == 0 ->
                            -- give the browser something to do (approx every second)
                            text (T.pack "")
                     Nothing -> do
                            -- hack, wait a 1/10 of a second
                            liftIO $ threadDelay (100 * 1000)
                            tryPicture picture (n - 1 :: Int)

            db <- liftIO $ readMVar contextDB
            case Map.lookup num db of
               Nothing -> text (T.pack $ "alert('/canvas/, can not find " ++ show num ++ "');")
               Just (Context _ pic _ _) -> tryPicture pic 10

   run port app

-- | Sends a set of Canvas commands to the canvas. Attempts
-- to common up as many commands as possible.
send :: Context -> Canvas a -> IO a
send cxt@(Context (h,w) _ _ _) commands = send' commands id
  where
      send' :: Canvas a -> (String -> String) -> IO a

      send' (Bind (Return a) k)    cmds = send' (k a) cmds
      send' (Bind (Bind m k1) k2)  cmds = send' (Bind m (\ r -> Bind (k1 r) k2)) cmds
      send' (Bind (Command cmd) k) cmds = send' (k ()) (cmds . shows cmd)
      send' (Bind Size k)          cmds = send' (k (h,w)) cmds
      send' (Bind other k)         cmds = do
              res <- send' other cmds
              send' (k res) id

      send' (Get a op)             cmds = do
              -- clear the commands
              sendToCanvas cxt cmds
              -- get the channel for this event
              chan <- events cxt a
              op chan

      send' (Return a)             cmds = do
              sendToCanvas cxt cmds
              return a
      send' other                  cmds = send' (Bind other Return) cmds


local_only :: Middleware
local_only f r = case remoteHost r of
                   SockAddrInet _  h | h == fromIntegral home
                                    -> f r
                   SockAddrUnix _   -> f r
                   _                ->  return $ responseLBS H.status403
                                                             [("Content-Type", "text/plain")]
                                                             "local access only"
 where
        home :: Integer
        home = 127 + (256 * 256 * 256) * 1