-----------------------------------------------------------------------------
--
-- Module      :  Builder
-- Copyright   :
-- License     :  BSD3
--
-- Maintainer  :  agocorona@gmail.com
-- Stability   :  experimental
-- Portability :
--
-- | Monad and Monoid instances for a builder that hang DOM elements from the
-- current parent element. It uses Haste.DOM from the haste-compiler
--
-----------------------------------------------------------------------------
{-#LANGUAGE CPP, ForeignFunctionInterface, TypeSynonymInstances, FlexibleInstances
            , OverloadedStrings, DeriveDataTypeable, UndecidableInstances
            , OverlappingInstances #-}
module Haste.Perch where
import Data.Typeable
import Haste hiding (Attribute, attr)
import Haste.DOM hiding (Attribute, attr)
import Haste.Foreign
import Data.Maybe
import Data.Monoid
import Unsafe.Coerce
import Data.String
import Control.Monad.IO.Class
import Control.Applicative


newtype PerchM a= Perch{build :: Elem -> IO Elem} deriving Typeable

type Perch = PerchM ()

instance Monoid (PerchM a) where
    mappend mx my= Perch $ \e -> do
         build mx e
         build my e
         return e
    mempty  = Perch return

instance Functor PerchM
instance Applicative PerchM

instance Monad PerchM where
   (>>) x y= mappend (unsafeCoerce x) y
   (>>=) = error "bind (>>=) invocation in the Perch monad creating DOM elements"
   return  = mempty

instance MonadIO PerchM where
  liftIO mx= Perch $ \e -> mx >> return e

instance IsString Perch where
  fromString= toElem

class ToElem a where
  toElem :: a -> Perch

instance ToElem String where
   toElem s= Perch $ \e -> do
        e' <- newTextElem s
        addChild e' e
        return e'

instance Show a => ToElem a where toElem = toElem . show

instance ToElem (PerchM a) where toElem e = unsafeCoerce e

attr tag (n, v)=Perch $ \e -> do
        tag' <- build tag e
        setAttr tag' n v
        return tag'

nelem :: String -> Perch
nelem s= Perch $ \e ->do
        e' <- newElem s
        addChild e' e
        return e'

child :: ToElem a => Perch -> a -> Perch
child me ch= Perch $ \e' -> do
        e <- build me e'
        let t = toElem ch
        r <- build t e
        return e

setHtml :: Perch -> String -> Perch
setHtml me text= Perch $ \e' -> do
    e <- build me e'
    inner e text
    return e'
  where
  inner :: Elem -> String -> IO ()
  inner e txt = setProp e "innerHTML" txt

-- | create an element and add a Haste event handler to it.
addEvent :: Perch -> Event IO a -> a -> Perch
addEvent be event action= Perch $ \e -> do
     e' <- build be e
     let atr= evtName event
     has <- getAttr e'  atr
     case has of
       "true" -> return e'
       _ -> do
        onEvent e' event  action
        setAttr e' atr "true"
        return e'





--listen :: JSType event => Elem -> event -> a -> IO Bool
--listen e event f= jsSetCB e (toJSString event) (mkCallback $! f)
--
--
--foreign import ccall jsSetCB :: Elem -> JSString -> JSFun a -> IO Bool


-- Leaf DOM nodes
--
area = nelem "area"
base = nelem "base"
br = nelem "br"
col = nelem "col"
embed = nelem "embed"
hr = nelem "hr"
img = nelem "img"
input = nelem "input"
keygen = nelem "keygen"
link = nelem "link"
menuitem = nelem "menuitem"
meta = nelem "meta"
param = nelem "param"
source = nelem "source"
track = nelem "track"
wbr = nelem "wbr"

-- Parent DOM nodes
--

a cont = nelem  "a" `child` cont
abbr cont = nelem  "abbr" `child` cont
address cont = nelem  "address" `child` cont
article cont = nelem  "article" `child` cont
aside cont = nelem  "aside" `child` cont
audio cont = nelem  "audio" `child` cont
b cont = nelem  "b" `child` cont
bdo cont = nelem  "bdo" `child` cont
blockquote cont = nelem  "blockquote" `child` cont
body cont = nelem  "body" `child` cont
button cont = nelem  "button" `child` cont
canvas cont = nelem  "canvas" `child` cont
caption cont = nelem  "caption" `child` cont
cite cont = nelem  "cite" `child` cont
code cont = nelem  "code" `child` cont
colgroup cont = nelem  "colgroup" `child` cont
command cont = nelem  "command" `child` cont
datalist cont = nelem  "datalist" `child` cont
dd cont = nelem  "dd" `child` cont
del cont = nelem  "del" `child` cont
details cont = nelem  "details" `child` cont
dfn cont = nelem  "dfn" `child` cont
div cont = nelem  "div" `child` cont
dl cont = nelem  "dl" `child` cont
dt cont = nelem  "dt" `child` cont
em cont = nelem  "em" `child` cont
fieldset cont = nelem  "fieldset" `child` cont
figcaption cont = nelem  "figcaption" `child` cont
figure cont = nelem  "figure" `child` cont
footer cont = nelem  "footer" `child` cont
form cont = nelem  "form" `child` cont
h1 cont = nelem  "h1" `child` cont
h2 cont = nelem  "h2" `child` cont
h3 cont = nelem  "h3" `child` cont
h4 cont = nelem  "h4" `child` cont
h5 cont = nelem  "h5" `child` cont
h6 cont = nelem  "h6" `child` cont
head cont = nelem  "head" `child` cont
header cont = nelem  "header" `child` cont
hgroup cont = nelem  "hgroup" `child` cont
html cont = nelem  "html" `child` cont
i cont = nelem  "i" `child` cont
iframe cont = nelem  "iframe" `child` cont
ins cont = nelem  "ins" `child` cont
kbd cont = nelem  "kbd" `child` cont
label cont = nelem  "label" `child` cont
legend cont = nelem  "legend" `child` cont
li cont = nelem  "li" `child` cont
map cont = nelem  "map" `child` cont
mark cont = nelem  "mark" `child` cont
menu cont = nelem  "menu" `child` cont
meter cont = nelem  "meter" `child` cont
nav cont = nelem  "nav" `child` cont
noscript cont = nelem  "noscript" `child` cont
object cont = nelem  "object" `child` cont
ol cont = nelem  "ol" `child` cont
optgroup cont = nelem  "optgroup" `child` cont
option cont = nelem  "option" `child` cont
output cont = nelem  "output" `child` cont
p cont = nelem  "p" `child` cont
pre cont = nelem  "pre" `child` cont
progress cont = nelem  "progress" `child` cont
q cont = nelem  "q" `child` cont
rp cont = nelem  "rp" `child` cont
rt cont = nelem  "rt" `child` cont
ruby cont = nelem  "ruby" `child` cont
samp cont = nelem  "samp" `child` cont
script cont = nelem  "script" `child` cont
section cont = nelem  "section" `child` cont
select cont = nelem  "select" `child` cont
small cont = nelem  "small" `child` cont
span cont = nelem  "span" `child` cont
strong cont = nelem  "strong" `child` cont
{-style cont = nelem  "style" `child` cont-}
sub cont = nelem  "sub" `child` cont
summary cont = nelem  "summary" `child` cont
sup cont = nelem  "sup" `child` cont
table cont = nelem  "table" `child` cont
tbody cont = nelem  "tbody" `child` cont
td cont = nelem  "td" `child` cont
textarea cont = nelem  "textarea" `child` cont
tfoot cont = nelem  "tfoot" `child` cont
th cont = nelem  "th" `child` cont
thead cont = nelem  "thead" `child` cont
time cont = nelem  "time" `child` cont
title cont = nelem  "title" `child` cont
tr cont = nelem  "tr" `child` cont
ul cont = nelem  "ul" `child` cont
var cont = nelem  "var" `child` cont
video cont = nelem  "video" `child` cont


ctag tag cont= nelem tag `child` cont

-- HTML4 support
center cont= nelem "center" `child` cont

noHtml= mempty :: Perch

type Attribute = (String,String)

class Attributable h where
 (!) :: h -> Attribute -> h

instance ToElem a => Attributable (a -> Perch) where
 (!) pe atrib = \e -> pe e `attr` atrib

instance Attributable Perch where
 (!) = attr


atr n v= (n,v)

style= atr "style"

id = atr "id"

width= atr "width"

height= atr "height"

href= atr "href"

src= atr "src"


---------------- DOM Tree navigation and edition

-- | return the current node
this :: Perch
this= Perch $ \e -> return e

-- | goes to the parent node of the first and execute the second
goParent :: Perch -> Perch -> Perch
goParent pe pe'= Perch $ \e' -> do
  e <- build pe e'
  p <- parent e
  e2 <- build pe' p
  return e2

-- | delete the current node. Return the parent
delete :: Perch
delete= Perch $ \e -> do
             p <- parent e
             removeChild e p
             return p

-- | delete the children of the current node.
clear :: Perch
clear= Perch $ \e -> clearChildren e >> return e

-- | replace the current node with a new one
outer ::  Perch -> Perch -> Perch
outer olde  newe= Perch $ \e'' -> do
   e  <- build olde e''
   e' <- build newe e''
   replace e e'

replace :: Elem -> Elem -> IO Elem
replace= ffi  "(function(e,e1){var par=  e.parentNode;par.replaceChild(e1,e);return e1;})"



parent :: Elem -> IO Elem
parent= ffi "(function(e){return e.parentNode;})"


getBody :: IO Elem
getBody= ffi "(function(){return document.body;})"

getDocument :: IO Elem
getDocument= ffi  "(function(){return document;})"



-- ! JQuery-like DOM manipulation: using a selector for querySelectorAll,
-- it apply the Perch DOM manipulation of the second parameter for each of the matches
--
-- Example
--
-- > main= do
-- >  body <- getBody
-- >  (flip build) body $ pre $ do
-- >      div ! atr "class" "modify" $ "click"
-- >      div $ "not changed"
-- >      div ! atr "class" "modify" $ "here"
-- >
-- >      addEvent this OnClick $ \_ _ -> do
-- >          forElems' ".modify" $  this ! style "color:red"
forElems' :: String -> Perch -> IO ()
forElems' for doit= do
    (flip build) undefined (forElems for doit)
    return ()

-- | a more declarative synmonym of `forElems'`
withElems'= forElems'

-- ! JQuery-like DOM manipulation: using a selector for querySelectorAll,
-- it apply the Perch DOM manipulation of the second parameter for each of the matches
forElems :: String -> Perch -> Perch
forElems selectors dosomething= Perch $ \e -> do
    es <- queryAll  selectors
    mapM (build dosomething) es
    return e
    where
    queryAll ::  String -> IO  [Elem]
    queryAll = ffi "(function(sel){return document.querySelectorAll(sel);})"

-- | a more declarative synmonym of `forElems`
withElems= forElems