{-# LANGUAGE FlexibleInstances #-}

module Graphics.QML.Test.ScriptDSL where

import Data.Char
import Data.Int
import Data.List
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as T
import Network.URI
import Numeric

data Expr = Global | Expr {unExpr :: ShowS}

data Prog = Prog ShowS ShowS

instance Monoid Prog where
    mempty = Prog id id
    mappend (Prog a1 b1) (Prog a2 b2) = Prog (a1 . a2) (b2 . b1)

showProg :: Prog -> ShowS
showProg (Prog a b) = a . b

class Literal a where
    literal :: a -> Expr

instance Literal Int where
    literal x = Expr $ shows x

instance Literal Int32 where
    literal x = Expr $ shows x

instance Literal Double where
    literal x | isNaN x                 = Expr $ showString "(0/0)"
              | isInfinite x && (x < 0) = Expr $ showString "(-1/0)"
              | isInfinite x            = Expr $ showString "(1/0)"
              | isNegativeZero x        = Expr $ showString "-0"
              | otherwise               = Expr $ shows x

instance Literal [Char] where
    literal [] = Expr $ showString "\"\""
    literal cs =
        Expr (showChar '"' . (foldr1 (.) $ map f cs) . showChar '"')
        where f '\"' = showString "\\\""
              f '\\' = showString "\\\\"
              f c | ord c < 32 = hexEsc c
                  | ord c > 127 = hexEsc c
                  | otherwise  = showChar c
              hexEsc c = let h = showHex (ord c)
                         in showString "\\u" .  showString (
                                replicate (4 - (length $ h "")) '0') . h

instance Literal Text where
    literal = literal . T.unpack

instance Literal URI where
    literal = literal . ($ "") . uriToString id

var :: Int -> Expr
var 0 = Global
var n = Expr (showChar 'x' . shows n)

sym :: String -> Expr
sym name = Expr $ showString name

dot :: Expr -> String -> Expr
dot Global     m = Expr $ showString m
dot (Expr lhs) m = Expr (lhs . showChar '.' . showString m)

call :: Expr -> [Expr] -> Expr
call (Expr f) ps = Expr (
    f . showChar '(' . (
        foldr1 (.) $ (id:) $ intersperse (showChar ',') $ map unExpr ps) .
    showChar ')')
call _ _ = error "cannot call the context object"

binOp :: String -> Expr -> Expr -> Expr
binOp op (Expr lhs) (Expr rhs) = Expr (
    showChar '(' . lhs . showString op . rhs . showChar ')')
binOp _ _ _ = error "cannot operate on the context object"

eq :: Expr -> Expr -> Expr
eq = binOp " == "

neq :: Expr -> Expr -> Expr
neq = binOp " != "

eval :: Expr -> Prog
eval (Expr ex) = Prog (ex . showString ";\n") id
eval _ = error "cannot eval the context object"

set :: Expr -> Expr -> Prog
set (Expr lhs) (Expr rhs) =
    Prog (lhs . showString " = " . rhs . showString ";\n") id
set _ _ = error "cannot set the context object"

saveVar :: Int -> Expr -> Prog
saveVar v (Expr rhs) =
    Prog (showString "var x" . shows v . showString " = " .
        rhs . showString ";\n") id
saveVar _ _ = error "cannot save the context object"

assert :: Expr -> Prog
assert (Expr ex) =
    Prog (showString "if (!" . ex .
        showString ") {window.close(); throw -1;}\n") id
assert _ = error "cannot assert the context object"

connect :: Expr -> Expr -> Prog
connect sig fn = eval $ sig `dot` "connect" `call` [fn]

disconnect :: Expr -> Expr -> Prog
disconnect sig fn = eval $ sig `dot` "disconnect" `call` [fn]

makeCont :: [String] -> Prog -> Prog -> Prog
makeCont args (Prog a1 b1) (Prog a2 b2) =
    Prog (showString "var cont = function(" . farg . showString ") {\n" . a2)
        (b2 . showString "};\n" . a1 . b1)
    where farg = foldr1 (.) $ (id:) $ intersperse (showChar ',') $
                     map showString args

contVar :: Expr
contVar = sym "cont"

callee :: Expr
callee = sym "arguments.callee"

end :: Prog
end = Prog (showString "window.close();\n") id
