{-# LANGUAGE ExistentialQuantification, TemplateHaskell, QuasiQuotes, OverloadedStrings, FlexibleInstances, UndecidableInstances, IncoherentInstances #-}

-- | QuasyString-like module. Tweaked for the cake3
module Text.QuasiMake (Chunk (..), getChunks) where
import Language.Haskell.TH.Quote
import Language.Haskell.TH.Syntax
import Language.Haskell.TH
import Language.Haskell.Meta (parseExp)

import Data.Attoparsec.Text
import qualified Data.Text as T 
import Data.Text (Text)
import Data.Char

import Data.Monoid
import Control.Applicative
                        
instance Lift Text where
    lift = litE . stringL . T.unpack

-- | Chunk is a part of quasy-quotation
data Chunk 
    = T Text
    -- ^ the text
    | E Char Text
    -- ^ \$(expr) or \@(expr)
  deriving (Show, Eq)

class Textish a where
    toText :: a -> Text

instance Textish Text where
    {-# INLINE toText #-}
    toText x = x

instance Textish [Char] where
    {-# INLINE toText #-}
    toText x = T.pack x

instance Show a => Textish a where 
    {-# INLINE toText #-}
    toText x = T.pack (show x)


-- | A simple 'QuasiQuoter' to interpolate 'Text' into other pieces of 'Text'.
-- Expressions can be embedded using \$(expr) or \@(expr), and values can be
-- interpolated with $name. Inside \$( )s, if you have a string of ambiguous
-- type, it will default to the Show instance for toText, which will escape
-- unicode characters in the string, and add quotes around them.
getChunks :: Text -> [Chunk]
getChunks i = case parseOnly parser (T.strip i) of
        Right m -> m
        _       -> error "Unclosed parenthesis."

  where
    parenthesis '(' = True
    parenthesis ')' = True
    parenthesis _   = False

    parseExpression :: Int -> Parser [Text]
    parseExpression level = do
        expr  <- takeTill parenthesis
        paren <- anyChar
        case paren of
            ')' | level <= 0 -> return [expr]
                | otherwise  -> do
                    next <- parseExpression (level - 1)
                    return ([expr, ")"] ++ next)

            '(' -> do
                next <- parseExpression (level + 1)
                return ([expr, "("] ++ next)

            _ -> return [expr, T.singleton paren]

    parser :: Parser [Chunk]
    parser = fmap concat $ flip manyTill endOfInput $ do
        text <- takeTill (\c -> elem c "@$")
        end  <- atEnd
        if end
            then return [T text]
            else do
                delim <- anyChar
                next <- anyChar
                case next of
                    -- opening an experssion
                    '(' -> do
                        expr <- T.concat <$> parseExpression 0
                        return [T text, E delim expr]
                    c | c == delim -> do
                        -- escaped '$', '@' or '%' 
                        return [T (text <> T.singleton delim)]

                      | otherwise -> do
                        -- value
                        name <- takeTill (\c -> not (isAlphaNum c || c == '_') )
                        return [T text, E delim (T.cons next name)]