{-# LANGUAGE QuasiQuotes, TemplateHaskell, DeriveDataTypeable, OverloadedStrings #-}
{-# OPTIONS_GHC -fno-warn-missing-fields #-} 
-- | NeatInterpolation provides a quasiquoter for producing `Text` data with a 
-- simple interpolation of input values. It removes the excessive indentation 
-- from the input text and accurately manages the indentation of all lines of 
-- interpolated variables. But enough words, the code shows it better.
-- 
-- Consider the following declaration:
-- 
-- > {-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
-- > 
-- > import NeatInterpolation
-- > import qualified Data.Text.IO as Text
-- > 
-- > f :: Text -> Text -> Text
-- > f a b = 
-- >   [text|
-- >     function(){
-- >       function(){
-- >         $a
-- >       }
-- >       return $b
-- >     }
-- >   |]
-- 
-- Executing the following:
-- 
-- > main = Text.putStrLn $ f "1" "2"
-- 
-- will produce this (notice the reduced indentation compared to how it was
-- declared):
-- 
-- > function(){
-- >   function(){
-- >     1
-- >   }
-- >   return 2
-- > }
-- 
-- Now let's test it with multiline text parameters:
-- 
-- > main = Text.putStrLn $ f 
-- >   "{\n  indented line\n  indented line\n}" 
-- >   "{\n  indented line\n  indented line\n}" 
--
-- We get
--
-- > function(){
-- >   function(){
-- >     {
-- >       indented line
-- >       indented line
-- >     }
-- >   }
-- >   return {
-- >     indented line
-- >     indented line
-- >   }
-- > }
-- 
-- See how it neatly preserved the indentation levels of lines the 
-- variable placeholders were at?  
module NeatInterpolation (text, indentQQPlaceholder) where

import Prelude ()
import ClassyPrelude

import Language.Haskell.TH
import Language.Haskell.TH.Quote

import NeatInterpolation.String
import NeatInterpolation.Parsing



text :: QuasiQuoter
text = QuasiQuoter {quoteExp = quoteExprExp}

indentQQPlaceholder :: Int -> Text -> Text
indentQQPlaceholder indent text = case lines text of
  head:tail -> intercalate "\n" $ head : map (replicate indent " " ++) tail
  [] -> text 


quoteExprExp :: [Char] -> Q Exp
quoteExprExp input = 
  case parseLines $ normalizeQQInput input of
    Left e -> fail $ show e
    Right lines -> appE [|unlines|] $ linesExp lines

linesExp :: [Line] -> Q Exp
linesExp [] = [|([] :: [Text])|]
linesExp (head : tail) = 
  (binaryOpE [|(:)|])
    (lineExp head)
    (linesExp tail)

lineExp :: Line -> Q Exp
lineExp (Line indent contents) = 
  msumExps $ map (contentExp $ fromIntegral indent) contents



contentExp :: Integer -> LineContent -> Q Exp
contentExp _ (LineContentText text) = stringE text
contentExp indent (LineContentIdentifier name) = do
  valueName <- lookupValueName name
  case valueName of
    Just valueName -> do
      Just indentQQPlaceholderName <- lookupValueName "indentQQPlaceholder"
      appE
        (appE (varE indentQQPlaceholderName) $ litE $ integerL indent)
        (varE valueName)
    Nothing -> fail $ "Value `" ++ name ++ "` is not in scope"

msumExps :: [Q Exp] -> Q Exp
msumExps = fold (binaryOpE mappendE) memptyE
memptyE = [|mempty|]
mappendE = [|mappend|]

binaryOpE e = \a b -> e `appE` a `appE` b