{-# LANGUAGE OverloadedStrings, StrictData #-}

module Include where

import Data.List (intersperse, mapAccumL)
import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as T
import Data.Text.Lazy.Builder (Builder, fromLazyText)
import Data.Text.Lazy.IO (hGetContents)
import System.IO (IOMode(ReadMode), openFile)

import HaskellLex (splitCodeOn, balancedCode)
import Report
import Text (linebreaks, splitOnOnce, textLiteral)


data Inclusion = Inclusion {

  -- | Which filename are we including?
  filename :: FilePath,

  -- | The delimiter which begins a code section in a template
  --
  -- Default: "$"
  openCode :: Text,

  -- | The delimiter which ends a code section in a template
  --
  -- Default: "$"
  closeCode :: Text,

  -- | Do we start in a literal section?
  --
  -- Default: True
  startLiteral :: Bool,

  -- | May we end in a literal section?
  --
  -- Default: True
  mayEndLiteral :: Bool,

  -- | May we end in a code section?
  --
  -- Default: False
  mayEndCode :: Bool,

  -- | Should we check that code sections are well-formed?
  --
  -- Default: True
  checkCodeBalanced :: Bool
}


data Chunk = Code Text | Literal Text


readTemplate :: Inclusion -> I Builder
readTemplate i = useChunks <$> ((liftE . formChunks 1) =<< liftIO input) where

  input :: IO Text
  input = hGetContents =<< openFile (filename i) ReadMode

  formChunks :: Int -> Text -> E [Chunk]
  formChunks
    | startLiteral i = formChunksLiteral
    | otherwise      = formChunksCode

  formChunksLiteral :: Int -> Text -> E [Chunk]
  formChunksLiteral n a
    | T.null a  = if mayEndLiteral i
                    then pure []
                    else illegalEnd
    | otherwise = f $ splitOnOnce (openCode i) a where
        illegalEnd = failure ("Template ends illegally in a literal beginning on line " <> T.pack (show n))
        f Nothing = if mayEndLiteral i
                      then pure [Literal a]
                      else illegalEnd
        f (Just (h,t)) = (Literal h:) <$> formChunksCode (n+p) t where
          p = linebreaks h

  formChunksCode :: Int -> Text -> E [Chunk]
  formChunksCode n a
    | T.null a  = if mayEndCode i
                    then pure []
                    else illegalEnd
    | otherwise = f $ splitCodeOn (closeCode i) a where
        illegalEnd = failure ("Template ends illegally in a code block beginning on line " <> T.pack (show n))
        f Nothing = if mayEndCode i
                      then pure . snd <$> makeChunkCode n a
                      else illegalEnd
        f (Just (h,t)) = do
          (n',c) <- makeChunkCode n h
          (c:) <$> formChunksLiteral (n'-1) t

  makeChunkCode :: Int -> Text -> E (Int, Chunk)
  makeChunkCode n t
    | balanced  = pure (n', Code $ T.intercalate "\n" clines)
    | otherwise = unbalanced where
        unbalanced = failure ("Unbalanced parentheses in code section beginning on line " <> T.pack (show n))
        (n', clines) = mapAccumL g n $ T.splitOn "\n" t where
          g k l = (k+1, l)
        balanced = not (checkCodeBalanced i) || balancedCode t

  useChunks :: [Chunk] -> Builder
  useChunks l = "makeTemplate (" <> innards <> ")" where
    innards = foldMap f . intersperse Nothing $ fmap Just l where
      f (Just (Literal a)) = "embedConst " <> textLiteral a
      f (Just (Code a)) = "(" <> fromLazyText a <> ")"
      f Nothing = " >>> "