-- |
-- Module:     WebWire.Render
-- Copyright:  (c) 2011 Ertugrul Soeylemez
-- License:    BSD3
-- Maintainer: Ertugrul Soeylemez <es@ertes.de>
--
-- Rendering module.

module WebWire.Render
    ( -- * Renderable types
      Renderable(..),
      render,
      respondOutput,

      -- * Default widget
      addWidget,
      renderDef
    )
    where

import qualified Data.ByteString.Char8 as BC
import Blaze.ByteString.Builder
import Blaze.ByteString.Builder.Char.Utf8
import Control.Arrow
import Control.Monad.Trans.State
import Data.ByteString (ByteString)
import Data.Monoid
import Data.Text (Text)
import FRP.NetWire
import Network.HTTP.Types
import Network.Wai
import Text.Blaze
import Text.Blaze.Renderer.Utf8
import Text.Cassius
import Text.Julius
import WebWire.Tools
import WebWire.Types
import WebWire.Widget


-- | This class represents renderable types.  Each renderable type can
-- support rendering to several target representations like HTML, JSON,
-- XML, etc.
--
-- For simple applications the predefined instances should suffice.

class Renderable src where
    -- | Render the input value as the most appropriate output type.
    toWebOutput :: WebWire site src WebOutput
    toWebOutput =
        toWebOutputHtml <+>
        toWebOutputPlain <+>
        toWebOutputGen

    -- | Render the input value as some appropriate output type.
    toWebOutputGen :: WebWire site src WebOutput
    toWebOutputGen = notFound

    -- | Render the input value as HTML.
    toWebOutputHtml :: WebWire site src WebOutput
    toWebOutputHtml = notFound

    -- | Render the input value as plain text.
    toWebOutputPlain :: WebWire site src WebOutput
    toWebOutputPlain = notFound


-- | 'ByteString' strings render to fixed length plain text.  Note that
-- UTF-8 encoding is assumed.

instance Renderable ByteString where
    toWebOutputPlain = arr (TextOutput True . fromByteString)


-- | 'Css' values render to a CSS stylesheet.

instance Renderable Css where
    toWebOutputGen = arr (GenOutput False "text/css" . fromLazyText . renderCss)


-- | HTML is rendered as text/html with an assumed character set of
-- UTF-8.

instance Renderable Html where
    toWebOutputHtml = arr (HtmlOutput False)


-- | 'Javascript' values render to a JavaScript resource.

instance Renderable Javascript where
    toWebOutputGen = arr (GenOutput False "text/javascript" . fromLazyText . renderJavascript)


-- | Strings render to variable length plain text.

instance Renderable String where
    toWebOutputPlain = arr (TextOutput False . fromString)


-- | 'Text' strings render to fixed length plain text.

instance Renderable Text where
    toWebOutputPlain = arr (TextOutput True . fromText)


-- | Widgets render to HTML in the way specified in "WebWire.Widget".

instance Renderable Widget where
    toWebOutputHtml = arr (HtmlOutput False . toHtml)


-- | Add the input widget to the current default widget.

addWidget :: WebWire site Widget ()
addWidget =
    proc w ->
        execute -< modify $ \cfg ->
            let w' = wcWidget cfg
            in cfg { wcWidget = mappend w' w }


-- | Render the given renderable value as a response to the user.

render :: Renderable src => WebWire site src Response
render = toWebOutput >>> respondOutput


-- | Render the default widget.

renderDef :: WebWire site a Response
renderDef =
    proc _ -> do
        wg <- execute -< gets wcWidget
        render -< wg


-- | Render the given output as a response to the user.

respondOutput :: WebWire site WebOutput Response
respondOutput =
    proc outp ->
        case outp of
          GenOutput withLen ctype ob ->
              identity -< builder withLen ctype ob
          HtmlOutput withLen html ->
              let ctype = "text/html; charset=UTF-8" in
              identity -< builder withLen ctype (renderHtmlBuilder html)
          TextOutput withLen ob ->
              let ctype = "text/plain; charset=UTF-8" in
              identity -< builder withLen ctype ob

    where
    builder :: Bool -> Ascii -> Builder -> Response
    builder False ctype ob =
        let hs = [headerContentType ctype] in
        ResponseBuilder statusOK hs ob
    builder True ctype ob =
        let ostr = toByteString ob
            olen = BC.length ostr
            hs   =
                headerContentType ctype :
                headerContentLength (BC.pack (show olen)) : []
        in ResponseBuilder statusOK hs (fromByteString ostr)