-- |
-- Module      :  Language.C.Quote
-- Copyright   :  (c) 2006-2011 Harvard University
--                (c) 2011-2013 Geoffrey Mainland
--             :  (c) 2013-2017 Drexel University
-- License     :  BSD-style
-- Maintainer  :  mainland@drexel.edu

{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -w #-}

module Language.C.Quote.Base (
    ToIdent(..),
    ToConst(..),
    ToExp(..),
    qqExp,
    qqPat,
    quasiquote
  ) where

import Control.Monad ((>=>))
import qualified Data.ByteString.Char8 as B
import Data.Char (isAscii, isPrint, ord)
import Data.Data (Data(..))
import Data.Generics (extQ)
import Data.Int
import Data.Loc
import Data.Typeable (Typeable(..))
import Data.Word
#ifdef FULL_HASKELL_ANTIQUOTES
import Language.Haskell.Meta (parseExp,parsePat)
#else
import Language.Haskell.ParseExp (parseExp,parsePat)
#endif
import Language.Haskell.TH as TH
#if MIN_VERSION_template_haskell(2,7,0)
import Language.Haskell.TH.Quote (QuasiQuoter(..),
                                  dataToQa,
                                  dataToExpQ,
                                  dataToPatQ)
#else /* !MIN_VERSION_template_haskell(2,7,0) */
import Language.Haskell.TH.Quote (QuasiQuoter(..))
#endif /* !MIN_VERSION_template_haskell(2,7,0) */
import Language.Haskell.TH.Syntax
import Numeric (showOct, showHex)

import qualified Language.C.Parser as P
import qualified Language.C.Syntax as C

newtype LongDouble = LongDouble Double

-- | An instance of 'ToIndent' can be converted to a 'C.Id'.
class ToIdent a where
    toIdent :: a -> SrcLoc -> C.Id

instance ToIdent C.Id where
    toIdent ident _ = ident

instance ToIdent (SrcLoc -> C.Id) where
    toIdent ident = ident

instance ToIdent String where
    toIdent s loc = C.Id s loc

-- | An instance of 'ToConst' can be converted to a 'C.Const'.
class ToConst a where
    toConst :: a -> SrcLoc -> C.Const

instance ToConst C.Const where
    toConst k _ = k

instance ToConst Int where
    toConst = toConst . toInteger

instance ToConst Int8 where
    toConst = toConst . toInteger

instance ToConst Int16 where
    toConst = toConst . toInteger

instance ToConst Int32 where
    toConst = toConst . toInteger

instance ToConst Int64 where
    toConst = toConst . toInteger

instance ToConst Word where
    toConst n loc = C.IntConst (show n) C.Unsigned (toInteger n) loc

instance ToConst Word8 where
    toConst n loc = C.IntConst (show n) C.Unsigned (toInteger n) loc

instance ToConst Word16 where
    toConst n loc = C.IntConst (show n) C.Unsigned (toInteger n) loc

instance ToConst Word32 where
    toConst n loc = C.IntConst (show n) C.Unsigned (toInteger n) loc

instance ToConst Word64 where
    toConst n loc = C.IntConst (show n) C.Unsigned (toInteger n) loc

instance ToConst Integer where
    toConst n loc = C.IntConst (show n) C.Signed n loc

instance ToConst Rational where
    toConst n loc = toConst (fromRational n :: Double) loc

instance ToConst Float where
    toConst n loc = C.FloatConst (realFloatToString n ++ "F") n loc

instance ToConst Double where
    toConst n loc = C.DoubleConst (realFloatToString n) n loc

instance ToConst LongDouble where
    toConst (LongDouble n) loc = C.LongDoubleConst (realFloatToString n ++ "L") n loc

realFloatToString :: (RealFloat a, Show a) => a -> String
realFloatToString n
  | isNaN n      = "NAN"
  | isInfinite n = if n < 0 then "-INFINITY" else "INFINITY"
  | otherwise    = show n

instance ToConst Char where
    toConst c loc = C.CharConst ("'" ++ charToString c ++ "'") c loc
      where
        charToString :: Char -> String
        charToString '\0' = "\\0"
        charToString '\a' = "\\a"
        charToString '\b' = "\\b"
        charToString '\f' = "\\f"
        charToString '\n' = "\\n"
        charToString '\r' = "\\r"
        charToString '\t' = "\\t"
        charToString '\v' = "\\v"
        charToString '\\' = "\\\\"
        charToString '\"' = "\\\""
        charToString c
          | isAscii c && isPrint c = [c]
          | isAscii c              = "\\x" ++ hexOf Nothing c
          | ord c < 0x10000        = "\\u" ++ hexOf (Just 4) c
          | otherwise              = "\\U" ++ hexOf (Just 8) c
          where
            hexOf :: Maybe Int -> Char -> String
            hexOf len c = case len of
                            Nothing -> hex
                            Just i  -> replicate (i - length hex) '0' ++ hex
              where
                hex :: String
                hex = showHex (ord c) ""

instance ToConst String where
    toConst s loc = C.StringConst [show s] s loc

-- | An instance of 'ToExp' can be converted to a 'C.Exp'.
class ToExp a where
    toExp :: a -> SrcLoc -> C.Exp

instance ToExp C.Exp where
    toExp e _ = e

instance ToExp Int where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Int8 where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Int16 where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Int32 where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Int64 where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Word where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Word8 where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Word16 where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Word32 where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Word64 where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Integer where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Rational where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Float where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Double where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp Char where
    toExp n loc = C.Const (toConst n loc) loc

instance ToExp String where
    toExp n loc = C.Const (toConst n loc) loc

antiVarE :: String -> ExpQ
antiVarE = either fail return . parseExp

qqLocE :: SrcLoc -> ExpQ
qqLocE loc = dataToExpQ qqExp loc

qqStringE :: String -> Maybe (Q Exp)
qqStringE s = Just $ litE $ stringL s

qqIdE :: C.Id -> Maybe (Q Exp)
qqIdE (C.AntiId v loc)  = Just [|toIdent $(antiVarE v) $(qqLocE loc)|]
qqIdE _                 = Nothing

qqDeclSpecE :: C.DeclSpec -> Maybe (Q Exp)
qqDeclSpecE (C.AntiDeclSpec v _) = Just $ antiVarE v
qqDeclSpecE (C.AntiTypeDeclSpec extraStorage extraTypeQuals v _) =
    Just [|
        case $(antiVarE v) of
            C.Type (C.DeclSpec storage typeQuals typeSpec loc) _ _ ->
                 C.DeclSpec
                     (storage ++ $(dataToExpQ qqExp extraStorage))
                     (typeQuals ++ $(dataToExpQ qqExp extraTypeQuals))
                     typeSpec
                     loc

            x -> error
                   $ "Impossible happened, expected C.Type (C.DeclSpec {}) but got "
                   ++ show x
         |]
qqDeclSpecE _ = Nothing

qqDeclE :: C.Decl -> Maybe (Q Exp)
qqDeclE (C.AntiTypeDecl v _) =
    Just [|
        case $(antiVarE v) of
            C.Type _ decl _ -> decl
            x -> error
                   $ "Impossible happened, expected C.Type but got "
                   ++ show x
    |]
qqDeclE _ = Nothing

qqTypeQualE :: C.TypeQual -> Maybe (Q Exp)
qqTypeQualE (C.AntiTypeQual v _)  = Just $ antiVarE v
qqTypeQualE _                     = Nothing

qqTypeQualListE :: [C.TypeQual] -> Maybe (Q Exp)
qqTypeQualListE [] = Just [|[]|]
qqTypeQualListE (C.AntiTypeQuals v _ : stms) =
    Just [|$(antiVarE v) ++ $(dataToExpQ qqExp stms)|]
qqTypeQualListE (stm : stms) =
    Just [|$(dataToExpQ qqExp stm) : $(dataToExpQ qqExp stms)|]

qqTypeE :: C.Type -> Maybe (Q Exp)
qqTypeE (C.AntiType v _)  = Just $ antiVarE v
qqTypeE _                 = Nothing

qqInitializerE :: C.Initializer -> Maybe (Q Exp)
qqInitializerE (C.AntiInit v _)  = Just $ antiVarE v
qqInitializerE _                 = Nothing

qqInitializerListE :: [(Maybe C.Designation, C.Initializer)] -> Maybe (Q Exp)
qqInitializerListE [] = Just [|[]|]
qqInitializerListE ((Nothing, C.AntiInits v _) : fields) =
    Just [|[(Nothing, init) | init <- $(antiVarE v)] ++ $(dataToExpQ qqExp fields)|]
qqInitializerListE (field : fields) =
    Just [|$(dataToExpQ qqExp field) : $(dataToExpQ qqExp fields)|]

qqInitGroupE :: C.InitGroup -> Maybe (Q Exp)
qqInitGroupE (C.AntiDecl v _)  = Just $ antiVarE v
qqInitGroupE _                 = Nothing

qqInitGroupListE :: [C.InitGroup] -> Maybe (Q Exp)
qqInitGroupListE [] = Just [|[]|]
qqInitGroupListE (C.AntiDecls v _ : inits) =
    Just [|$(antiVarE v) ++ $(dataToExpQ qqExp inits)|]
qqInitGroupListE (ini : inis) =
    Just [|$(dataToExpQ qqExp ini) : $(dataToExpQ qqExp inis)|]

qqAttrE :: C.Attr -> Maybe (Q Exp)
qqAttrE (C.AntiAttr v _)  = Just $ antiVarE v
qqAttrE _                 = Nothing

qqAttrListE :: [C.Attr] -> Maybe (Q Exp)
qqAttrListE [] = Just [|[]|]
qqAttrListE (C.AntiAttrs v _ : attrs) =
    Just [|$(antiVarE v) ++ $(dataToExpQ qqExp attrs)|]
qqAttrListE (field : fields) =
    Just [|$(dataToExpQ qqExp field) : $(dataToExpQ qqExp fields)|]

qqFieldGroupE :: C.FieldGroup -> Maybe (Q Exp)
qqFieldGroupE (C.AntiSdecl v _)  = Just $ antiVarE v
qqFieldGroupE _                  = Nothing

qqFieldGroupListE :: [C.FieldGroup] -> Maybe (Q Exp)
qqFieldGroupListE [] = Just [|[]|]
qqFieldGroupListE (C.AntiSdecls v _ : fields) =
    Just [|$(antiVarE v) ++ $(dataToExpQ qqExp fields)|]
qqFieldGroupListE (field : fields) =
    Just [|$(dataToExpQ qqExp field) : $(dataToExpQ qqExp fields)|]

qqCEnumE :: C.CEnum -> Maybe (Q Exp)
qqCEnumE (C.AntiEnum v _)  = Just $ antiVarE v
qqCEnumE _                 = Nothing

qqCEnumListE :: [C.CEnum] -> Maybe (Q Exp)
qqCEnumListE [] = Just [|[]|]
qqCEnumListE (C.AntiEnums v _ : fields) =
    Just [|$(antiVarE v) ++ $(dataToExpQ qqExp fields)|]
qqCEnumListE (field : fields) =
    Just [|$(dataToExpQ qqExp field) : $(dataToExpQ qqExp fields)|]

qqParamE :: C.Param -> Maybe (Q Exp)
qqParamE (C.AntiParam v _)  = Just $ antiVarE v
qqParamE _                  = Nothing

qqParamListE :: [C.Param] -> Maybe (Q Exp)
qqParamListE [] = Just [|[]|]
qqParamListE (C.AntiParams v _ : args) =
    Just [|$(antiVarE v) ++ $(dataToExpQ qqExp args)|]
qqParamListE (arg : args) =
    Just [|$(dataToExpQ qqExp arg) : $(dataToExpQ qqExp args)|]

qqDefinitionE :: C.Definition -> Maybe (Q Exp)
qqDefinitionE (C.AntiFunc v loc) =
    Just [|C.FuncDef $(antiVarE v) $(qqLocE loc)|]
qqDefinitionE (C.AntiEsc v loc) =
    Just [|C.EscDef $(antiVarE v) $(qqLocE loc)|]
qqDefinitionE (C.AntiEdecl v _) =
    Just $ antiVarE v
qqDefinitionE (C.AntiObjCMeth m _) =
    Just $ antiVarE m
qqDefinitionE _ = Nothing

qqDefinitionListE :: [C.Definition] -> Maybe (Q Exp)
qqDefinitionListE [] = Just [|[]|]
qqDefinitionListE (C.AntiEdecls v _ : defs) =
    Just [|$(antiVarE v) ++ $(dataToExpQ qqExp defs)|]
qqDefinitionListE (C.AntiObjCMeths m _ : meths) =
    Just [|$(antiVarE m) ++ $(dataToExpQ qqExp meths)|]
qqDefinitionListE (def : defs) =
    Just [|$(dataToExpQ qqExp def) : $(dataToExpQ qqExp defs)|]

qqConstE :: C.Const -> Maybe (Q Exp)
qqConstE = go
  where
    go (C.AntiConst v loc) =
        Just [|toConst $(antiVarE v) $(qqLocE loc) :: C.Const|]

    go (C.AntiInt v loc) =
        Just [|C.IntConst  $(intConst (antiVarE v)) C.Signed
                           (fromIntegral $(antiVarE v))
                           $(qqLocE loc)|]

    go (C.AntiUInt v loc) =
        Just [|C.IntConst  ($(intConst (antiVarE v)) ++ "U") C.Unsigned
                           (fromIntegral $(antiVarE v))
                           $(qqLocE loc)|]

    go (C.AntiLInt v loc) =
        Just [|C.LongIntConst  ($(intConst (antiVarE v)) ++ "L") C.Signed
                               (fromIntegral $(antiVarE v))
                               $(qqLocE loc)|]

    go (C.AntiULInt v loc) =
        Just [|C.LongIntConst  ($(intConst (antiVarE v)) ++ "UL") C.Unsigned
                               (fromIntegral $(antiVarE v))
                               $(qqLocE loc)|]

    go (C.AntiLLInt v loc) =
        Just [|C.LongLongIntConst  ($(intConst (antiVarE v)) ++ "LL") C.Signed
                               (fromIntegral $(antiVarE v))
                               $(qqLocE loc)|]

    go (C.AntiULLInt v loc) =
        Just [|C.LongLongIntConst  ($(intConst (antiVarE v)) ++ "ULL") C.Unsigned
                               (fromIntegral $(antiVarE v))
                               $(qqLocE loc)|]

    go (C.AntiFloat v loc) =
        Just [|toConst ($(antiVarE v) :: Float) $(qqLocE loc)|]

    go (C.AntiDouble v loc) =
        Just [|toConst ($(antiVarE v) :: Double) $(qqLocE loc)|]

    go (C.AntiLongDouble v loc) =
        Just [|toConst (LongDouble $(antiVarE v)) $(qqLocE loc)|]

    go (C.AntiChar v loc) =
        Just [|toConst $(antiVarE v) $(qqLocE loc)|]

    go (C.AntiString v loc) =
        Just [|C.StringConst [show $(antiVarE v)] $(antiVarE v) $(qqLocE loc)|]

    go _ = Nothing

    intConst :: ExpQ -> ExpQ
    intConst e = [|show $(e)|]

qqExpE :: C.Exp -> Maybe (Q Exp)
qqExpE (C.AntiExp v loc)    = Just [|toExp $(antiVarE v) $(qqLocE loc) :: C.Exp|]
qqExpE (C.AntiEscExp v loc) = Just [|C.EscExp $(antiVarE v) $(qqLocE loc) :: C.Exp|]
qqExpE _                    = Nothing

qqExpListE :: [C.Exp] -> Maybe (Q Exp)
qqExpListE [] = Just [|[]|]
qqExpListE (C.AntiArgs v loc : exps) =
    Just [|[toExp v $(qqLocE loc) | v <- $(antiVarE v)] ++
           $(dataToExpQ qqExp exps)|]
qqExpListE (exp : exps) =
    Just [|$(dataToExpQ qqExp exp) : $(dataToExpQ qqExp exps)|]

qqStmE :: C.Stm -> Maybe (Q Exp)
qqStmE (C.AntiEscStm v loc)      = Just [|C.EscStm $(antiVarE v) $(qqLocE loc)|]
qqStmE (C.AntiPragma v loc)      = Just [|C.Pragma $(antiVarE v) $(qqLocE loc)|]
qqStmE (C.AntiComment v stm loc) = Just [|C.Comment $(antiVarE v) $(dataToExpQ qqExp stm) $(qqLocE loc)|]
qqStmE (C.AntiStm v _)           = Just $ antiVarE v
qqStmE _                         = Nothing

qqStmListE :: [C.Stm] -> Maybe (Q Exp)
qqStmListE [] = Just [|[]|]
qqStmListE (C.AntiStms v _ : stms) =
    Just [|$(antiVarE v) ++ $(dataToExpQ qqExp stms)|]
qqStmListE (stm : stms) =
    Just [|$(dataToExpQ qqExp stm) : $(dataToExpQ qqExp stms)|]

qqBlockItemE :: C.BlockItem -> Maybe (Q Exp)
qqBlockItemE (C.AntiBlockItem v _) = Just $ antiVarE v
qqBlockItemE _                     = Nothing

qqBlockItemListE :: [C.BlockItem] -> Maybe (Q Exp)
qqBlockItemListE [] = Just [|[]|]
qqBlockItemListE (C.BlockDecl (C.AntiDecls v _) : items) =
    Just [|map C.BlockDecl $(antiVarE v) ++ $(dataToExpQ qqExp items)|]
qqBlockItemListE (C.BlockStm (C.AntiStms v _) : items) =
    Just [|map C.BlockStm $(antiVarE v) ++ $(dataToExpQ qqExp items)|]
qqBlockItemListE (C.AntiBlockItems v _ : items) =
    Just [|$(antiVarE v) ++ $(dataToExpQ qqExp items)|]
qqBlockItemListE (stm : stms) =
    Just [|$(dataToExpQ qqExp stm) : $(dataToExpQ qqExp stms)|]

qqObjcIfaceDeclE :: C.ObjCIfaceDecl -> Maybe (Q Exp)
qqObjcIfaceDeclE (C.AntiObjCProp p _) = Just $ antiVarE p
qqObjcIfaceDeclE _                    = Nothing

qqObjcIfaceDeclListE :: [C.ObjCIfaceDecl] -> Maybe (Q Exp)
qqObjcIfaceDeclListE [] = Just [|[]|]
qqObjcIfaceDeclListE (C.AntiObjCProps p _ : decls) =
    Just [|$(antiVarE p) ++ $(dataToExpQ qqExp decls)|]
qqObjcIfaceDeclListE (C.AntiObjCIfaceDecls v _ : decls) =
    Just [|$(antiVarE v) ++ $(dataToExpQ qqExp decls)|]
qqObjcIfaceDeclListE (C.AntiObjCIfaceDecl v _  : decls) =
    Just [|$(antiVarE v) : $(dataToExpQ qqExp decls)|]
qqObjcIfaceDeclListE (decl : decls) =
    Just [|$(dataToExpQ qqExp decl) : $(dataToExpQ qqExp decls)|]

qqObjCPropAttrE :: C.ObjCPropAttr -> Maybe (Q Exp)
qqObjCPropAttrE (C.AntiObjCAttr pa _) = Just $ antiVarE pa
qqObjCPropAttrE _                     = Nothing

qqObjCPropAttrListE :: [C.ObjCPropAttr] -> Maybe (Q Exp)
qqObjCPropAttrListE [] = Just [|[]|]
qqObjCPropAttrListE (C.AntiObjCAttrs pa _:attrelems) =
    Just [|$(antiVarE pa) ++ $(dataToExpQ qqExp attrelems)|]
qqObjCPropAttrListE (pattr : pattrs) =
    Just [|$(dataToExpQ qqExp pattr) : $(dataToExpQ qqExp pattrs)|]

qqObjCDictsE :: [C.ObjCDictElem] -> Maybe (Q Exp)
qqObjCDictsE [] = Just [|[]|]
qqObjCDictsE (C.AntiObjCDictElems e _:elems) =
    Just [|$(antiVarE e) ++ $(dataToExpQ qqExp elems)|]
qqObjCDictsE (elem : elems) =
    Just [|$(dataToExpQ qqExp elem) : $(dataToExpQ qqExp elems)|]

qqObjCParamE :: C.ObjCParam -> Maybe (Q Exp)
qqObjCParamE (C.AntiObjCParam p _) = Just $ antiVarE p
qqObjCParamE _                     = Nothing

qqObjCParamsE :: [C.ObjCParam] -> Maybe (Q Exp)
qqObjCParamsE [] = Just [|[]|]
qqObjCParamsE (C.AntiObjCParams p _: props) =
    Just [|$(antiVarE p) ++ $(dataToExpQ qqExp props)|]
qqObjCParamsE (param : params) =
    Just [|$(dataToExpQ qqExp param) : $(dataToExpQ qqExp params)|]

qqObjCMethodProtoE :: C.ObjCMethodProto -> Maybe (Q Exp)
qqObjCMethodProtoE (C.AntiObjCMethodProto p _) = Just $ antiVarE p
qqObjCMethodProtoE _                           = Nothing

qqObjCRecvE :: C.ObjCRecv -> Maybe (Q Exp)
qqObjCRecvE (C.AntiObjCRecv p _) = Just $ antiVarE p
qqObjCRecvE _                  = Nothing

qqObjCArgE :: C.ObjCArg -> Maybe (Q Exp)
qqObjCArgE (C.AntiObjCArg p _) = Just $ antiVarE p
qqObjCArgE _                  = Nothing

qqObjCArgsE :: [C.ObjCArg] -> Maybe (Q Exp)
qqObjCArgsE [] = Just [|[]|]
qqObjCArgsE (C.AntiObjCArgs a _: args) =
    Just [|$(antiVarE a) ++ $(dataToExpQ qqExp args)|]
qqObjCArgsE (arg : args) =
    Just [|$(dataToExpQ qqExp arg) : $(dataToExpQ qqExp args)|]

qqExp :: Typeable a => a -> Maybe (Q Exp)
qqExp = const Nothing  `extQ` qqStringE
                       `extQ` qqIdE
                       `extQ` qqDeclSpecE
                       `extQ` qqDeclE
                       `extQ` qqTypeQualE
                       `extQ` qqTypeQualListE
                       `extQ` qqTypeE
                       `extQ` qqInitializerE
                       `extQ` qqInitializerListE
                       `extQ` qqInitGroupE
                       `extQ` qqInitGroupListE
                       `extQ` qqAttrE
                       `extQ` qqAttrListE
                       `extQ` qqFieldGroupE
                       `extQ` qqFieldGroupListE
                       `extQ` qqCEnumE
                       `extQ` qqCEnumListE
                       `extQ` qqParamE
                       `extQ` qqParamListE
                       `extQ` qqDefinitionE
                       `extQ` qqDefinitionListE
                       `extQ` qqConstE
                       `extQ` qqExpE
                       `extQ` qqExpListE
                       `extQ` qqStmE
                       `extQ` qqStmListE
                       `extQ` qqBlockItemE
                       `extQ` qqBlockItemListE
                       `extQ` qqObjcIfaceDeclE
                       `extQ` qqObjcIfaceDeclListE
                       `extQ` qqObjCPropAttrE
                       `extQ` qqObjCPropAttrListE
                       `extQ` qqObjCDictsE
                       `extQ` qqObjCParamE
                       `extQ` qqObjCParamsE
                       `extQ` qqObjCMethodProtoE
                       `extQ` qqObjCRecvE
                       `extQ` qqObjCArgE
                       `extQ` qqObjCArgsE

antiVarP :: String -> PatQ
antiVarP = either fail return . parsePat

qqStringP :: String -> Maybe (Q Pat)
qqStringP s = Just $ litP $ stringL s

qqLocP :: Data.Loc.Loc -> Maybe (Q Pat)
qqLocP _ = Just wildP

qqIdP :: C.Id -> Maybe (Q Pat)
qqIdP (C.AntiId v _) = Just $ conP (mkName "C.Id") [antiVarP v, wildP]
qqIdP _              = Nothing

qqDeclSpecP :: C.DeclSpec -> Maybe (Q Pat)
qqDeclSpecP (C.AntiDeclSpec v _) = Just $ antiVarP v
qqDeclSpecP C.AntiTypeDeclSpec{} =
    error "Illegal antiquoted type in pattern"
qqDeclSpecP _ = Nothing

qqDeclP :: C.Decl -> Maybe (Q Pat)
qqDeclP C.AntiTypeDecl{} =
    error "Illegal antiquoted type in pattern"
qqDeclP _ = Nothing

qqTypeQualP :: C.TypeQual -> Maybe (Q Pat)
qqTypeQualP (C.AntiTypeQual v _) = Just $ antiVarP v
qqTypeQualP _                    = Nothing

qqTypeQualListP :: [C.TypeQual] -> Maybe (Q Pat)
qqTypeQualListP [] = Just $ listP []
qqTypeQualListP [C.AntiTypeQuals v _] = Just $ antiVarP v
qqTypeQualListP (C.AntiTypeQuals{} : _ : _) =
    error "Antiquoted list of type qualifiers must be last item in quoted list"
qqTypeQualListP (arg : args) =
    Just $ conP (mkName ":") [dataToPatQ qqPat arg, dataToPatQ qqPat args]

qqTypeP :: C.Type -> Maybe (Q Pat)
qqTypeP (C.AntiType v _)  = Just $ antiVarP v
qqTypeP _                 = Nothing

qqInitializerP :: C.Initializer -> Maybe (Q Pat)
qqInitializerP (C.AntiInit v _)  = Just $ antiVarP v
qqInitializerP _                 = Nothing

qqInitializerListP :: [C.Initializer] -> Maybe (Q Pat)
qqInitializerListP [] = Just $ listP []
qqInitializerListP [C.AntiInits v _] = Just $ antiVarP v
qqInitializerListP (C.AntiInits{} : _ : _) =
    error "Antiquoted list of initializers must be last item in quoted list"
qqInitializerListP (ini : inis) =
    Just $ conP (mkName ":") [dataToPatQ qqPat ini,  dataToPatQ qqPat inis]

qqInitGroupP :: C.InitGroup -> Maybe (Q Pat)
qqInitGroupP (C.AntiDecl v _) = Just $ antiVarP v
qqInitGroupP _                = Nothing

qqInitGroupListP :: [C.InitGroup] -> Maybe (Q Pat)
qqInitGroupListP [] = Just $ listP []
qqInitGroupListP [C.AntiDecls v _] = Just $ antiVarP v
qqInitGroupListP (C.AntiDecls{} : _ : _) =
    error "Antiquoted list of initialization groups must be last item in quoted list"
qqInitGroupListP (ini : inis) =
    Just $ conP (mkName ":") [dataToPatQ qqPat ini,  dataToPatQ qqPat inis]

qqAttrP :: C.Attr -> Maybe (Q Pat)
qqAttrP (C.AntiAttr v _)  = Just $ antiVarP v
qqAttrP _                 = Nothing

qqAttrListP :: [C.Attr] -> Maybe (Q Pat)
qqAttrListP [] = Just $ listP []
qqAttrListP [C.AntiAttrs v _] = Just $ antiVarP v
qqAttrListP (C.AntiAttrs{} : _ : _) =
   error "Antiquoted list of attrs must be last item in quoted list"
qqAttrListP (ini : inis) =
    Just $ conP (mkName ":") [dataToPatQ qqPat ini,  dataToPatQ qqPat inis]

qqFieldGroupP :: C.FieldGroup -> Maybe (Q Pat)
qqFieldGroupP (C.AntiSdecl v _) = Just $ antiVarP v
qqFieldGroupP _                 = Nothing

qqFieldGroupListP :: [C.FieldGroup] -> Maybe (Q Pat)
qqFieldGroupListP [] = Just $ listP []
qqFieldGroupListP [C.AntiSdecls v _] = Just $ antiVarP v
qqFieldGroupListP (C.AntiSdecls{} : _ : _) =
    error "Antiquoted list of struct/union fields must be last item in quoted list"
qqFieldGroupListP (ini : inis) =
    Just $ conP (mkName ":") [dataToPatQ qqPat ini,  dataToPatQ qqPat inis]

qqCEnumP :: C.CEnum -> Maybe (Q Pat)
qqCEnumP (C.AntiEnum v _) = Just $ antiVarP v
qqCEnumP _                = Nothing

qqCEnumListP :: [C.CEnum] -> Maybe (Q Pat)
qqCEnumListP [] = Just $ listP []
qqCEnumListP [C.AntiEnums v _] = Just $ antiVarP v
qqCEnumListP (C.AntiEnums{} : _ : _) =
    error "Antiquoted list of enumerations must be last item in quoted list"
qqCEnumListP (ini : inis) =
    Just $ conP (mkName ":") [dataToPatQ qqPat ini,  dataToPatQ qqPat inis]

qqParamP :: C.Param -> Maybe (Q Pat)
qqParamP (C.AntiParam v _) = Just $ antiVarP v
qqParamP _                 = Nothing

qqParamListP :: [C.Param] -> Maybe (Q Pat)
qqParamListP [] = Just $ listP []
qqParamListP [C.AntiParams v _] = Just $ antiVarP v
qqParamListP (C.AntiParams{} : _ : _) =
    error "Antiquoted list of parameters must be last item in quoted list"
qqParamListP (arg : args) =
    Just $ conP (mkName ":") [dataToPatQ qqPat arg,  dataToPatQ qqPat args]

qqDefinitionP :: C.Definition -> Maybe (Q Pat)
qqDefinitionP (C.AntiFunc v _)  = Just $ conP (mkName "C.FuncDef") [antiVarP v, wildP]
qqDefinitionP (C.AntiEsc v _)   = Just $ conP (mkName "C.EscDef") [antiVarP v, wildP]
qqDefinitionP (C.AntiEdecl v _) = Just $ antiVarP v
qqDefinitionP _                 = Nothing

qqDefinitionListP :: [C.Definition] -> Maybe (Q Pat)
qqDefinitionListP [] = Just $ listP []
qqDefinitionListP [C.AntiEdecls v _] = Just $ antiVarP v
qqDefinitionListP (C.AntiEdecls{} : _ : _) =
    error "Antiquoted list of definitions must be last item in quoted list"
qqDefinitionListP (arg : args) =
    Just $ conP (mkName ":") [dataToPatQ qqPat arg,  dataToPatQ qqPat args]

qqConstP :: C.Const -> Maybe (Q Pat)
qqConstP = go
  where
    go (C.AntiInt v _) =
        Just $ con "C.IntConst" [wildP, signed, antiVarP v, wildP]
    go (C.AntiUInt v _) =
        Just $ con "C.IntConst" [wildP, unsigned, antiVarP v, wildP]
    go (C.AntiLInt v _) =
        Just $ con "C.LongIntConst" [wildP, signed, antiVarP v, wildP]
    go (C.AntiULInt v _) =
        Just $ con "C.LongIntConst" [wildP, unsigned, antiVarP v, wildP]
    go (C.AntiFloat v _) =
        Just $ con "C.FloatConst" [wildP, antiVarP v, wildP]
    go (C.AntiDouble v _) =
        Just $ con "C.DoubleConst" [wildP, antiVarP v, wildP]
    go (C.AntiLongDouble v _) =
        Just $ con "C.LongDoubleConst" [wildP, antiVarP v, wildP]
    go (C.AntiChar v _) =
        Just $ con "C.CharConst" [wildP, antiVarP v, wildP]
    go (C.AntiString v _) =
        Just $ con "C.StringConst" [wildP, antiVarP v, wildP]
    go _ =
        Nothing

    con n = conP (mkName n)

    signed   = conP (mkName "C.Signed") []
    unsigned = conP (mkName "C.Unsigned") []

qqExpP :: C.Exp -> Maybe (Q Pat)
qqExpP (C.AntiExp v _)    = Just $ antiVarP v
qqExpP (C.AntiEscExp v _) = Just $ conP (mkName "C.EscExp") [antiVarP v, wildP]
qqExpP _                  = Nothing

qqExpListP :: [C.Exp] -> Maybe (Q Pat)
qqExpListP [] = Just $ listP []
qqExpListP [C.AntiArgs v _] = Just $ antiVarP v
qqExpListP (C.AntiArgs{} : _ : _) =
    error "Antiquoted list of arguments must be last item in quoted list"
qqExpListP (arg : args) =
    Just $ conP (mkName ":") [dataToPatQ qqPat arg,  dataToPatQ qqPat args]

qqStmP :: C.Stm -> Maybe (Q Pat)
qqStmP (C.AntiStm v _)    = Just $ antiVarP v
qqStmP (C.AntiEscStm v _) = Just $ conP (mkName "C.EscStm") [antiVarP v, wildP]
qqStmP _                  = Nothing

qqStmListP :: [C.Stm] -> Maybe (Q Pat)
qqStmListP [] = Just $ listP []
qqStmListP [C.AntiStms v _] = Just $ antiVarP v
qqStmListP (C.AntiStms{} : _ : _) =
    error "Antiquoted list of statements must be last item in quoted list"
qqStmListP (arg : args) =
    Just $ conP (mkName ":") [dataToPatQ qqPat arg,  dataToPatQ qqPat args]

qqBlockItemP :: C.BlockItem -> Maybe (Q Pat)
qqBlockItemP (C.AntiBlockItem v _) = Just $ antiVarP v
qqBlockItemP _                     = Nothing

qqBlockItemListP :: [C.BlockItem] -> Maybe (Q Pat)
qqBlockItemListP [] = Just $ listP []
qqBlockItemListP (C.BlockDecl C.AntiDecls{} : _) =
    error "Antiquoted list of declarations cannot appear in block"
qqBlockItemListP (C.BlockStm C.AntiStms{} : _) =
    error "Antiquoted list of statements cannot appear in block"
qqBlockItemListP [C.AntiBlockItems v _] = Just $ antiVarP v
qqBlockItemListP (C.AntiBlockItems{} : _ : _) =
    error "Antiquoted list of block items must be last item in quoted list"
qqBlockItemListP (arg : args) =
    Just $ conP (mkName ":") [dataToPatQ qqPat arg,  dataToPatQ qqPat args]

qqPat :: Typeable a => a -> Maybe (Q Pat)
qqPat = const Nothing `extQ` qqStringP
                      `extQ` qqLocP
                      `extQ` qqIdP
                      `extQ` qqDeclSpecP
                      `extQ` qqDeclP
                      `extQ` qqTypeQualP
                      `extQ` qqTypeQualListP
                      `extQ` qqTypeP
                      `extQ` qqInitializerP
                      `extQ` qqInitializerListP
                      `extQ` qqInitGroupP
                      `extQ` qqInitGroupListP
                      `extQ` qqAttrP
                      `extQ` qqAttrListP
                      `extQ` qqFieldGroupP
                      `extQ` qqCEnumP
                      `extQ` qqCEnumListP
                      `extQ` qqParamP
                      `extQ` qqParamListP
                      `extQ` qqDefinitionP
                      `extQ` qqDefinitionListP
                      `extQ` qqConstP
                      `extQ` qqExpP
                      `extQ` qqExpListP
                      `extQ` qqStmP
                      `extQ` qqStmListP
                      `extQ` qqBlockItemP
                      `extQ` qqBlockItemListP

parse :: [C.Extensions]
      -> [String]
      -> P.P a
      -> String
      -> Q a
parse exts typenames p s = do
    loc <- location
    case P.parse (C.Antiquotation : exts) typenames p (B.pack s) (Just (locToPos loc)) of
      Left err -> fail (show err)
      Right x  -> return x
  where
    locToPos :: TH.Loc -> Pos
    locToPos TH.Loc {loc_filename = filename, loc_start = (line, col)} =
        Pos filename line col 0

quasiquote :: Data a
           => [C.Extensions]
           -> [String]
           -> P.P a
           -> QuasiQuoter
quasiquote exts typenames p =
    QuasiQuoter { quoteExp  = parse exts typenames p >=> dataToExpQ qqExp
                , quotePat  = parse exts typenames p >=> dataToPatQ qqPat
                , quoteType = error "C type quasiquoter undefined"
                , quoteDec  = error "C declaration quasiquoter undefined"
                }

#if !MIN_VERSION_template_haskell(2,7,0)
dataToQa  ::  forall a k q. Data a
          =>  (Name -> k)
          ->  (Lit -> Q q)
          ->  (k -> [Q q] -> Q q)
          ->  (forall b . Data b => b -> Maybe (Q q))
          ->  a
          ->  Q q
dataToQa mkCon mkLit appCon antiQ t =
    case antiQ t of
      Nothing ->
          case constrRep constr of
            AlgConstr _  ->
                appCon con conArgs
            IntConstr n ->
                mkLit $ integerL n
            FloatConstr n ->
                mkLit $ rationalL (toRational n)
            CharConstr c ->
                mkLit $ charL c
        where
          constr :: Constr
          constr = toConstr t

          con :: k
          con = mkCon (mkConName mod occ)
            where
              mod :: String
              mod = (tyconModule . dataTypeName . dataTypeOf) t

              occ :: String
              occ = showConstr constr

              mkConName :: String -> String -> Name
              mkConName "Prelude" "(:)" = Name (mkOccName ":") NameS
              mkConName "Prelude" "[]"  = Name (mkOccName "[]") NameS
              mkConName "Prelude" "()"  = Name (mkOccName "()") NameS

              mkConName "Prelude" s@('(' : ',' : rest) = go rest
                where
                  go :: String -> Name
                  go (',' : rest) = go rest
                  go ")"          = Name (mkOccName s) NameS
                  go _            = Name (mkOccName occ) (NameQ (mkModName mod))

              mkConName "GHC.Real" ":%" = mkNameG_d "base" "GHC.Real" ":%"

              mkConName mod occ = Name (mkOccName occ) (NameQ (mkModName mod))

          conArgs :: [Q q]
          conArgs = gmapQ (dataToQa mkCon mkLit appCon antiQ) t

      Just y -> y

-- | 'dataToExpQ' converts a value to a 'Q Exp' representation of the same
-- value. It takes a function to handle type-specific cases.
dataToExpQ  ::  Data a
            =>  (forall b . Data b => b -> Maybe (Q Exp))
            ->  a
            ->  Q Exp
dataToExpQ = dataToQa conE litE (foldl appE)

-- | 'dataToPatQ' converts a value to a 'Q Pat' representation of the same
-- value. It takes a function to handle type-specific cases.
dataToPatQ  ::  Data a
            =>  (forall b . Data b => b -> Maybe (Q Pat))
            ->  a
            ->  Q Pat
dataToPatQ = dataToQa id litP conP
#endif /* !MIN_VERSION_template_haskell(2,7,0) */
