{-# LANGUAGE EmptyDataDecls, ForeignFunctionInterface, JavaScriptFFI,
             OverloadedStrings, DeriveDataTypeable
  #-}

module JavaScript.Web.Canvas ( Context
                             , Canvas
                             , Image
                             , TextAlign(..)
                             , TextBaseline(..)
                             , LineCap(..)
                             , LineJoin(..)
                             , Repeat(..)
                             , Gradient
                             , Pattern
                             , create
                             , unsafeToCanvas
                             , toCanvas
                             , getContext
                             , save
                             , restore
                             , scale
                             , rotate
                             , translate
                             , transform
                             , setTransform
                             , fill
                             , fillRule
                             , stroke
                             , beginPath
                             , closePath
                             , clip
                             , moveTo
                             , lineTo
                             , quadraticCurveTo
                             , bezierCurveTo
                             , arc
                             , arcTo
                             , rect
                             , isPointInPath
                             , fillStyle
                             , strokeStyle
                             , globalAlpha
                             , lineJoin
                             , lineCap
                             , lineWidth
                             , setLineDash
                             , lineDashOffset
                             , miterLimit
                             , fillText
                             , strokeText
                             , font
                             , measureText
                             , textAlign
                             , textBaseline
                             , fillRect
                             , strokeRect
                             , clearRect
                             , drawImage
                             , width
                             , setWidth
                             , height
                             , setHeight
                             ) where

import Prelude hiding (Left, Right)

import Control.Applicative
import Control.Monad

import Data.Data
import Data.Maybe (fromJust)
import Data.Text (Text)
import Data.Typeable

import GHCJS.Foreign
import GHCJS.Marshal
import GHCJS.Types

import           JavaScript.Web.Canvas.Internal

import           JavaScript.Object (Object)
import qualified JavaScript.Object as O
import           JavaScript.Array  (JSArray)
import qualified JavaScript.Array  as A

data TextAlign = Start
               | End
               | Left
               | Right
               | Center
             deriving (Eq, Show, Enum, Data, Typeable)

data TextBaseline = Top
                  | Hanging
                  | Middle
                  | Alphabetic
                  | Ideographic
                  | Bottom
                deriving (Eq, Show, Enum, Data, Typeable)

data LineJoin = LineJoinBevel
              | LineJoinRound
              | LineJoinMiter
            deriving (Eq, Show, Enum)

data LineCap = LineCapButt
             | LineCapRound
             | LineCapSquare deriving (Eq, Show, Enum, Data, Typeable)

data Repeat = Repeat
            | RepeatX
            | RepeatY
            | NoRepeat
            deriving (Eq, Ord, Show, Enum, Data, Typeable)

unsafeToCanvas :: JSVal -> Canvas
unsafeToCanvas r = Canvas r
{-# INLINE unsafeToCanvas #-}

toCanvas :: JSVal -> Maybe Canvas
toCanvas x = error "toCanvas" -- fixme
{-# INLINE toCanvas #-}

create :: Int -> Int -> IO Canvas
create = js_create
{-# INLINE create #-}

getContext :: Canvas -> IO Context
getContext c = js_getContext c
{-# INLINE getContext #-}

save :: Context -> IO ()
save ctx = js_save ctx
{-# INLINE save #-}

restore :: Context -> IO ()
restore = js_restore
{-# INLINE restore #-}

transform :: Double -> Double -> Double -> Double -> Double -> Double -> Context -> IO ()
transform = js_transform
{-# INLINE transform #-}

setTransform :: Double -> Double -> Double -> Double -> Double -> Double -> Context -> IO ()
setTransform = js_setTransform
{-# INLINE setTransform #-}

scale :: Double -> Double -> Context -> IO ()
scale x y ctx = js_scale x y ctx
{-# INLINE scale #-}

translate :: Double -> Double -> Context -> IO ()
translate x y ctx = js_translate x y ctx
{-# INLINE translate #-}

rotate :: Double -> Context -> IO ()
rotate r ctx = js_rotate r ctx
{-# INLINE rotate #-}

fill :: Context -> IO ()
fill ctx = js_fill ctx
{-# INLINE fill #-}

fillRule :: JSString -> Context -> IO ()
fillRule rule ctx = js_fill_rule rule ctx
{-# INLINE fillRule #-}

stroke :: Context -> IO ()
stroke = js_stroke
{-# INLINE stroke #-}

beginPath :: Context -> IO ()
beginPath = js_beginPath
{-# INLINE beginPath #-}

closePath :: Context -> IO ()
closePath = js_closePath
{-# INLINE closePath #-}

clip :: Context -> IO ()
clip = js_clip
{-# INLINE clip #-}

moveTo :: Double -> Double -> Context -> IO ()
moveTo = js_moveTo
{-# INLINE moveTo #-}

lineTo :: Double -> Double -> Context -> IO ()
lineTo = js_lineTo
{-# INLINE lineTo #-}

quadraticCurveTo :: Double -> Double -> Double -> Double -> Context -> IO ()
quadraticCurveTo = js_quadraticCurveTo
{-# INLINE quadraticCurveTo #-}

bezierCurveTo :: Double -> Double -> Double -> Double -> Double -> Double -> Context -> IO ()
bezierCurveTo = js_bezierCurveTo
{-# INLINE bezierCurveTo #-}

arc :: Double -> Double -> Double -> Double -> Double -> Bool -> Context -> IO ()
arc a b c d e bl ctx = js_arc a b c d e bl ctx
{-# INLINE arc #-}

arcTo :: Double -> Double -> Double -> Double -> Double -> Context -> IO ()
arcTo = js_arcTo
{-# INLINE arcTo #-}

rect :: Double -> Double -> Double -> Double -> Context -> IO ()
rect = js_rect
{-# INLINE rect #-}

isPointInPath :: Double -> Double -> Context -> IO ()
isPointInPath = js_isPointInPath
{-# INLINE isPointInPath #-}

fillStyle :: Int -> Int -> Int -> Double -> Context -> IO ()
fillStyle = js_fillStyle
{-# INLINE fillStyle #-}

strokeStyle :: Int -> Int -> Int -> Double -> Context -> IO ()
strokeStyle = js_strokeStyle
{-# INLINE strokeStyle #-}

globalAlpha :: Double -> Context -> IO ()
globalAlpha = js_globalAlpha
{-# INLINE globalAlpha #-}

lineJoin :: LineJoin -> Context -> IO ()
lineJoin LineJoinBevel ctx = js_lineJoin "bevel" ctx
lineJoin LineJoinRound ctx = js_lineJoin "round" ctx
lineJoin LineJoinMiter ctx = js_lineJoin "miter" ctx
{-# INLINE lineJoin #-}

lineCap :: LineCap -> Context -> IO ()
lineCap LineCapButt   ctx = js_lineCap "butt"   ctx
lineCap LineCapRound  ctx = js_lineCap "round"  ctx
lineCap LineCapSquare ctx = js_lineCap "square" ctx
{-# INLINE lineCap #-}

miterLimit :: Double -> Context -> IO ()
miterLimit = js_miterLimit
{-# INLINE miterLimit #-}

-- | pass an array of numbers
setLineDash :: JSArray -> Context -> IO ()
setLineDash arr ctx = js_setLineDash arr ctx
{-# INLINE setLineDash #-}

lineDashOffset :: Double -> Context -> IO ()
lineDashOffset = js_lineDashOffset
{-# INLINE lineDashOffset #-}

textAlign :: TextAlign -> Context -> IO ()
textAlign align ctx = case align of
     Start  -> js_textAlign "start"  ctx
     End    -> js_textAlign "end"    ctx
     Left   -> js_textAlign "left"   ctx
     Right  -> js_textAlign "right"  ctx
     Center -> js_textAlign "center" ctx
{-# INLINE textAlign #-}

textBaseline :: TextBaseline -> Context -> IO ()
textBaseline baseline ctx = case baseline of
     Top         -> js_textBaseline "top"         ctx
     Hanging     -> js_textBaseline "hanging"     ctx
     Middle      -> js_textBaseline "middle"      ctx
     Alphabetic  -> js_textBaseline "alphabetic"  ctx
     Ideographic -> js_textBaseline "ideographic" ctx
     Bottom      -> js_textBaseline "bottom"      ctx
{-# INLINE textBaseline #-}

lineWidth :: Double -> Context -> IO ()
lineWidth = js_lineWidth
{-# INLINE lineWidth #-}

fillText :: JSString -> Double -> Double -> Context -> IO ()
fillText t x y ctx = js_fillText t x y ctx
{-# INLINE fillText #-}

strokeText :: JSString -> Double -> Double -> Context -> IO ()
strokeText t x y ctx = js_strokeText t x y ctx
{-# INLINE strokeText #-}

font :: JSString -> Context -> IO ()
font f ctx = js_font f ctx
{-# INLINE font #-}

measureText :: JSString -> Context -> IO Double
measureText t ctx = js_measureText t ctx
                    >>= O.getProp "width"
                    >>= liftM fromJust . fromJSVal
{-# INLINE measureText #-}

fillRect :: Double -> Double -> Double -> Double -> Context -> IO ()
fillRect = js_fillRect
{-# INLINE fillRect #-}

clearRect :: Double -> Double -> Double -> Double -> Context -> IO ()
clearRect = js_clearRect
{-# INLINE clearRect #-}

strokeRect :: Double -> Double -> Double -> Double -> Context -> IO ()
strokeRect = js_strokeRect
{-# INLINE strokeRect #-}

drawImage :: Image -> Int -> Int -> Int -> Int -> Context -> IO ()
drawImage = js_drawImage
{-# INLINE drawImage #-}

createPattern :: Image -> Repeat -> Context -> IO Pattern
createPattern img Repeat   ctx = js_createPattern img "repeat"    ctx
createPattern img RepeatX  ctx = js_createPattern img "repeat-x"  ctx
createPattern img RepeatY  ctx = js_createPattern img "repeat-y"  ctx
createPattern img NoRepeat ctx = js_createPattern img "no-repeat" ctx
{-# INLINE createPattern #-}

setWidth :: Int -> Canvas -> IO ()
setWidth w c = js_setWidth w c
{-# INLINE setWidth #-}

width :: Canvas -> IO Int
width c = js_width c
{-# INLINE width #-}

setHeight :: Int -> Canvas -> IO ()
setHeight h c = js_setHeight h c
{-# INLINE setHeight #-}

height :: Canvas -> IO Int
height c = js_height c
{-# INLINE height #-}

-- ----------------------------------------------------------------------------

foreign import javascript unsafe "$r = document.createElement('canvas');\
                                 \$r.width = $1;\
                                 \$r.height = $2;"
  js_create :: Int -> Int -> IO Canvas
foreign import javascript unsafe "$1.getContext('2d')"
  js_getContext :: Canvas -> IO Context
foreign import javascript unsafe "$1.save()"
  js_save :: Context -> IO ()
foreign import javascript unsafe "$1.restore()"
  js_restore  :: Context -> IO ()
foreign import javascript unsafe "$7.transform($1,$2,$3,$4,$5,$6)"
  js_transform :: Double -> Double -> Double -> Double -> Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$7.setTransform($1,$2,$3,$4,$5,$6)"
  js_setTransform :: Double -> Double -> Double -> Double -> Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$3.scale($1,$2)"
  js_scale :: Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$3.translate($1,$2)"
  js_translate  :: Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$2.rotate($1)"
  js_rotate :: Double -> Context -> IO ()
foreign import javascript unsafe "$1.fill()"
  js_fill :: Context -> IO ()
foreign import javascript unsafe "$2.fill($1)"
  js_fill_rule  :: JSString -> Context -> IO ()
foreign import javascript unsafe "$1.stroke()"
  js_stroke :: Context -> IO ()
foreign import javascript unsafe "$1.beginPath()"
  js_beginPath :: Context -> IO ()
foreign import javascript unsafe "$1.closePath()"
  js_closePath :: Context -> IO ()
foreign import javascript unsafe "$1.clip()"
  js_clip  :: Context -> IO ()
foreign import javascript unsafe "$3.moveTo($1,$2)"
  js_moveTo :: Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$3.lineTo($1,$2)"
  js_lineTo :: Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$5.quadraticCurveTo($1,$2,$3,$4)"
  js_quadraticCurveTo :: Double -> Double -> Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$7.bezierCurveTo($1,$2,$3,$4,$5,$6)"
  js_bezierCurveTo :: Double -> Double -> Double -> Double -> Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$7.arc($1,$2,$3,$4,$5,$6)"
  js_arc :: Double -> Double -> Double -> Double -> Double -> Bool -> Context -> IO ()
foreign import javascript unsafe "$6.arcTo($1,$2,$3,$4,$5)"
  js_arcTo :: Double -> Double -> Double -> Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$5.rect($1,$2,$3,$4)"
  js_rect :: Double -> Double -> Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$3.isPointInPath($1,$2)"
  js_isPointInPath :: Double -> Double -> Context -> IO ()
foreign import javascript unsafe
  "$5.fillStyle = 'rgba(' + $1 + ',' + $2 + ',' + $3 + ',' + $4 + ')'"
  js_fillStyle :: Int -> Int -> Int -> Double -> Context -> IO ()
foreign import javascript unsafe
  "$5.strokeStyle = 'rgba(' + $1 + ',' + $2 + ',' + $3 + ',' + $4 + ')'"
  js_strokeStyle :: Int -> Int -> Int -> Double -> Context -> IO ()
foreign import javascript unsafe "$2.globalAlpha = $1"
  js_globalAlpha :: Double           -> Context -> IO ()
foreign import javascript unsafe
  "$2.lineJoin = $1"
  js_lineJoin :: JSString -> Context -> IO ()
foreign import javascript unsafe "$2.lineCap = $1"
  js_lineCap :: JSString -> Context -> IO ()
foreign import javascript unsafe "$2.miterLimit = $1"
  js_miterLimit :: Double -> Context -> IO ()
foreign import javascript unsafe "$2.setLineDash($1)"
  js_setLineDash :: JSArray -> Context -> IO ()
foreign import javascript unsafe "$2.lineDashOffset = $1"
  js_lineDashOffset :: Double -> Context -> IO ()
foreign import javascript unsafe "$2.font = $1"
  js_font :: JSString -> Context -> IO ()
foreign import javascript unsafe "$2.textAlign = $1"
  js_textAlign :: JSString -> Context -> IO ()
foreign import javascript unsafe "$2.textBaseline = $1"
  js_textBaseline :: JSString -> Context -> IO ()
foreign import javascript unsafe "$2.lineWidth = $1"
  js_lineWidth :: Double -> Context -> IO ()
foreign import javascript unsafe "$4.fillText($1,$2,$3)"
  js_fillText :: JSString -> Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$4.strokeText($1,$2,$3)"
  js_strokeText :: JSString -> Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$2.measureText($1)"
  js_measureText :: JSString                    -> Context -> IO Object
foreign import javascript unsafe "$5.fillRect($1,$2,$3,$4)"
  js_fillRect :: Double -> Double -> Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$5.clearRect($1,$2,$3,$4)"
  js_clearRect :: Double -> Double -> Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$5.strokeRect($1,$2,$3,$4)"
  js_strokeRect :: Double -> Double -> Double -> Double -> Context -> IO ()
foreign import javascript unsafe "$6.drawImage($1,$2,$3,$4,$5)"
  js_drawImage :: Image -> Int -> Int -> Int -> Int -> Context -> IO ()
foreign import javascript unsafe "$3.createPattern($1,$2)"
  js_createPattern :: Image -> JSString -> Context -> IO Pattern
foreign import javascript unsafe "$1.width"
  js_width :: Canvas -> IO Int
foreign import javascript unsafe "$1.height"
  js_height :: Canvas -> IO Int
foreign import javascript unsafe "$2.width = $1;"
  js_setWidth :: Int -> Canvas -> IO ()
foreign import javascript unsafe "$2.height = $1;"
  js_setHeight :: Int -> Canvas -> IO ()