content: {-# OPTIONS_GHC -fno-warn-orphans #-}
  
  {-# LANGUAGE UndecidableInstances #-}
  {-# LANGUAGE ScopedTypeVariables  #-}
  {-# LANGUAGE FlexibleInstances    #-}
  {-# LANGUAGE TypeApplications     #-}
  {-# LANGUAGE FlexibleContexts     #-}
  {-# LANGUAGE ConstraintKinds      #-}
  {-# LANGUAGE TypeOperators        #-}
  {-# LANGUAGE DataKinds            #-}
  {-# LANGUAGE PolyKinds            #-}
  
  module Html.Render
    ( renderString
    , renderText
    , renderByteString
    , renderBuilder
    , compactHTML
    , renderCompactString
    , renderCompactText
    , renderCompactByteString
    , renderCompactBuilder
    , Put(..)
    , V(..)
    ) where
  
  import Html.Reify
  import Html.Convert
  import Html.Type
  import Html.Type.Internal
  
  import Control.Arrow
  import GHC.Exts
  import Data.Either
  import Data.List
  import Data.ByteString.Builder
  import Data.Maybe
  
  import qualified Data.ByteString               as B
  import qualified Data.ByteString.Lazy          as BL
  import qualified Data.ByteString.Builder.Extra as BE
  import qualified Data.Text.Lazy                as T
  import qualified Data.Text.Lazy.Encoding       as T
  
  -- | Render a html document to a Builder.
  renderBuilder :: Document a => a -> Builder
  renderBuilder = unConv . (render @'False . (T :: a -> T (ToList a) a))
  
  -- | Render a html document to a String.
  renderString :: Document a => a -> String
  renderString = T.unpack . renderText
  
  -- | Render a html document to a lazy Text.
  renderText :: Document a => a -> T.Text
  renderText = T.decodeUtf8 . renderByteString
  
  -- | Render a html document to a lazy ByteString.
  renderByteString :: Document a => a -> BL.ByteString
  renderByteString = BE.toLazyByteStringWith
    ( BE.untrimmedStrategy
      1024
      BE.smallChunkSize
    ) BL.empty . renderBuilder
  
  renderCompactHTML :: Retrievable a => (Builder -> f) -> CompactHTML a -> Retrieve f a
  renderCompactHTML = retrieve id
  
  -- | Render a compacted html document to a Builder.
  renderCompactBuilder :: Retrievable a => CompactHTML a -> Retrieve Builder a
  renderCompactBuilder = renderCompactHTML id
  
  -- | Render a compacted html document to a lazy ByteString.
  renderCompactByteString :: Retrievable a => CompactHTML a -> Retrieve BL.ByteString a
  renderCompactByteString = renderCompactHTML toLazyBS
  
  -- | Render a compacted html document to a lazy Text.
  renderCompactText :: Retrievable a => CompactHTML a -> Retrieve T.Text a
  renderCompactText = renderCompactHTML (T.decodeUtf8 . toLazyBS)
  
  -- | Render a compacted html document to a String.
  renderCompactString :: Retrievable a => CompactHTML a -> Retrieve String a
  renderCompactString = renderCompactHTML (T.unpack . T.decodeUtf8 . toLazyBS)
  
  toLazyBS :: Builder -> BL.ByteString
  toLazyBS = BE.toLazyByteStringWith (BE.untrimmedStrategy 1024 BE.smallChunkSize) BL.empty
  
  -- | Compact a html document.
  compactHTML :: Compactable a => a -> CompactHTML (Variables a)
  compactHTML html
    = uncurry MkCompactHTML
    . concatEithers
    . toList
    . render @'True
    $ (T :: a -> T (ToList a) a) html
    where
      concatEithers :: [Either Converted String] -> (B.ByteString, [(Int, B.ByteString)])
      concatEithers = (f *** go) . span isLeft
        where go (Right r1:xs) = let (ls,rs) = span isLeft xs
                                 in (indexVar html r1, f ls) : go rs
              go _ = []
              f = BL.toStrict . toLazyByteString . unConv . mconcat . lefts
      indexVar :: forall a. Compactable a => a -> String -> Int
      indexVar _ s = fromJust (elemIndex s (showTypeList @(Variables a)))
  
  -- | Show instances to faciliate ghci development.
  instance Document (a # b) => Show (a # b) where show = renderString
  
  instance Document (a :@ b) => Show (a :@ b) where show = renderString
  instance Document (a :> b) => Show (a :> b) where show = renderString
  instance Document (Attribute a global value) => Show (Attribute a global value) where show = renderString
  instance Document (Element name categories contentModel contentAttributes) => Show (Element name categories contentModel contentAttributes) where show = renderString
  