{-|
Module      : $Header$
Description : Types and conversions
Copyright   : (c) Justus Adam, 2015
License     : LGPL-3
Maintainer  : development@justusadam.com
Stability   : experimental
Portability : POSIX
-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TupleSections         #-}
{-# LANGUAGE UnicodeSyntax         #-}
module Text.Mustache.Types
  (
  -- * Types for the Parser / Template
    AST
  , Template(..)
  , Node(..)
  , DataIdentifier(..)
  -- * Types for the Substitution / Data
  , Value(..)
  -- ** Converting
  , object
  , (~>), (), (~=), (), (~~>), (~), (~~=), (~)
  , ToMustache, toMustache, toTextBlock, mFromJSON
  -- ** Representation
  , Array, Object
  , Context(..)
  ) where


import           Conversion
import           Conversion.Text     ()
import qualified Data.Aeson          as Aeson
import           Data.HashMap.Strict as HM
import           Data.Scientific
import           Data.Text
import qualified Data.Text.Lazy      as LT
import qualified Data.Vector         as V
import           Prelude.Unicode

-- | Abstract syntax tree for a mustache template
type AST = [Node Text]


-- | Basic values composing the AST
data Node α
  = TextBlock α
  | Section DataIdentifier AST
  | InvertedSection DataIdentifier AST
  | Variable Bool DataIdentifier
  | Partial (Maybe α) FilePath
  deriving (Show, Eq)


data DataIdentifier
  = NamedData [Text]
  | Implicit
  deriving (Show, Eq)


type Array  = V.Vector Value
type Object = HM.HashMap Text Value
type Pair   = (Text, Value)


-- | Representation of stateful context for the substitution process
data Context α = Context [α] α


-- | Internal value AST
data Value
  = Object Object
  | Array  Array
  | Number Scientific
  | String Text
  | Lambda (Context Value  AST  AST)
  | Bool   Bool
  | Null


instance Show Value where
  show (Lambda _) = "Lambda Context Value → AST → Either String AST"
  show (Object o) = show o
  show (Array  a) = show a
  show (String s) = show s
  show (Number n) = show n
  show (Bool   b) = show b
  show Null       = "null"


-- | Conversion class
class ToMustache ω where
  toMustache  ω  Value


instance ToMustache Value where
  toMustache = id

instance ToMustache [Char] where
  toMustache = String  pack

instance ToMustache Bool where
  toMustache = Bool

instance ToMustache Char where
  toMustache = String  pack  return

instance ToMustache () where
  toMustache = const Null

instance ToMustache Text where
  toMustache = String

instance ToMustache LT.Text where
  toMustache = String  LT.toStrict

instance ToMustache Scientific where
  toMustache = Number

instance ToMustache ω  ToMustache [ω] where
  toMustache = Array  V.fromList  fmap toMustache

instance ToMustache ω  ToMustache (V.Vector ω) where
  toMustache = Array  fmap toMustache

instance ToMustache ω  ToMustache (HM.HashMap Text ω) where
  toMustache = Object  fmap toMustache

instance ToMustache (Context Value  AST  AST) where
  toMustache = Lambda

instance ToMustache (Context Value  AST  Text) where
  toMustache f = Lambda wrapper
    where wrapper c lAST = return  TextBlock $ f c lAST

instance ToMustache (Context Value  AST  String) where
  toMustache f = toMustache wrapper
    where wrapper c = pack  f c

instance ToMustache (AST  AST) where
  toMustache f = Lambda $ const f

instance ToMustache (AST  Text) where
  toMustache f = Lambda wrapper
    where wrapper _ = (return  TextBlock)  f

instance ToMustache (AST  String) where
  toMustache f = toMustache (pack  f)

instance ToMustache Aeson.Value where
  toMustache (Aeson.Object o) = Object $ fmap toMustache o
  toMustache (Aeson.Array  a) = Array $ fmap toMustache a
  toMustache (Aeson.Number n) = Number n
  toMustache (Aeson.String s) = String s
  toMustache (Aeson.Bool   b) = Bool b
  toMustache Aeson.Null       = Null

instance (ToMustache α, ToMustache β)  ToMustache (α, β) where
  toMustache (a, b) = toMustache [toMustache a, toMustache b]

instance (ToMustache α, ToMustache β, ToMustache γ)
          ToMustache (α, β, γ) where
  toMustache (a, b, c) = toMustache [toMustache a, toMustache b, toMustache c]

instance (ToMustache α, ToMustache β, ToMustache γ, ToMustache δ)
          ToMustache (α, β, γ, δ) where
  toMustache (a, b, c, d) = toMustache
    [ toMustache a
    , toMustache b
    , toMustache c
    , toMustache d
    ]

instance ( ToMustache α
         , ToMustache β
         , ToMustache γ
         , ToMustache δ
         , ToMustache ε
         )  ToMustache (α, β, γ, δ, ε) where
  toMustache (a, b, c, d, e) = toMustache
    [ toMustache a
    , toMustache b
    , toMustache c
    , toMustache d
    , toMustache e
    ]

instance ( ToMustache α
         , ToMustache β
         , ToMustache γ
         , ToMustache δ
         , ToMustache ε
         , ToMustache ζ
         )  ToMustache (α, β, γ, δ, ε, ζ) where
  toMustache (a, b, c, d, e, f) = toMustache
    [ toMustache a
    , toMustache b
    , toMustache c
    , toMustache d
    , toMustache e
    , toMustache f
    ]

instance ( ToMustache α
         , ToMustache β
         , ToMustache γ
         , ToMustache δ
         , ToMustache ε
         , ToMustache ζ
         , ToMustache η
         )  ToMustache (α, β, γ, δ, ε, ζ, η) where
  toMustache (a, b, c, d, e, f, g) = toMustache
    [ toMustache a
    , toMustache b
    , toMustache c
    , toMustache d
    , toMustache e
    , toMustache f
    , toMustache g
    ]

instance ( ToMustache α
         , ToMustache β
         , ToMustache γ
         , ToMustache δ
         , ToMustache ε
         , ToMustache ζ
         , ToMustache η
         , ToMustache θ
         )  ToMustache (α, β, γ, δ, ε, ζ, η, θ) where
  toMustache (a, b, c, d, e, f, g, h) = toMustache
    [ toMustache a
    , toMustache b
    , toMustache c
    , toMustache d
    , toMustache e
    , toMustache f
    , toMustache g
    , toMustache h
    ]

-- | Convenience function for creating Object values.
--
-- This function is supposed to be used in conjuction with the '~>' and '~=' operators.
--
-- ==== __Examples__
--
-- @
--   data Address = Address { ... }
--
--   instance Address ToJSON where
--     ...
--
--   data Person = Person { name :: String, address :: Address }
--
--   instance ToMustache Person where
--     toMustache (Person { name, address }) = object
--       [ "name" ~> name
--       , "address" ~= address
--       ]
-- @
--
-- Here we can see that we can use the '~>' operator for values that have themselves
-- a 'ToMustache' instance, or alternatively if they lack such an instance but provide
-- an instance for the 'ToJSON' typeclass we can use the '~=' operator.
object  [Pair]  Value
object = Object  HM.fromList


-- | Map keys to values that provide a 'ToMustache' instance
--
-- Recommended in conjunction with the `OverloadedStrings` extension.
(~>)  ToMustache ω  Text  ω  Pair
(~>) t = (t, )  toMustache


-- | Unicode version of '~>'
()  ToMustache ω  Text  ω  Pair
() = (~>)


-- | Map keys to values that provide a 'ToJSON' instance
--
-- Recommended in conjunction with the `OverloadedStrings` extension.
(~=)  Aeson.ToJSON ι  Text  ι  Pair
(~=) t = (t ~>)  Aeson.toJSON


-- | Unicode version of '~='
()  Aeson.ToJSON ι  Text  ι  Pair
() = (~=)

-- | Conceptually similar to '~>' but uses arbitrary String-likes as keys.
(~~>)  (Conversion ζ Text, ToMustache ω)  ζ  ω  Pair
(~~>) = (~>)  convert


-- | Unicde version of '~~>'
(~)  (Conversion ζ Text, ToMustache ω)  ζ  ω  Pair
(~) = (~~>)

-- | Conceptually similar to '~=' but uses arbitrary String-likes as keys.
(~~=)  (Conversion ζ Text, Aeson.ToJSON ι)  ζ  ι  Pair
(~~=) = (~=)  convert


-- | Unicode version of '~~='
(~)  (Conversion ζ Text, Aeson.ToJSON ι)  ζ  ι  Pair
(~) = (~~=)


-- | Converts arbitrary String-likes to Values
toTextBlock  Conversion ζ Text  ζ  Value
toTextBlock = String  convert


-- | Converts a value that can be represented as JSON to a Value.
mFromJSON  Aeson.ToJSON ι  ι  Value
mFromJSON = toMustache  Aeson.toJSON


{-|
  A compiled Template with metadata.
-}
data Template = Template
  { name      String
  , ast       AST
  , partials  HashMap String Template
  } deriving (Show)