{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Language.Sunroof.JS.Browser
-- Copyright   :  (c) 2011 The University of Kansas
-- License     :  BSD-style (see LICENSE)
-- Maintainer  :  ???
--
-- A reflection of the standard browser Javascript API.
--
module Language.Sunroof.JS.Browser
  ( -- * Top level functions
    alert
  , confirm
  , prompt
  , decodeURI
  , encodeURI
  , decodeURIComponent
  , encodeURIComponent
  , eval
  , isFinite
  , isNaN
  , parseFloat
  , parseInt
  -- * Window API
  , window
  , setInterval
  , clearInterval
  , setTimeout
  , clearTimeout
  -- * Screen API
  , screen
  -- * Document API
  , document
  , getElementById
  , getElementsByName
  , getElementsByTagName
  , createAttribute
  , createElement
  , createTextNode
  , open
  , close
  , write
  , writeln
  , setCookie
  , cookie
  , referrer
  , setTitle
  , title
  , url
  -- * Image DOM
  , src
  -- * The JavaScript Console
  , JSConsole
  , console
  , Language.Sunroof.JS.Browser.log
  , debug
  , info
  , warn
  , Language.Sunroof.JS.Browser.error
  ) where

import Prelude hiding (isNaN)

import Data.Boolean ( BooleanOf, IfB(..), EqB(..) )

import Language.Sunroof.Types
  ( JS, JSB
  , JS ( (:=) )
  , fun
  , invoke
  , attr
  , apply
  , (#)
  , continuation
  )
import Language.Sunroof.Classes ( Sunroof(..), SunroofArgument )
import Language.Sunroof.Selector ( JSSelector )
import Language.Sunroof.JS.Bool ( JSBool, jsIfB )
import Language.Sunroof.JS.Object ( JSObject, object )
import Language.Sunroof.JS.String ( JSString )
import Language.Sunroof.JS.Number ( JSNumber )

-- -----------------------------------------------------------------------
-- Object Independent Functions
-- -----------------------------------------------------------------------

-- | Display the given text in a message box.
--
--   See <http://www.w3schools.com/js/js_popup.asp>.
alert :: JSString -> JS t ()
alert msg = fun "alert" `apply` (msg)

-- | Ask the user to confirm the given massege.
--
--   See <http://www.w3schools.com/js/js_popup.asp>.
confirm :: JSString -> JS t JSBool
confirm msg = fun "confirm" `apply` (msg)

-- | Ask the user to give some input. May return @null@
--   or a string. Don't forget to check against 'nullJS'
--   before casting to string.
--
--   See <http://www.w3schools.com/js/js_popup.asp>.
prompt :: JSString -> JSString -> JS t JSObject
prompt msg val = fun "prompt" `apply` (msg, val)

-- | Decode the URI encoded in the given string.
decodeURI :: JSString -> JS t JSString
decodeURI str = fun "decodeURI" `apply` (str)

-- | Encode the given string in URI encoding.
encodeURI :: JSString -> JS t JSString
encodeURI str = fun "encodeURI" `apply` (str)

-- | Decode the URI encoded string. For use with 'encodeURIComponent'.
decodeURIComponent :: JSString -> JS t JSString
decodeURIComponent str = fun "decodeURIComponent" `apply` (str)

-- | Encode the string with URI encoding. This encodes a few more
--   characters to make the string safe for direct server communication (AJAX).
encodeURIComponent :: JSString -> JS t JSString
encodeURIComponent str = fun "encodeURIComponent" `apply` (str)

-- | Evaluate the given JavaScript string if possible. Returns
--   the result of evaluation.
-- TODO: think about this a bit.
eval :: (Sunroof a) => JSString -> JS t a
eval str = fun "eval" `apply` (str)

-- | Check if a given number is within the valid JavaScript number range.
isFinite :: JSNumber -> JS t JSBool
isFinite n = fun "isFinite" `apply` (n)

-- | Check if a given number is NaN or not.
isNaN :: JSNumber -> JS t JSBool
isNaN n = fun "isNaN" `apply` (n)

-- | Parse the given string to a number.
parseFloat :: JSString -> JS t JSNumber
parseFloat str = fun "parseFloat" `apply` (str)

-- | Parse the given string to a number.
parseInt :: JSString -> JS t JSNumber
parseInt str = fun "parseInt" `apply` (str)

-- -----------------------------------------------------------------------
-- Window API
-- -----------------------------------------------------------------------

-- | The window object.
window :: JSObject
window = object "window"

-- | Calls a function at specified intervals in milliseconds.
--   It will continue calling the function until 'clearInterval' is called,
--   or the window is closed. The returned number is needed for 'clearInterval'.
--   This is supposed to be called on the 'window' object.
--   See: <http://www.w3schools.com/jsref/met_win_setinterval.asp>
setInterval :: (() -> JSB ()) -> JSNumber -> JSObject -> JS t JSNumber
setInterval f interval o = do
  callback <- continuation f
  o # invoke "setInterval" (callback, interval)

-- | Clears a timer set with the 'setInterval' method.
--   This is supposed to be called on the 'window' object.
--   See: <http://www.w3schools.com/jsref/met_win_clearinterval.asp>
clearInterval :: JSNumber -> JSObject -> JS t ()
clearInterval ident = invoke "clearInterval" (ident)

-- | Execute the given continutation after the given amount of
--   milliseconds. Returns a handler for the set timer.
--   This is supposed to be called on the 'window' object.
--   See: <http://www.w3schools.com/jsref/met_win_settimeout.asp>
setTimeout :: (() -> JSB ()) -> JSNumber -> JSObject -> JS t JSNumber
setTimeout f interval o = do
  callback <- continuation f
  o # invoke "setTimeout" (callback, interval)

-- | Removes the timer associated with the given handler.
--   This is supposed to be called on the 'window' object.
--   See: <http://www.w3schools.com/jsref/met_win_cleartimeout.asp>
clearTimeout :: JSNumber -> JSObject -> JS t ()
clearTimeout ident = invoke "clearTimeout" (ident)

-- -----------------------------------------------------------------------
-- Screen API
-- -----------------------------------------------------------------------

-- | The screen object.
screen :: JSObject
screen = object "screen"

-- -----------------------------------------------------------------------
-- Document API
-- -----------------------------------------------------------------------

-- | The document object.
document :: JSObject
document = object "document"

-- | Get the DOM object of the element with the given id.
--   For use with 'document'.
getElementById :: JSString -- ^ The id.
               -> JSObject -> JS t JSObject
getElementById ident = invoke "getElementById" (ident)

-- | Get the DOM objects of the elements with the given name.
--   For use with 'document'.
getElementsByName :: JSString -- ^ The name.
                  -> JSObject -> JS t JSObject
getElementsByName name = invoke "getElementsByName" (name)

-- | Get the DOM objects of the elements with the given tag.
--   For use with 'document'.
getElementsByTagName :: JSString -- ^ The tag name.
                     -> JSObject -> JS t JSObject
getElementsByTagName tag = invoke "getElementsByTagName" (tag)

-- | Create a attribute DOM node with the given name.
--   For use with 'document'.
createAttribute :: JSString -- ^ The name of the new attribute.
                -> JSObject -> JS t JSObject
createAttribute a = invoke "createAttribute" (a)

-- | Create a element DOM node with the given tag name.
--   For use with 'document'.
createElement :: JSString -- ^ The tag name of the new element.
              -> JSObject -> JS t JSObject
createElement e = invoke "createElement" (e)

-- | Create a text DOM node with the given string as text.
--   For use with 'document'.
createTextNode :: JSString -- ^ The text of the new text node.
               -> JSObject -> JS t JSObject
createTextNode text = invoke "createTextNode" (text)

-- | Opens the document for writing.
--   For use with 'document'.
open :: JSObject -> JS t ()
open = invoke "open" ()

-- | Closes the document after writing.
--   For use with 'document'.
close :: JSObject -> JS t ()
close = invoke "close" ()

-- | Writes something into the document.
--   For use with 'document'.
write :: JSString -> JSObject -> JS t ()
write str = invoke "write" (str)

-- | Write something into the document and appends a new line.
--   For use with 'document'.
writeln :: JSString -> JSObject -> JS t ()
writeln str = invoke "writeln" (str)

-- | Sets the value of the cookie.
--   For use with 'document'.
setCookie :: JSString -> JSObject -> JS t ()
setCookie c = "cookie" := c

-- | Returns the value of the cookie.
--   For use with 'document'.
cookie :: JSSelector JSString
cookie = attr "cookie"

-- | Returns the referrer of the document.
--   For use with 'document'.
referrer :: JSSelector JSString
referrer = attr "referrer"

-- | Sets the title of the document.
--   For use with 'document'.
setTitle :: JSString -> JSObject -> JS t ()
setTitle t = "title" := t

-- | Returns the title of the document.
--   For use with 'document'.
title :: JSSelector JSString
title = attr "title"

-- | Returns the complete URL of the document.
--   For use with 'document'.
url :: JSSelector JSString
url = attr "URL"

-- | Returns the src of a DOM image object.
src :: JSSelector JSString
src = attr "src"

-- -----------------------------------------------------------------------
-- Console API
-- -----------------------------------------------------------------------

-- | The type of the debugging console object.
--   See:
--   <https://developers.google.com/chrome-developer-tools/docs/console-api>,
--   <https://developer.mozilla.org/en-US/docs/DOM/console>,
--   <http://msdn.microsoft.com/en-us/library/windows/apps/hh696634.aspx>;
data JSConsole = JSConsole JSObject

-- | Show the Javascript.
instance Show JSConsole where
  show (JSConsole o) = show o

-- | First-class values in Javascript.
instance Sunroof JSConsole where
        box = JSConsole . box
        unbox (JSConsole e) = unbox e

-- | Associated boolean is 'JSBool'.
type instance BooleanOf JSConsole = JSBool

-- | Can be returned in branches.
instance IfB JSConsole where
  ifB = jsIfB

-- | Reference equality, not value equality.
instance EqB JSConsole where
  (JSConsole a) ==* (JSConsole b) = a ==* b

-- | The console object.
console :: JSConsole
console = JSConsole (object "console")

-- | Log the given message.
log :: (SunroofArgument a) => a -> JSConsole -> JS t ()
log a (JSConsole o) = o # invoke "log" a

-- | Send a debug level message to the console.
debug :: (SunroofArgument a) => a -> JSConsole -> JS t ()
debug a (JSConsole o) = o # invoke "debug" a

-- | Send a info message to the console.
info :: (SunroofArgument a) => a -> JSConsole -> JS t ()
info a (JSConsole o) = o # invoke "info" a

-- | Send a warning message to the console.
warn :: (SunroofArgument a) => a -> JSConsole -> JS t ()
warn a (JSConsole o) = o # invoke "warn" a

-- | Send an error message to the console.
error :: (SunroofArgument a) => a -> JSConsole -> JS t ()
error a (JSConsole o) = o # invoke "error" a