{-# LANGUAGE BangPatterns               #-}
{-# LANGUAGE ConstraintKinds            #-}
{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE FlexibleInstances          #-}
{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses      #-}
{-# LANGUAGE OverloadedLabels           #-}
{-# LANGUAGE OverloadedLists            #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE RankNTypes                 #-}
{-# LANGUAGE TypeFamilies               #-}
{-# LANGUAGE TypeFamilyDependencies     #-}
{-# LANGUAGE TypeOperators              #-}
-- | A 'Free' monad-based HTML markup monad. Unlike the writer-like monad in
-- "Text.Html.Nice.Writer", sequencing bits of markup together results in them
-- being nested, rather than concatenated.
--
-- There is no kitchen-sink of HTML5 elements provided. Use OverloadedLabels
-- instead: @ #div :: Markup n () @ and @ #div :: MakeNode n () @.
--
-- Since the monad and applicative of 'Markup' here nests rather than
-- concatenates, the function 'nodes' is provided to put a list of nodes
-- in sequence. You can use OverloadedLists for convenient syntax here.
--
-- Example Markup:
--
-- @
-- #html >>
-- [ #head >> #title >> "Title goes here"
-- , #body >>
--   [ #h1 >> "heading goes here"
--   , #p >> "i am a paragraph below the heading"
--   , do i <- branch [0..100]
--        builder (decimal i)
--   ]
-- ]
-- @
--
module Text.Html.Nice.FreeMonad
  ( -- * Markup
    Markup
  , FastMarkup
  , runMarkup
    -- * Compiling
  , compile
    -- * Rendering
    -- ** Rendering through some monad
  , renderM
  , renderMs
    -- ** Pure rendering
  , render
  , Identity (..)
    -- * Util
  , TLB.toLazyText
    -- * Special HTML elements
  , doctype
    -- ** Basic node types
  , node
  , Attr (..)
  , attr
  , empty
    -- ** Text types
  , text
  , lazyText
  , builder
  , unescape
  , string
    -- * Dynamic nodes
  , dynamic
  , hole
  , embed
    -- ** Sequential nodes
  , nodes
  , branch
    -- ** Streamed dynamic nodes
  , stream
    -- ** Combinators
  , sub
    -- * Useful exports
    -- ** Useful 'TLB.Builder' functions
  , TLB.decimal
  , TLB.realFloat
  , TLB.fromText
  , TLB.fromString
  , TLB.fromLazyText
    -- ** Text builder
  , TLB.Builder
    -- ** Void type
  , Void
  ) where
import           Control.Monad
import           Control.Monad.Free.Church
import           Data.Bifunctor
import           Data.Default.Class
import           Data.Foldable                    as F
import qualified Data.Functor.Foldable            as F
import           Data.Functor.Identity
import           Data.String                      (IsString (..))
import           Data.Text                        (Text)
import qualified Data.Text.Lazy                   as TL
import qualified Data.Text.Lazy.Builder           as TLB
import qualified Data.Text.Lazy.Builder.Int       as TLB
import qualified Data.Text.Lazy.Builder.RealFloat as TLB
import qualified Data.Vector                      as V
import           Data.Void
import           GHC.Exts                         (IsList (..))
import           GHC.OverloadedLabels             (IsLabel (..))
import           GHC.TypeLits                     (KnownSymbol, symbolVal')
import           Text.Html.Nice.Internal

-- | 'Markup' is a free monad based on the base functor to 'Markup\'F'
--
-- Beware: this is a wacky monad. '>>' does *not* sequence nodes together;
-- instead, it nests them. To lay out nodes sequentially, use 'nodes'.
--
-- = Syntactic sugar
-- 'Markup' supports @OverloadedLabels@, @OverloadedStrings@ and
-- @OverloadedLists@.
--
-- == @OverloadedStrings@
-- @("foo" :: Markup n a) = 'text' "foo"@
--
-- == @OverloadedLists@
-- @([a,b,c] :: Markup n a) = 'nodes' [a,b,c]@
--
-- == @OverloadedLabels@
-- The 'IsLabel' instances give a convenient way to write nodes.
--
-- === Nodes without attributes
-- @ (#foo :: 'Markup' n a) = 'node' "foo" [] @
--
-- === Nodes with attributes
-- @ 'attr' #foo [a,b,c] = 'node' "foo" [a,b,c] @
--
newtype Markup n a = Markup { unMarkup :: F (Markup'F n) a }
  deriving (Functor, Applicative, Monad, MonadFree (Markup'F n))

instance Default (Markup n a) where
  def = empty

instance Monoid (Markup n a) where
  mempty = empty
  mappend a b = nodes [a, b]

instance IsString (Markup n a) where
  fromString = text . fromString

instance IsList (Markup n a) where
  type Item (Markup n a) = Markup n a
  fromList = nodes
  toList _ = error "haha, fooled you, Markup has no toList"

instance (a ~ (), KnownSymbol s) => IsLabel s (Markup n a) where
  fromLabel p = node (fromString (symbolVal' p)) []

newtype MakeNode n a = N ([Attr n] -> Markup n a)

instance (a ~ (), KnownSymbol s) => IsLabel s (MakeNode n a) where
  fromLabel p = N (node (fromString (symbolVal' p)))

instance Bifunctor Markup where
  first f = Markup . hoistF (first f) . unMarkup
  second = fmap

-- | For use with @OverloadedLabels@.
--
-- @ 'attr' #x [a,b,c] = 'node' "x" [a,b,c] @
--
attr :: MakeNode n a -> [Attr n] -> Markup n a
attr (N f) a = f a

runMarkup :: Markup n a -> Markup' n
runMarkup h = runF (unMarkup h) (const Empty) F.embed

-- | Compile a 'Html' for use with 'render' and its friends.
--
-- See also: 'compile_'.
compile :: Markup n a -> FastMarkup n
compile = compile_ . runMarkup

-- | Make a node with some attributes.
node :: Text -> [Attr n] -> Markup n ()
node t v = liftF (NodeF t (V.fromList v) ())

-- | Insert text and escape it
text :: Text -> Markup n a
text t = liftF (TextF DoEscape (StrictT t))

-- | Insert text and escape it
lazyText :: TL.Text -> Markup n a
lazyText t = liftF (TextF DoEscape (LazyT t))

-- | Insert text and escape it
builder :: TLB.Builder -> Markup n a
builder t = liftF (TextF DoEscape (BuilderT t))

-- | Insert text and escape it
string :: String -> Markup n a
string t = liftF (TextF DoEscape (BuilderT (TLB.fromString t)))

-- | Insert text and don't escape it
unescape :: Text -> Markup n a
unescape t = liftF (TextF Don'tEscape (StrictT t))

-- | Insert a dynamic value.
dynamic :: n -> Markup n a
dynamic n = liftF (HoleF DoEscape n)

hole :: Markup (a -> a) t
hole = dynamic id

-- | For each element of a list of branches, generate sequential markup
branch :: [a] -> Markup n a
branch = liftF . ListF

-- | For each element of a list, generate sequential markup
nodes :: [Markup n a] -> Markup n a
nodes = join . liftF . ListF

-- | Empty node. Terminates 'Markup' to this point
empty :: Markup n a
empty = liftF EmptyF

-- | Insert a sub-template.
sub :: Markup n a -> Markup (FastMarkup n) a
sub x = liftF (HoleF Don'tEscape (compile x))

-- | Insert a sub-template.
{-# INLINE embed #-}
embed :: (t -> FastMarkup n) -> Markup (t -> FastMarkup n) a
embed f = dynamic f

{-# INLINE stream #-}
stream :: Foldable f
       => Markup (a -> n) r'
       -> Markup (f a -> FastMarkup n) r
stream m = embed $ \fa -> case F.toList fa of
  []   -> FEmpty
  list -> FStream (ListS list (\a -> fmap ($ a) fm))
    where
      !fm = compile m

doctype :: Markup n a
doctype = liftF DoctypeF