{-# LANGUAGE FlexibleInstances, OverlappingInstances, TypeSynonymInstances, UndecidableInstances #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  HJScript.Lang
-- License     :  BSD-style
-- Maintainer  :  Joel Bjornson joel.bjornson@gmail.com,
--                Niklas Broberg nibro@cs.chalmers.se
-- Stability   :  experimental
-----------------------------------------------------------------------------
module HJScript.Lang
  (
    -- Operators
    preinc, postinc, predec, postdec,
    (.+.), (.-.), (.*.), (./.), (.&&.), (.||.), (.==.), (.!=.),
    (.>.), (.<.) , (.>=.), (.<=.) , (.=.), (.+=.), (?), (<|>),

    -- Method calls
    this, callMethod, callVoidMethod, callProc,

    -- Functions and declarations
    function, procedure, functionDecl, procedureDecl,

    -- Control flow
    for, forIn, forInVar, while, doWhile, doIf, doElse, doIfElse, doIfNoElse, noElse,

    -- Objects
    var, varWith, inVar, new, delete, ( # ), ( #. ),
    rec, first, second, x, y,

    -- Helpers
    ( #! ) , jnull, jShow, castObject, hasFeature,
    break, continue, true ,ifOp, false, int, float, bool, string,

    -- Re-exports from internal module HJScript.Monad
    HJScript, IsHJScript(..),
    outputBlock, outputStmt,

    -- Evaluating HJScript
    evaluateHJScript, evalHJScript,

    -- Re-export all of Language.HJavaScript.Syntax
    module Language.HJavaScript.Syntax

  )  where

import Language.HJavaScript.Syntax
import HJScript.Monad
import Prelude hiding (break)


-- Infix operators
infixr  2   .||.
infixr  3   .&&.
infix   4   .=. , .==. , .!=., .>., .<. , .<=. , .>=. , ? ,
            `doIfNoElse` , `doIfElse`
infixl  6   .+. , .-.
infixl  7   .*., ./.
infixl  8   # , #! , #. , <|>

-------------------------------------------------------------------
-- Operators
-------------------------------------------------------------------
type HJSJBinOperator t r = Exp t -> Exp t -> Exp r

-- | Incrementing or decrementing numbers.
preinc :: Num t => Var t -> HJScript ()
preinc = outputStmt . ExpStmt . JIncrement Pre

postinc :: Num t => Var t -> HJScript ()
postinc = outputStmt . ExpStmt . JIncrement Pst

predec :: Num t => Var t -> HJScript ()
predec = outputStmt . ExpStmt . JDecrement Pre

postdec :: Num t => Var t -> HJScript ()
postdec = outputStmt . ExpStmt . JDecrement Pst

binOp :: BinOp t r -> HJSJBinOperator t r
binOp op e1 e2 = JBinOp (toExp e1) op (toExp e2)

(.+.) :: PlusOpType a => HJSJBinOperator a a
(.+.) = binOp Plus

(.-.) :: Num a => HJSJBinOperator a a
(.-.) = binOp Minus

(.*.) :: Num a => HJSJBinOperator a a
(.*.) = binOp Times

(./.) :: Num a => HJSJBinOperator a a
(./.) = binOp Div

(.&&.) :: HJSJBinOperator Bool Bool
(.&&.) = binOp And

(.||.) :: HJSJBinOperator Bool Bool
(.||.) = binOp Or

(.==.) :: HJSJBinOperator a Bool
(.==.) = binOp Equals

(.!=.) :: HJSJBinOperator a Bool
(.!=.) = binOp NotEquals

(.>.) :: Num a => HJSJBinOperator a Bool
(.>.) = binOp GThan

(.<.) :: Num a => HJSJBinOperator a Bool
(.<.) = binOp LThan

(.>=.) :: Num a => HJSJBinOperator a Bool
(.>=.) = binOp GEThan

(.<=.) :: Num a => HJSJBinOperator a Bool
(.<=.) = binOp LEThan

-- | Assignment
(.=.) ::  Var t -> Exp t -> HJScript ()
v .=. e = outputStmt . ExpStmt $ JAssign v e

-- | Plus with
(.+=.) :: Num t => Var t -> Exp t -> HJScript ()
v .+=. e = outputStmt . ExpStmt $ JAssignWith v PlusAssign e

-----------------------------------------------------------
-- Control flow
-----------------------------------------------------------

-- | for
for :: JInt -> JInt -> (JInt -> HJScript t) -> HJScript ()
for from to script = do
  name <- newVarName
  (_,body) <- hjsInside $ script (val $ JVar name)
  outputStmt $ For (pre name) (cond name) (inc name) body
  where
    inc name  = JIncrement Pst (JVar name)  :: JInt
    pre name  = VarDeclAssign name from
    cond name = (val $ JVar name) .<=. to

-- | for (var in object) { .. }
forIn :: (IsDeref d) => d -> (JString -> HJScript ()) -> HJScript ()
forIn obj script =
  do v <- var
     (_, body) <- hjsInside $ script (val v)
     outputStmt $ ForIn v obj body

-- | for (var in object) { .. }
forInVar :: (IsDeref d) => d -> (Var a -> HJScript ()) -> HJScript ()
forInVar obj script =
  do v <- var
     (_, body) <- hjsInside $ script (obj # propertyVar (val v))
     outputStmt $ ForIn v obj body

-- | while
while :: JBool -> HJScript t -> HJScript ()
while cond script = do
  (_,body) <- hjsInside script
  outputStmt $ While cond body

-- | doWhile
doWhile :: HJScript t -> JBool -> HJScript ()
doWhile = flip while

-- | doIf
doIf :: JBool -> HJScript t -> HJScript (Elses ()) -> HJScript ()
doIf cond script els = do
  (_,body) <- hjsInside script
  els' <- els
  outputStmt $ If cond body els'

-- | doElse
doElse :: HJScript () -> HJScript (Elses ())
doElse script = do
  (_,body) <- hjsInside script
  return $ Else body

-- | doIfElse
doIfElse :: JBool -> (HJScript t1, HJScript t2) -> HJScript ()
doIfElse cond (hj1,hj2) = do
  (_,body1) <- hjsInside hj1
  (_,body2) <- hjsInside hj2
  outputStmt $ If cond body1 (Else body2)

-- | Alternative if-else syntax: isTrue ? (doA,doB)
(?) :: JBool -> (HJScript t1, HJScript t2) -> HJScript ()
(?) = doIfElse

-- | Providing a way of writing if-else expression as in: isTrue ? doA <|> doB
(<|>) :: a -> a -> (a,a)
(<|>) = (,)

-- | Only an if branch
doIfNoElse :: Exp Bool -> HJScript () -> HJScript ()
doIfNoElse cond script = doIf cond script noElse

-- | No else branch.
noElse :: HJScript (Elses ())
noElse = return NoElse

-----------------------------------------------------------
-- HJScript function declarations
-----------------------------------------------------------

-- | Anonymous function, returning an expression
function :: (FormalParams a t, VarsToExps a e) =>
            (e -> HJScript (Exp r)) ->  HJScript (Exp (t -> r))
function fun = do
  n <- newVarNum
  let args = mkFParams (\_ -> ()) n
  let script = fun $ v2e args
  (ret, body) <-  hjsInside script
  let body' = addReturn ret body
  return $ JFunction Nothing args body'

-- | Anonymous void function.
procedure :: (FormalParams a t, VarsToExps a e) =>
             (e -> HJScript ()) -> HJScript (Exp (t -> ()))
procedure fun = do
  n <- newVarNum
  let args = mkFParams (\_ -> ()) n
  body <- return . snd =<< (hjsInside $ fun $ v2e args)
  return $ JFunction Nothing args body

-- | Function declaration
functionDecl :: (FormalParams a t, VarsToExps a e) =>
                String -> (e -> HJScript (Exp r)) -> HJScript ()
functionDecl name fun = do
  n <- newVarNum
  let args = mkFParams (\_ -> ()) n
  let script = fun $ v2e args
  (ret,body) <- hjsInside script
  let body' = addReturn ret body
  outputStmt $ ExpStmt $ JFunction (Just name) args body'

-- | Procedure declaration.
procedureDecl ::  (FormalParams a t, VarsToExps a e) =>
                  String -> (e -> HJScript ()) -> HJScript ()

procedureDecl name fun = do
  n <- newVarNum
  let args = mkFParams (\_ -> ()) n
  let script = fun $ v2e args
  (_, body) <- hjsInside script
  outputStmt $ ExpStmt $ JFunction (Just name) args body

-- | Adds a return statement to a Block.
addReturn :: Exp t -> Block () -> Block t
addReturn e block = Sequence block (Return e)



-----------------------------------------------------------
-- A return-adding evaluator
-----------------------------------------------------------

evaluateHJScript :: HJScript (Exp t) -> Block t
evaluateHJScript m =
    let (v,b) = evalHJScript m
     in addReturn v b

-----------------------------------------------------------
-- HJScript method calls
-----------------------------------------------------------

-- Call an object method, returning an expression.
callMethod :: (IsDeref d, Args e t1) => String -> e -> d -> Exp t2
callMethod = methodCall

-- Method call for void methods. Returns a HJScript () since the return value is
-- not of any interest.
callVoidMethod :: (Args e t1, IsDeref a) => String -> e -> a -> HJScript ()
callVoidMethod fun args = outputStmt . ExpStmt . callMethod fun args

-----------------------------------------------------------
-- Variables, objects and records
-----------------------------------------------------------
-- Creates a JavaScript variable with a fresh name.
var :: HJScript (Var t)
var = do
  name <- newVarName
  outputStmt $ VarDecl name
  return $ JVar name

-- Assign an expression to a new variable.
varWith :: Exp t -> HJScript (Var t)
varWith e = do
  name <- newVarName
  outputStmt $ VarDeclAssign name e
  return $ JVar name

inVar :: Exp t -> HJScript (Exp t)
inVar = fmap val . varWith

this :: IsClass c => Exp c
this = JThis

callProc :: (Args e t) => Exp (t -> t1) -> e -> HJScript ()
callProc e = outputStmt . ExpStmt . (JCall e)

-- Create new Objects.
new ::  (HasConstructor o e t, Args e t) => o -> e -> HJScript (Exp o)
new o = fmap val . varWith . JNew o

-- |delete a property
--
-- Can only delete properties/variables that are created implicitly,
-- not those declared with the var statement.
--
-- returns true if property was deleted. false if operation was not possible.
delete :: Var a -> Exp Bool
delete = JDelete

-- | Dereferencing operator, similar to the `dot` operator in JavaScript.
-- E.g. document.forms => document # forms, same as forms document
( # ) :: a -> (a -> b) -> b
a # f = f a

-- Operator used for binding dereferencing without argument,
-- e.g. "style #. display"
( #. ) :: (a -> b) -> (b -> c) -> (a -> c)
( #. ) = flip (.)

-- Creating a record
rec :: Exp a -> Exp b -> Exp (Rec a b)
rec = JRec

first, x :: Exp (Rec a b) -> Exp a
first = JFst
x = first

second, y :: Exp (Rec a b) -> Exp b
second = JSnd
y = second

-----------------------------------------------------------
-- Helpers
-----------------------------------------------------------

-- | Accessing arrays.
( #! ) ::  JArray t -> JInt -> Var t
( #! ) = JArrayIndex

-- | Null value
jnull :: IsNullable t => Exp t
jnull = JNull

-- | Converts to JString expression.
jShow :: JShow t => Exp t -> JString
jShow = JShow

-- | Casting an JObject
castObject :: (IsClass c1, IsClass c2) => JObject c1 -> JObject c2
castObject = JCastObject

-- | Checks if an object is supported by browser
hasFeature :: (IsFeature f , IsClass c) => JObject c -> f -> JBool
hasFeature =  JIsImpl

ifOp :: JBool -> Exp t -> Exp t -> Exp t
ifOp = JIfOp

break :: HJScript ()
break = outputStmt Break

continue :: HJScript ()
continue = outputStmt Continue

true :: JBool
true = JBool True

false :: JBool
false = JBool False

int :: Int -> JInt
int = JInt

float :: Float -> JFloat
float = JFloat

bool :: Bool -> JBool
bool = JBool

string :: String -> JString
string = JString