{-# LANGUAGE QuasiQuotes #-}

-- | JMonkey interpreter using jmacro backend.
module JMonkey.Interpreter
        (
        -- * Interpreter
          interpretString
        , interpretJStat
        ) where

import           Control.Monad.Free
import           Language.Javascript.JMacro
import           Text.Casing
import           JMonkey.Action
import           JMonkey.Data

-- | Interpret to javascript string. 
interpretString :: JMonkey -> String
interpretString = show . renderJs . interpretJStat

toStr :: Selector -> String
toStr (Id s) = '#' : s
toStr (Class s) = '.' : s

onEvent :: String -> String
onEvent = mappend "on"

jvar :: String -> JStat
jvar s = DeclStat (StrI s) Nothing

jref :: String -> JExpr
jref = ValExpr . JVar . StrI

-- TODO: check when compiling?
forbidden :: JStat
forbidden = [jmacro| throw "forbidden action: id corresponds to multiple elements." |]

-- | Interpret to jmacro statement. 
interpretJStat :: JMonkey -> JStat
interpretJStat (Free (Log s n)) = [jmacro| console.log `s` |] <> interpretJStat n
interpretJStat (Free (Alert s n)) = [jmacro| alert `s` |] <> interpretJStat n
interpretJStat (Free (Select sel@(Id s) n)) = [jmacro|
        `jvar tmp`;
        `jref tmp` = document.querySelector(`toStr sel`);
|] <> interpretJStat (n $ Elem tmp)
        where tmp = "i_" ++ snake s -- TODO: allocate by generator?
interpretJStat (Free (Select sel@(Class s) n)) = [jmacro|
        `jvar tmps`;
        `jref tmps` = document.querySelectorAll(`toStr sel`);
|] <> interpretJStat (n $ Elems tmps)
        where tmps = "c_" ++ snake s -- TODO: allocate by generator?
interpretJStat (Free (Add (Elem t) (Class s) n)) = [jmacro| `jref t`.classList.add(`s`); |] <> interpretJStat n
interpretJStat (Free (Add (Elem t) sel@(Id _) n)) = [jmacro| `jref t`.id = `toStr sel`; |] <> interpretJStat n
interpretJStat (Free (Add (Elems t) (Class s) n)) = [jmacro|
        `jref t`.forEach( function e {
                e.classList.add(`s`);
        })
|] <> interpretJStat n
interpretJStat (Free (Add (Elems _) (Id _) n)) = forbidden <> interpretJStat n
interpretJStat (Free (Remove (Elem t) (Class s) n)) = [jmacro| `jref t`.classList.remove(`s`); |] <> interpretJStat n
interpretJStat (Free (Remove (Elem t) sel@(Id _) n)) = [jmacro| if (`jref t`.id == `toStr sel`) { `jref t`.id = ""; } |] <> interpretJStat n
interpretJStat (Free (Remove (Elems t) (Class s) n)) = [jmacro|
        `jref t`.forEach( function e {
                e.classList.remove(`s`);
        })
|] <> interpretJStat n
interpretJStat (Free (Remove (Elems _) (Id _) n)) = forbidden <> interpretJStat n
interpretJStat (Free (On s (Elem t) a n)) = [jmacro|
        `jref t`[`event`] = function {
                `interpretJStat a`;
        }
|] <> interpretJStat n
        where event = onEvent s
interpretJStat (Free (On s (Elems t) a n)) = [jmacro|
        `jref t`.forEach( function e {
                e[`event`] = function { `interpretJStat a`; }
        });
|] <> interpretJStat n
        where event = onEvent s
interpretJStat (Free (OnTime Once i a n)) = [jmacro|
        var f = function { `interpretJStat a`; };
        setTimeout(f, `i`);
|] <> interpretJStat n
interpretJStat (Free (OnTime Endless i a n)) = [jmacro|
        var f = function { `interpretJStat a`; };
        setInterval(f, `i`);
|] <> interpretJStat n
interpretJStat (Free (If (Possess target sel) tA fA n)) = case (target, sel) of
        (Elem t, Class s) -> ifElse [jmacroE| `jref t`.classList.contains(`s`) |] tA fA <> interpretJStat n
        (Elems t, Class s) -> ifElse [jmacroE| Array.from(`jref t`).every(function e { return e.classList.contains(`s`); }) |] tA fA <> interpretJStat n
        (Elem t, Id s) -> ifElse [jmacroE| `jref t`.id == `s` |] tA fA <> interpretJStat n
        (Elems _, Id _) -> forbidden <> interpretJStat n
interpretJStat (Free (If (YOffset op v) tA fA n)) =
        let cond = case op of
                Equal -> [jmacroE| window.pageYOffset == `v` |]
                Grater -> [jmacroE| window.pageYOffset > `v` |]
                Less -> [jmacroE| window.pageYOffset < `v` |]
        in ifElse cond tA fA <> interpretJStat n
interpretJStat (Pure _) = nullStat

ifElse :: JExpr -> JMonkey -> JMonkey -> JStat
ifElse cond tA fA = [jmacro|
        if (`cond`) {
                `interpretJStat tA`;
        } else {
                `interpretJStat fA`;
        }
|]