{-# LANGUAGE PatternSynonyms, PatternGuards, TemplateHaskell, GADTs, KindSignatures, DataKinds #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Database.PostgreSQL.Typed.Internal
  ( stringE
  , pattern StringE
  , SQLSplit(..)
  , sqlSplitExprs
  , sqlSplitParams
  ) where

import Data.Char (isDigit)
import Data.String (IsString(..))
import qualified Language.Haskell.TH as TH
import Numeric (readDec)

-- |@'TH.LitE' . 'TH.stringL'@
stringE :: String -> TH.Exp
stringE = TH.LitE . TH.StringL

-- |Pattern match for 'stringE'.
pattern StringE s = TH.LitE (TH.StringL s)

instance IsString TH.Exp where
  fromString = stringE

-- |Parsed representation of a SQL statement containing placeholders.
data SQLSplit a (literal :: Bool) where
  SQLLiteral :: String -> SQLSplit a 'False -> SQLSplit a 'True
  SQLPlaceholder :: a -> SQLSplit a 'True -> SQLSplit a 'False
  SQLSplitEnd :: SQLSplit a any

sqlCons :: Char -> SQLSplit a 'True -> SQLSplit a 'True
sqlCons c (SQLLiteral s l) = SQLLiteral (c : s) l
sqlCons c SQLSplitEnd = SQLLiteral [c] SQLSplitEnd

-- |Parse a SQL stamement with ${string} placeholders into a 'SQLSplit'.
sqlSplitExprs :: String -> SQLSplit String 'True
sqlSplitExprs ('$':'$':'{':s) = sqlCons '$' $ sqlCons '{' $ sqlSplitExprs s
sqlSplitExprs ('$':'{':s)
  | (e, '}':r) <- break (\c -> c == '{' || c == '}') s = SQLLiteral "" $ SQLPlaceholder e $ sqlSplitExprs r
  | otherwise = error $ "Error parsing SQL: could not find end of expression: ${" ++ s
sqlSplitExprs (c:s) = sqlCons c $ sqlSplitExprs s
sqlSplitExprs [] = SQLSplitEnd

-- |Parse a SQL stamement with $numeric placeholders into a 'SQLSplit'.
sqlSplitParams :: String -> SQLSplit Int 'True
sqlSplitParams ('$':'$':d:s) | isDigit d = sqlCons '$' $ sqlCons d $ sqlSplitParams s
sqlSplitParams ('$':s@(d:_)) | isDigit d, [(n, r)] <- readDec s = SQLLiteral "" $ SQLPlaceholder n $ sqlSplitParams r
sqlSplitParams (c:s) = sqlCons c $ sqlSplitParams s
sqlSplitParams [] = SQLSplitEnd