-- | Defines document template
module Text.PDF.Slave.Template(
    TemplateName
  , TemplateInput
  , TemplateBody
  , TemplateBibtex
  , DependencyBody
  , BibTexBody
  , TemplateDependency(..)
  , Template(..)
  , TemplateDependencyFile(..)
  , TemplateFile(..)
  ) where

import Control.Monad (mzero, unless)
import Data.ByteString (ByteString)
import Data.Monoid
import Data.Text as T
import Data.Aeson
import GHC.Generics

import qualified Data.ByteString.Base64 as B64
import qualified Data.Map.Strict        as M
import qualified Data.Text.Encoding     as T

-- | Template unique name
type TemplateName = Text

-- | A template takes simple YAML document as input
type TemplateInput = Value

-- | Template body is text with .htex content
type TemplateBody = Text

-- | Template can define additional bibtex database
type TemplateBibtex = Text

-- | Dependency can be a binary file
type DependencyBody = ByteString

-- | Content of bibtex file
type BibTexBody = Text

-- | Template has different types of dependencies, each type of the dependecy
-- has own affect on rendering pipe.
data TemplateDependency =
  -- | Bibtex file for references to other documents. Need call to bibtex
    BibtexDep BibTexBody
  -- | HTex file that need to be compiled to .tex file
  | TemplateDep Template
  -- | HTex file that need to be compiled to .pdf file
  | TemplatePdfDep Template
  -- | Any other file that doesn't need a compilation (listings, images, etc)
  | OtherDep DependencyBody
  deriving (Generic, Show)

instance FromJSON TemplateDependency where
  parseJSON val@(Object o) = do
    depType <- o .: "type"
    case T.toLower . T.strip $ depType of
      "bibtex"       -> BibtexDep       <$> o .: "body"
      "template"     -> TemplateDep     <$> parseJSON val
      "template_pdf" -> TemplatePdfDep  <$> parseJSON val
      "other"        -> do
        (t :: Text) <- o .: "body"
        either (\e -> fail $ "Cannot decode dependency body (base64): " <> e) (return . OtherDep) $
          B64.decode . T.encodeUtf8 $ t
      _ -> fail $ "Unknown template type " <> unpack depType
  parseJSON _ = mzero

instance ToJSON TemplateDependency where
  toJSON d = case d of
    BibtexDep body -> object [
        "type" .= ("bibtex" :: Text)
      , "body" .= body
      ]
    TemplateDep body -> let
      Object o1 = object [ "type" .= ("template" :: Text) ]
      Object o2 = toJSON body
      in Object (o1 <> o2)
    TemplatePdfDep body -> let
      Object o1 = object [ "type" .= ("template_pdf" :: Text) ]
      Object o2 = toJSON body
      in Object (o1 <> o2)
    OtherDep bs -> object [
        "type" .= ("other" :: Text)
      , "body" .= (T.decodeUtf8 . B64.encode $ bs)
      ]

-- | Description of document template
data Template = Template {
  -- | Template has human readable name
    templateName          :: TemplateName
  -- | Template expects input in YAML format
  , templateInput         :: Maybe TemplateInput
  -- | Template contents
  , templateBody          :: TemplateBody
  -- | Template dependencies (bibtex, listings, other htex files)
  , templateDeps          :: M.Map TemplateName TemplateDependency
  -- | Additional flags for `haskintex`
  , templateHaskintexOpts :: [Text]
  } deriving (Generic, Show)

instance FromJSON Template where
  parseJSON (Object o) = do
    flag <- o .: "bundle"
    unless flag $ fail "Expected bundle template format, but got ordinary template"
    Template
      <$> o .: "name"
      <*> o .:? "input"
      <*> o .: "body"
      <*> o .:? "dependencies" .!= mempty
      <*> o .:? "haskintex-opts" .!= mempty
  parseJSON _ = mzero

instance ToJSON Template where
  toJSON Template{..} = object [
      "name"           .= templateName
    , "input"          .= templateInput
    , "body"           .= templateBody
    , "dependencies"   .= templateDeps
    , "haskintex-opts" .= templateHaskintexOpts
    , "bundle"         .= True
    ]

-- | Same as 'TemplateDependency' but keeps contents in separate files
data TemplateDependencyFile =
  -- | Bibtex file for references to other documents. Need call to bibtex.
  -- Name of dependency is a filename with contents.
    BibtexDepFile
  -- | HTex file that need to be compiled to .tex file
  -- Name of dependency defines a subfolder for the template.
  | TemplateDepFile TemplateFile
  -- | HTex file that need to be compiled to .pdf file
  -- Name of dependency deinfes a subfolder for the template.
  | TemplatePdfDepFile TemplateFile
  -- | Any other file that doesn't need a compilation (listings, images, etc)
  -- Name of dependency is a filename with contents.
  | OtherDepFile
  deriving (Generic, Show)

instance FromJSON TemplateDependencyFile where
  parseJSON val@(Object o) = do
    depType <- o .: "type"
    case T.toLower . T.strip $ depType of
      "bibtex"       -> pure BibtexDepFile
      "template"     -> TemplateDepFile     <$> parseJSON val
      "template_pdf" -> TemplatePdfDepFile  <$> parseJSON val
      "other"        -> pure OtherDepFile
      _ -> fail $ "Unknown template type " <> unpack depType
  parseJSON _ = mzero

instance ToJSON TemplateDependencyFile where
  toJSON d = case d of
    BibtexDepFile -> object [
        "type" .= ("bibtex" :: Text)
      ]
    TemplateDepFile body -> let
      Object o1 = object [ "type" .= ("template" :: Text) ]
      Object o2 = toJSON body
      in Object (o1 <> o2)
    TemplatePdfDepFile body -> let
      Object o1 = object [ "type" .= ("template_pdf" :: Text) ]
      Object o2 = toJSON body
      in Object (o1 <> o2)
    OtherDepFile -> object [
        "type" .= ("other" :: Text)
      ]

-- | Same as 'Template', but holds info about template content and dependencies
-- in other files.
data TemplateFile = TemplateFile {
  -- | Template has human readable name
    templateFileName      :: TemplateName
  -- | Template expects input in JSON format. The field contains filename of the YAML file.
  , templateFileInput     :: Maybe Text
  -- | Template contents filename.
  , templateFileBody      :: Text
  -- | Template dependencies (bibtex, listings, other htex files)
  , templateFileDeps      :: M.Map TemplateName TemplateDependencyFile
  -- | Additional flags for `haskintex`
  , templateFileHaskintexOpts :: [Text]
  } deriving (Generic, Show)

instance FromJSON TemplateFile where
  parseJSON (Object o) = do
    mflag <- o .:? "bundle"
    case mflag of
      Just True -> fail "Expected ordinary template format, but got bundle template"
      _ -> return ()
    TemplateFile
      <$> o .: "name"
      <*> o .:? "input"
      <*> o .: "body"
      <*> o .:? "dependencies" .!= mempty
      <*> o .:? "haskintex-opts" .!= mempty
  parseJSON _ = mzero

instance ToJSON TemplateFile where
  toJSON TemplateFile{..} = object [
      "name"           .= templateFileName
    , "input"          .= templateFileInput
    , "body"           .= templateFileBody
    , "dependencies"   .= templateFileDeps
    , "haskintex-opts" .= templateFileHaskintexOpts
    ]
