{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedLabels #-}

module Web.Page.Render
  ( renderPage
  , renderPageWith
  , renderPageHtmlWith
  , renderPageAsText
  , renderPageToFile
  , renderPageHtmlToFile
  , renderPageTextWith
  ) where

import Control.Applicative
import Control.Lens
import Control.Monad
import Data.Monoid
import Data.Text (Text)
import Data.Text.IO (writeFile)
import Data.Text.Lazy (fromStrict, toStrict)
import Data.Traversable
import Lucid
import Prelude hiding (writeFile)
import Web.Page.Html
import Web.Page.Types
import qualified Data.Text as Text
import qualified Lucid.Svg as Svg
import qualified Web.Page.Css as Css
import qualified Web.Page.Js as Js

renderPage :: Page -> Html ()
renderPage p =
  (\(_, _, x) -> x) $ renderPageWith defaultPageConfig p

renderPageHtmlWith :: PageConfig -> Page -> Html ()
renderPageHtmlWith pc p =
  (\(_, _, x) -> x) $ renderPageWith pc p

renderPageWith :: PageConfig -> Page -> (Text, Text, Html ())
renderPageWith pc p =
  case pc ^. #concerns of
    Inline -> (mempty, mempty, h)
    Separated -> (css, js, h)
  where
    h =
      case pc ^. #structure of
        HeaderBody ->
          doctype_ <>
          with html_ [lang_ "en"]
          (head_
          (mconcat
            [ meta_ [charset_ "utf-8"]
            , cssInline
            , mconcat libsCss'
            , p ^. #htmlHeader
            ]) <>
          body_
          (mconcat
            [ p ^. #htmlBody
            , mconcat libsJs'
            , jsInline
            ]))
        Headless ->
          mconcat
            [ doctype_
            , meta_ [charset_ "utf-8"]
            , mconcat libsCss'
            , cssInline
            , p ^. #htmlHeader
            , p ^. #htmlBody
            , mconcat libsJs'
            , jsInline
            ]
        Snippet ->
          mconcat
            [ mconcat libsCss'
            , cssInline
            , p ^. #htmlHeader
            , p ^. #htmlBody
            , mconcat libsJs'
            , jsInline
            ]
        Svg ->
          Svg.doctype_ <>
          svg_
            (Svg.defs_ $
             mconcat
               [ mconcat libsCss'
               , cssInline
               , p ^. #htmlBody
               , mconcat libsJs'
               , jsInline
               ])
    css = rendercss (p ^. #cssBody)
    js = renderjs (p ^. #jsGlobal <> Js.onLoad (p ^. #jsOnLoad))
    (renderjs, rendercss) = renderers $ pc ^. #pageRender
    cssInline
      | pc ^. #concerns == Separated || css == mempty = mempty
      | otherwise = style_ [type_ "text/css"] css
    jsInline
      | pc ^. #concerns == Separated || js == mempty = mempty
      | otherwise = script_ mempty js
    libsCss' =
      case pc ^. #concerns of
        Inline -> p ^. #libsCss
        Separated ->
          p ^. #libsCss <>
          [libCss (Text.pack $ pc ^. #filenames . #css)]
    libsJs' =
      case pc ^. #concerns of
        Inline -> p ^. #libsJs
        Separated ->
          p ^. #libsJs <>
          [libJs (Text.pack $ pc ^. #filenames . #js)]

renderPageToFile :: FilePath -> PageConfig -> Page -> IO ()
renderPageToFile dir pc page =
  void $ sequenceA $ liftA2 writeFile' (pc ^. #filenames) (renderPageAsText pc page)
  where
    writeFile' fp s = unless (s == mempty) (writeFile (dir <> "/" <> fp) s)

renderPageHtmlToFile :: FilePath -> PageConfig -> Page -> IO ()
renderPageHtmlToFile file pc page =
  writeFile file (toText $ renderPageHtmlWith pc page)

renderPageAsText :: PageConfig -> Page -> Concerns Text
renderPageAsText pc p =
  case pc ^. #concerns of
    Inline -> Concerns mempty mempty htmlt
    Separated -> Concerns css js htmlt
  where
    htmlt = toText h
    (css, js, h) = renderPageWith pc p

rendererJs :: PageRender -> Js.PageJs -> Text
rendererJs _ (Js.PageJsText js) = js
rendererJs Minified (Js.PageJs js) = toStrict . Js.renderToText . Js.minifyJS . Js.unJS $ js
rendererJs Pretty (Js.PageJs js) = toStrict . Js.renderToText . Js.unJS $ js

rendererCss :: PageRender -> Css.PageCss -> Text
rendererCss Minified (Css.PageCss css) = toStrict $ Css.renderWith Css.compact [] css
rendererCss Pretty (Css.PageCss css) = toStrict $ Css.render css
rendererCss _ (Css.PageCssText css) = css

renderers :: PageRender -> (Js.PageJs -> Text, Css.PageCss -> Text)
renderers p = (rendererJs p, rendererCss p)

renderPageTextWith :: PageConfig -> PageText -> (Text, Text, Html ())
renderPageTextWith pc p =
  case pc ^. #concerns of
    Inline -> (mempty, mempty, h)
    Separated -> (css, js, h)
  where
    h =
      case pc ^. #structure of
        HeaderBody ->
          doctype_ <>
          with html_ [lang_ "en"]
          (head_
          (mconcat
            [ meta_ [charset_ "utf-8"]
            , toHtmlRaw cssInline
            , mconcat (toHtmlRaw <$> libsCss')
            , toHtmlRaw $ p ^. #htmlHeaderText
            ]) <>
          body_
          (mconcat
            [ toHtmlRaw $ p ^. #htmlBodyText
            , mconcat (toHtmlRaw <$> libsJs')
            , toHtmlRaw jsInline
            ]))
        Headless ->
          mconcat
            [ doctype_
            , meta_ [charset_ "utf-8"]
            , mconcat (toHtmlRaw <$> libsCss')
            , toHtmlRaw cssInline
            , toHtmlRaw $ p ^. #htmlHeaderText
            , toHtmlRaw $ p ^. #htmlBodyText
            , mconcat (toHtmlRaw <$> libsJs')
            , toHtmlRaw jsInline
            ]
        Snippet ->
          mconcat
            [ mconcat (toHtmlRaw <$> libsCss')
            , toHtmlRaw cssInline
            , toHtmlRaw $ p ^. #htmlHeaderText
            , toHtmlRaw $ p ^. #htmlBodyText
            , mconcat (toHtmlRaw <$> libsJs')
            , toHtmlRaw jsInline
            ]
        Svg ->
          Svg.doctype_ <>
          svg_
            (Svg.defs_ $
             mconcat
               [ mconcat (toHtmlRaw <$> libsCss')
               , toHtmlRaw cssInline
               , toHtmlRaw $ p ^. #htmlBodyText
               , mconcat (toHtmlRaw <$> libsJs')
               , toHtmlRaw jsInline
               ])
    css = p ^. #cssBodyText
    js = rendererJs Pretty (Js.PageJsText $ p ^. #jsGlobalText) <>
         rendererJs Pretty (Js.onLoad (Js.PageJsText $ p ^. #jsOnLoadText))
    cssInline
      | pc ^. #concerns == Separated || css == mempty = mempty
      | otherwise = renderText $ style_ [type_ "text/css"] css
    jsInline
      | pc ^. #concerns == Separated || js == mempty = mempty
      | otherwise = renderText $ script_ mempty js
    libsCss' =
      case pc ^. #concerns of
        Inline -> fromStrict <$> p ^. #libsCssText
        Separated ->
          (fromStrict <$> p ^. #libsCssText) <>
          [renderText $ libCss (Text.pack $ pc ^. #filenames . #css)]
    libsJs' =
      case pc ^. #concerns of
        Inline -> fromStrict <$> p ^. #libsJsText
        Separated ->
          (fromStrict <$> p ^. #libsJsText) <>
          [renderText $ libJs (Text.pack $ pc ^. #filenames . #js)]