{-# LANGUAGE QuasiQuotes,TemplateHaskell,DeriveDataTypeable #-}
module Data.String.Interpolation(str) where
import Language.Haskell.TH as TH
import Language.Haskell.TH.Quote
import Language.Haskell.Meta
import Data.Data
import Data.Maybe
import Data.Char
import Data.List(intercalate)

quoteExprExp :: String -> TH.ExpQ
-- | Quasiquote 'str' implements multiline strings with interpolation.
--   Interpolating a value of parameter a into the string is done by $a$
--   and interpolating anything with instance Show is $:a$.
--
--   Repetitive patterns can be made by # symbol using the following syntax: 
--
--  @
--  \#\<var\> in \<list\>: \<interpolated string\> (|\<interpolated string\>)\#
--  @
--   
--   Where (|\<interpolated string\>) denotes optional separator for the 
--   elements.
--   
--
--   Example:
-- 
--   @
--    \#i in myList: this is $i$|--\#
--   @
--   
--   Which will evaluate to @ 1--2--3 @ given myList of [1,2,3]
--   
-- 
--   Multiline indentation is handled by aligning on smallest un-empty
--   line after the first.
--
--   Pattern matching is not supported. 
--

str  :: QuasiQuoter
str  =  QuasiQuoter quoteExprExp undefined
--
quoteExprExp s = psToStringE (parParse $ norming s)
--

psToStringE :: PieceString -> TH.Q TH.Exp
psToStringE [] = TH.stringE ""
psToStringE (x:xs) = TH.infixE (Just $ sbitToExp x)
                               ([| (++) |])
                               (Just $ psToStringE xs)

runEither :: (Monad m) => [Char] -> Either [Char] t -> m t
runEither _ (Right x) = return x
runEither s (Left e)  = error $ s ++" : "++ e

appM :: (Monad m) => String -> m Exp
appM expr = runEither "Parse error in antiquote" (parseExp expr)
sbitToExp :: StringBits -> ExpQ
sbitToExp (Str x) =  TH.stringE x 
sbitToExp (Var x)  =  appM x --(TH.varE (TH.mkName x))
sbitToExp (SVar x) = TH.appE (TH.varE (TH.mkName "show"))
                        (sbitToExp (Var x))

sbitToExp (RepVar varName lstName rep sep) 
    = [|intercalate $(sepr) (map $(lam) $(lstN))|]
    where
     sepr = psToStringE (fromMaybe [Str ""] sep)
     lam  = TH.lamE [varN] (psToStringE rep)
     varN = runEither "Parse error in repetition pattern"
             (parsePat varName)--TH.varP $ TH.mkName varName
     lstN = runEither "Parse error in repetition binding"
             (parseExp lstName) -- TH.varE $ TH.mkName lstName



type PieceString = [StringBits]
data StringBits = Str String | Var String | SVar String
                  | RepVar String String 
                    PieceString (Maybe PieceString)
                deriving (Eq,Ord,Show,Typeable,Data)

-- Split to string into pieces. This needs proper error messages.
-- Perhaps parsec?
parParse :: [Char] -> [StringBits]
parParse [] = []

parParse ('$':'$':s) = Str "$":parParse s -- Parse escapes
parParse ('#':'#':s) = Str "#":parParse s

parParse ('$':':':s) = SVar (takeWhile (/='$') s) -- Parse antiquotes
                        :parParse (drop 1 (dropWhile (/='$') s))

parParse ('$':s) = Var (takeWhile (/='$') s)
                    :parParse (drop 1 (dropWhile (/='$') s))

parParse ('#':s) = let (bind,exprS) = break (==':') s
                       (hasSep,expr,sepS) = takeRep $ drop 1 exprS
                       (sep,restS) = break (=='#') $ sepS
                       rest = drop 1 restS
                       (varNameS,listNameS) = break (=="in") . words
                                            $ bind
                       listName = unwords . drop 1 $ listNameS
                       varName  = unwords varNameS
                    in if hasSep 
                        then RepVar varName listName 
                             (parParse expr) (Just $ parParse sep) 
                             : parParse rest
                        else RepVar varName listName 
                             (parParse expr) (Nothing) 
                             : parParse sepS

parParse s = Str (takeWhile (notIn "$#") s)
                 : parParse (dropWhile (notIn "$#") s) 

notIn :: (Eq a) => [a] -> a -> Bool
notIn s x =not $ elem x s 

takeRep :: String -> (Bool,String,String)
takeRep x = takeRep' x []

takeRep' :: String -> String -> (Bool,String,String)
takeRep' [] _ = error "Repetition template does not end"
takeRep' ('|':'|':s) acc = takeRep' s ('|':acc)
takeRep' ('|':s) acc = (True,reverse acc,s)
takeRep' ('#':s) acc = (False,reverse acc,s)
takeRep' (r:s) acc = takeRep' s (r:acc)


-- Normalize the indentation to match second line

norming :: String -> String
norming = unlines . norming' . lines

norming':: [String] -> [String]
norming' [] = []
norming' (l:lst) = l:map (drop (n)) lst
    where 
     n = minimumD 0 $ map (length . takeWhile (==' ') ) 
         $ (filter (not.isEmpty) lst)
     isEmpty = all (isSpace)


minimumD :: (Ord a) => a -> [a] -> a
minimumD d [] = d
minimumD _ x  = minimum x