content: {-# LANGUAGE FlexibleInstances #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE QuasiQuotes #-}
  {-# LANGUAGE DataKinds #-}
  {-# LANGUAGE RecordWildCards #-}
  {-# LANGUAGE PolyKinds #-}
  {-# LANGUAGE TypeFamilies #-}
  {-# LANGUAGE ConstraintKinds #-}
  {-# LANGUAGE TypeOperators #-}
  {-# OPTIONS -fno-warn-orphans #-}
  module Dixi.Markup where
  
  import Control.Lens
  import Data.Foldable            (toList)
  import Data.Maybe               (fromMaybe)
  import Data.Monoid
  import Data.Patch               (Hunks, HunkStatus(..))
  import Data.Proxy
  import Data.Text                (Text)
  import Servant.API
  import Servant.HTML.Blaze
  import Text.Blaze
  import Text.Blaze.Renderer.Utf8 (renderMarkup)
  import Text.Hamlet              (shamlet, Html)
  import Text.Heredoc
  import Text.Lucius
  import Text.Pandoc.Error
  
  import qualified Data.Text            as T
  import qualified Data.Text.Lazy       as L
  import qualified Data.ByteString.Lazy as B
  
  import Dixi.API
  import Dixi.Common
  import Dixi.Config
  import Dixi.Page
  import Dixi.Hamlet
  import Dixi.PatchUtils
  
  link :: (IsElem endpoint Dixi, HasLink endpoint) => Proxy endpoint -> MkLink endpoint
  link = safeLink dixi
  
  
  
  renderTitle :: Text -> Text
  renderTitle = T.pack . map (\c -> if c == '_' then ' ' else c) .  T.unpack
  
  prettyUrl :: Proxy (  Capture "page" Key :> "history"
                     :> Capture "version" Version
                     :> Get '[HTML, JSON] PrettyPage
                     )
  prettyUrl =  Proxy
  
  latestUrl :: Proxy (Capture "page" Key :> Get '[HTML, JSON] PrettyPage)
  latestUrl =  Proxy
  
  rawUrl :: Proxy (  Capture "page" Key :> "history"
                  :> Capture "version" Version
                  :> "raw" :> Get '[HTML, JSON] RawPage
                  )
  rawUrl =  Proxy
  
  
  amendUrl :: Proxy (  Capture "page" Key :> "history"
                    :> Capture "version" Version
                    :> ReqBody '[FormUrlEncoded, JSON] NewBody
                    :> Post '[HTML, JSON] PrettyPage
                    )
  amendUrl =  Proxy
  
  diffUrl :: Proxy (Capture "page" Key :> "history" :> "diff" :> Get '[HTML, JSON] DiffPage)
  diffUrl =  Proxy
  historyUrl :: Proxy (Capture "page" Key :> "history" :> Get '[HTML, JSON] History)
  historyUrl =  Proxy
  revertUrl :: Proxy (Capture "page" Key :> "history" :> "revert" :>  ReqBody '[FormUrlEncoded, JSON] RevReq :> Post '[HTML, JSON] PrettyPage)
  revertUrl =  Proxy
  
  outerMatter :: Html -> Text -> Html -> Html
  outerMatter ss title bod = [shamlet|
    $doctype 5
    <html>
      <head>
        <link href="http://fonts.googleapis.com/css?family=PT+Serif:400,700" rel="stylesheet" type="text/css">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
        #{ss}
        <title> #{title}
      <body>
        <div .header> #{title}
        #{bod}
  |]
  
  
  
  unlast :: a -> Last a -> a
  unlast d (Last x) = fromMaybe d x
  
  guardText :: Text -> Text -> Text
  guardText x y | y == ""   = x
                | otherwise = y
  
  
  dixiError :: Html -> DixiError -> B.ByteString
  dixiError header (VersionNotFound k v) = renderMarkup $ outerMatter header (renderTitle k)
              [shamlet|
                #{pageHeader k "Error"}
                <div .body>
                  <h1> Error
                  <span.error> Version #{v} not found!
              |]
  dixiError header (PatchNotApplicable k) = renderMarkup $ outerMatter header (renderTitle k)
              [shamlet|
                #{pageHeader k "Error"}
                <div .body>
                  <h1> Internal Error
                  <span.error> Patch not Applicable!
              |]
  
  instance ToMarkup URI where
    toMarkup u = [shamlet|#{show u}|]
  
  instance ToMarkup PatchSummary where
    toMarkup (i,d,r) = [hml|
      <span .fa .fa-plus-square-o .addition-sum> #{show i}
      <span .fa .fa-minus-square-o .subtraction-sum> #{show d}
      <span .fa .fa-pencil-square-o .replacement-sum> #{show r}
  |]
  
  
  instance ToMarkup DiffPage where
    toMarkup (DP (Renders {..}) k v1 v2 p) = outerMatter headerBlock (renderTitle k) $ [shamlet| 
      #{pageHeader k vString}
      <div .body>
        <div>
          #{renderHunks d}
        <br>
        <hr>
        <form method="POST" action="/#{link revertUrl k}">
          <input type="hidden" name="from" value="#{show v1}">
          <input type="hidden" name="to" value="#{show v2}">
          <input type="text" name="comment" value="revert #{show v1} - #{show v2}">
          <button type="submit">
            <span .fa .fa-undo>  Revert
      |]
      where
        d = p ^. body
        renderHunks :: Hunks Char -> Html
        renderHunks ps = [hml|
          $forall (x, s) <- ps
            <span class="hunk #{styleFor s}">#{toList x}
          |]
        styleFor :: HunkStatus -> String
        styleFor Inserted  = "hunk-inserted"
        styleFor Deleted   = "hunk-deleted"
        styleFor Replaced  = "hunk-replaced"
        styleFor Unchanged = "hunk-unchanged"
        vString :: Text
        vString = "diff " <> T.pack (show v1) <> " - " <> T.pack (show v2)
  
  instance ToMarkup History where
    toMarkup (H (Renders {..}) k []) = outerMatter headerBlock (renderTitle k) $ pageHeader k "history"
    toMarkup (H (Renders {..}) k ps) = outerMatter headerBlock (renderTitle k) $ [shamlet| 
      #{pageHeader k "history"}
      <div .body>
       <form method="GET" action="/#{link diffUrl k}">
         <table .history>
             <tr>
               <th .histh-version> Version
               <th .histh-fromto> From/To
               <th .histh-changes> Changes
               <th .histh-comment> Comment
               <th .histh-comment> Time
           $forall (v, p) <- ps'
             <tr>
              <td .hist-version>
               #{show v}.
              <td .hist-fromto>
               <input type="radio" checked style="position:relative; top:1em;" name="from" value="#{show (v - 1)}">
               <input type="radio" checked name="to" value="#{show v}">
              <td>
               #{(p ^. body)}
              <td>
               <a .histlink href="/#{link prettyUrl k v}">#{guardText "no comment" (unlast "no comment" (p ^. comment))}
              <td>
               <span .timestamp>#{renderTime (p ^. time)}
           <tr>
             <td> &nbsp;
           <tr>
             <td>
             <td>
               <button type="submit">
                 <span .fa .fa-files-o>
                  \  Diff
             <td>
             <td>
               <small> (to revert a change, view the diff first)
      |]
     where ps' = reverse $ zip [1..] ps
  
  versionHeader :: Key -> Version -> Text -> Html
  versionHeader k v com = [shamlet| 
             <div .subtitle>
               version #{v} (#{com'})
             <div .toolbar>
               <a href="/#{link rawUrl k v}" .fa .fa-edit> edit
               <a href="/#{link prettyUrl k v}" .fa .fa-eye> view
               <a href="/#{link historyUrl k}" .fa .fa-history> history
               <a href="/#{link latestUrl k}" .fa .fa-fast-forward> latest
    |]
   where com' = if com == "" then "no comment" else com
  pageHeader :: Key -> Text -> Html
  pageHeader k com = [shamlet| 
             <div .subtitle>
               #{com}
             <div .toolbar>
               <a href="/#{link historyUrl k}" .fa .fa-history> history
               <a href="/#{link latestUrl k}" .fa .fa-fast-forward> latest
    |]
  
  instance ToMarkup PandocError where
    toMarkup (ParseFailure s)  = [shamlet| <b> Parse Failure: </b> #{s}|]
    toMarkup (ParsecError _ e) = [shamlet| <b> Parse Error: </b> #{show e} |]
  
  writePandocError :: PandocError -> Html
  writePandocError err = [shamlet|#{err}|]
  
  instance ToMarkup PrettyPage where
    toMarkup (PP (Renders {..}) k v p)
      = let
         com = p ^. comment . traverse
         tim = renderTime $ p ^. time
      in outerMatter headerBlock (renderTitle k)
           [shamlet|
             #{versionHeader k v com}
             <div .body>
               #{p ^. body}
             <div .timestamp> This version was last edited at #{tim}
           |]
  
  instance ToMarkup RawPage where
    toMarkup (RP (Renders {..}) k v p )
      = let
         com = p ^. comment . traverse
         bod = p ^. body
      in outerMatter headerBlock (renderTitle k)
           [shamlet|
             #{versionHeader k v com}
             <div .body>
              <form method="POST" action="/#{link amendUrl k v}">
                <textarea name="content" cols=80 rows=24 style="font-family:monospace">#{bod}
                <br>
                <input type="text" name="comment" value="no comment">
                <input type="submit">
           |]
  
  defaultStylesheet :: L.Text
  Right defaultStylesheet = luciusRT [here|
    div.body {
      margin: 1em;
    }
    table.history {
      border: 0px;
      td {
        border: 0px;
        button {
          width: 100%;
          padding: 4px;
        }
      }
      tr {
        border: 0px;
      }
    }
    .hist-version {
      text-align:right;
    }
    .histh-comment {
      text-align:left;
    }
    .histh-version {
      padding-right:5px;
    }
    .hist-fromto {
      text-align:center;
    }
    body {
      font-family: PT Serif, Palatino, Georgia, Times, serif;
      margin: 0px;
    }
    .toolbar {
      background: #BBBBAA;
      border-top: 1px solid #888877;
      border-bottom: 1px solid #EEEEDD;
      a:hover {
        background: #F1F1D9;
        border: 1px outset #F1F1D9;
      }
      a:active {
        background: #F1F1D9;
        border: 1px inset #F1F1D9;
      }
      a {
        background: #DCDCCB;
        border: 1px outset #F1F1D9;
        text-decoration: none;
        color: black;
        padding: 2px;
        margin-top: 2px;
        margin-bottom: 2px;
        margin-left: 2px;
      }
    }
    .header {
      background: #FFFFDD;
      font-size: 1.5em;
      font-weight: bold;
      padding-left: 0.5em;
      padding-top: 0.5em;
      padding-bottom: 0.5em;
    }
    .subtitle {
      float:right;
      font-size: 0.8em;
      margin-right: 0.5em;
      color: gray;
      position: relative;
      top: -2.5em;
    }
    .addition-sum {
      background: #B5F386;
      padding: 3px;
      border-radius: 6px 0px 0px 6px;
      margin-top:1px;
      margin-bottom:1px;
    }
    .subtraction-sum {
      background: #EC8160;
      padding: 3px;
      margin-top:1px;
      margin-bottom:1px;
    }
    .replacement-sum {
      background: #F3E686;
      padding: 3px;
      border-radius: 0px 6px 6px 0px;
      margin-top:1px;
      margin-bottom:1px;
    }
    .hunk {
      white-space: pre;
      font-family:monospace;
      border-radius: 4px;
    }
    .hunk-inserted {
      background: #B5F386;
    }
    .hunk-deleted {
      background: #EC8160;
      text-decoration: line-through;;
    }
    .hunk-replaced {
      background: #F3E686;
    }
    .timestamp {
      color: #444444;
      font-size: small;
    }
    div.timestamp {
      margin-left: 0.5em;
      margin-top: 2em;
    }
  |] [] 
  