{-# LANGUAGE FlexibleInstances, OverlappingInstances, TypeSynonymInstances, UndecidableInstances, GADTs #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  HJScript.Monad
-- License     :  BSD-style
-- Maintainer  :  Joel Bjornson joel.bjornson@gmail.com,
--                Niklas Broberg nibro@cs.chalmers.se
-- Stability   :  experimental
-----------------------------------------------------------------------------
module HJScript.Monad
  (
    -- * Data types and classes
    HJScript, HJScript',
    IsHJScript(..),

    -- * Functions
    evalHJScript,
    runHJScript,
    outputBlock,
    outputStmt,
    newVarName,
    newVarNum,
    hjsInside,

  )  where

import Language.HJavaScript.Syntax
import Control.Monad.Writer
import Control.Monad.State

import HSP (XMLGenT, unXMLGenT)

-- | HJScript Monad
type HJScript'= StateT HJState (Writer (Block ()))
type HJScript = XMLGenT HJScript'

-- | To keep track of number of created variables
type HJState = Int

-- | Init state
initState :: HJState
initState = 0

-- | Shows a HJScript ()
instance Show (HJScript ()) where
  show script = show . snd $ evalHJScript script

-- | Block as a Monoid
instance Monoid (Block ()) where
  mempty = EmptyBlock
  mappend EmptyBlock b = b
  mappend b EmptyBlock = b
  mappend b1 (Sequence b2 s) = Sequence (mappend b1 b2) s

-- | Evaluate a script returning a tuple of the produced value and
-- a block of code.
evalHJScript :: HJScript t -> (t, Block ())
evalHJScript m = runWriter $ evalStateT (unXMLGenT m) initState

-- | Runs a script returning the value, the new state and
-- the block of code.
runHJScript :: HJScript t -> HJState -> (t, HJState, Block ())
runHJScript m state =
  let ((v,state'),block) = runWriter $ runStateT (unXMLGenT m) state
  in  (v,state',block)

-- Get the state
getHJState :: HJScript HJState
getHJState = lift get

-- Set the state
putHJState :: HJState -> HJScript ()
putHJState = lift . put

-- | Adds a statement
outputStmt :: Stmt () -> HJScript ()
outputStmt = outputBlock . toBlock

-- | Adds a block
outputBlock :: Block () -> HJScript ()
outputBlock = lift . lift . tell

-- Creates a fresh variable number
newVarNum :: HJScript Int
newVarNum = lift $ do
  n <- get
  put $ n + 1
  return n

-- Creates a fresh variable name
newVarName :: HJScript String
newVarName = do
  n <- newVarNum
  return $ "var" ++ "_" ++ (show n)

-- | Runs one script inside another
hjsInside :: HJScript t -> HJScript (t, Block ())
hjsInside script = do
  state <- getHJState
  let (v,state',block) = runHJScript script state
  putHJState state'
  return (v,block)

-------------------------------------------------------------------
-- IsHJScript
-------------------------------------------------------------------
-- | IsHJscript class with function toHJScript for converting
-- instances to HJScript ()
class IsHJScript a where
  toHJScript :: a -> HJScript ()

instance IsHJScript (HJScript t) where
  toHJScript s = s >> return ()

instance IsHJScript (Block ()) where
  toHJScript = outputBlock

instance IsHJScript (Stmt ()) where
  toHJScript = outputStmt

instance IsHJScript (Exp t) where
  toHJScript = toHJScript . ExpStmt