{-# OPTIONS_GHC -fno-warn-orphans #-}

{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE TypeApplications     #-}
{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE ConstraintKinds      #-}
{-# LANGUAGE TypeOperators        #-}
{-# LANGUAGE DataKinds            #-}

module Html
  ( module Html.Type
  , module Html.Convert
  , module Html.Element
  , renderString
  , renderText
  , renderByteString
  , renderBuilder
  , compactHTML
  , renderCompactString
  , renderCompactText
  , renderCompactByteString
  , renderCompactBuilder
  , Put(..)
  , V(..)
  ) where

import Html.Reify
import Html.Convert
import Html.Element
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 mempty

-- | 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 @ (Reverse (Variables a))))

-- | Show instances to faciliate ghci development.
instance Document ((a :@: b) c) => Show ((a :@: b) c) where show = renderString
instance Document (a := b)      => Show (a := b)      where show = renderString
instance Document (a # b)       => Show (a # b)       where show = renderString