{-# LANGUAGE OverloadedStrings #-}

-- | The final purpose of this module is to render a Text value
--   from a 'LaTeX' value. The interface is abstracted via a typeclass
--   so you can cast to 'Text' other types as well. Also, some other
--   handy 'Text'-related functions are defined.
module Text.LaTeX.Base.Render
 ( -- * Re-exports
   Text
 , module Data.String
   -- * Render class
 , Render (..)
 , renderAppend
 , renderChars
 , renderCommas
 , renderFile
 , rendertex
   -- * Reading files
 , readFileTex
   -- * Util
 , showFloat
   ) where

import Data.Text (Text,lines,unlines)
import Text.LaTeX.Base.Syntax
import Text.LaTeX.Base.Class
import Data.String
import Data.Text.Encoding
import Data.List (intersperse)
import qualified Data.ByteString as B
import Data.Word (Word8)
import Numeric (showFFloat)

-- | Class of values that can be transformed to 'Text'.
-- You mainly will use this to obtain the 'Text' output
-- of a 'LaTeX' value. If you are going to write the result
-- in a file, consider to use 'renderFile'.
--
-- Consider also to use 'rendertex' to get 'Render'able values
-- into 'LaTeX' blocks.
--
-- If you want to make a type instance of 'Render' and you already
-- have a 'Show' instance, you can use the default instance.
--
-- > render = fromString . show
--
class Show a => Render a where
 render :: a -> Text
 --
 render = fromString . show

-- | This instance escapes LaTeX reserved characters.
instance Render Text where
 render = protectText

-- | Render every element of a list and append results.
renderAppend :: Render a => [a] -> Text
renderAppend = mconcat . fmap render

-- | Render every element of a list and append results,
--   separated by the given 'Char'.
renderChars :: Render a => Char -> [a] -> Text
renderChars c = mconcat . intersperse (fromString [c]) . fmap render

-- | Render every element of a list and append results,
--   separated by commas.
renderCommas :: Render a => [a] -> Text
renderCommas = renderChars ','

-- | Use this function to render a 'LaTeX' (or another
--   one in the 'Render' class) value directly
--   in a file.
renderFile :: Render a => FilePath -> a -> IO ()
renderFile f = B.writeFile f . encodeUtf8 . render

-- | If you are going to insert the content of a file
-- in your 'LaTeX' data, use this function to ensure
-- your encoding is correct.
readFileTex :: FilePath -> IO Text
readFileTex = fmap decodeUtf8 . B.readFile

-- | If you can transform a value to 'Text', you can
--   insert that 'Text' in your 'LaTeX' code.
--   That is what this function does.
--
-- /Warning: /'rendertex'/ does not escape LaTeX reserved characters./
-- /Use /'protectText'/ to escape them./
rendertex :: (Render a,LaTeXC l) => a -> l
rendertex = fromLaTeX . TeXRaw . render

-- Render instances

instance Render Measure where
 render (Pt x) = render x <> "pt"
 render (Mm x) = render x <> "mm"
 render (Cm x) = render x <> "cm"
 render (In x) = render x <> "in"
 render (Ex x) = render x <> "ex"
 render (Em x) = render x <> "em"
 render (CustomMeasure x) = render x

-- LaTeX instances

instance Render LaTeX where
  
  render (TeXRaw t) = t
  
  render (TeXComm name []) = "\\" <> fromString name <> "{}"
  render (TeXComm name args) =
      "\\"
   <> fromString name
   <> renderAppend args
  render (TeXCommS name) = "\\" <> fromString name
  
  render (TeXEnv name args c) =
      "\\begin{"
   <> fromString name
   <> "}"
   <> renderAppend args
   <> render c
   <> "\\end{"
   <> fromString name
   <> "}"

  render (TeXMath Dollar l) = "$" <> render l <> "$"
  render (TeXMath Square l) = "\\[" <> render l <> "\\]"
  render (TeXMath Parentheses l) = "\\(" <> render l <> "\\)"

  render (TeXLineBreak m b) = "\\\\" <> maybe mempty (\x -> "[" <> render x <> "]") m <> ( if b then "*" else mempty )

  render (TeXBraces l) = "{" <> render l <> "}"

  render (TeXComment c) =
   let xs = Data.Text.lines c
   in if null xs then "%\n"
                 else Data.Text.unlines $ fmap ("%" <>) xs

  render (TeXSeq l1 l2) = render l1 <> render l2
  render TeXEmpty = mempty

instance Render TeXArg where
 render (FixArg l) = "{" <> render l <> "}"
 render (OptArg l) = "[" <> render l <> "]"
 render (MOptArg []) = mempty
 render (MOptArg ls) = "[" <> renderCommas ls <> "]"
 render (SymArg l) = "<" <> render l <> ">"
 render (MSymArg []) = mempty
 render (MSymArg ls) = "<" <> renderCommas ls <> ">"
 render (ParArg l) = "(" <> render l <> ")"
 render (MParArg []) = mempty
 render (MParArg ls) = "(" <> renderCommas ls <> ")"

-- Other instances

-- | Show a signed floating number using standard decimal notation using 5 decimals.
showFloat :: RealFloat a => a -> String
showFloat x = showFFloat (Just 5) x []

instance Render Int where
instance Render Integer where
instance Render Float where
  render = fromString . showFloat
instance Render Double where
  render = fromString . showFloat
instance Render Word8 where

-- | 'Render' instance for 'Bool'. It satisfies @render True = "true"@ and @render False = "false"@.
instance Render Bool where
  render True = "true"
  render _ = "false"

instance Render a => Render [a] where
 render xs = "[" <> renderCommas xs <> "]"