module Components.Parsers.QueryParser (processString,validateQuery,parseStringToObjects) where

import Model.ServerExceptions (
        QueryException(
            SyntaxException,
            InvalidScalarException,
            InvalidObjectException,
            InvalidVariableNameException,
            EmptyQueryException,
            ParseFragmentException,
            MismatchedVariableTypeException
        )
    )
import Model.ServerObjectTypes (
        NestedObject(..),
        ServerObject,
        Alias,
        Argument,
        Transformation,
        ScalarType(..),
        RootObjects,
        RootObject,
        Name,
        SubFields,
        InlinefragmentObject(..),
        Field,
        FieldObject,
        SubSelection
    )
import Components.ObjectHandlers.ObjectsHandler (readServerObject)
import Control.Exception (throw)
import Data.Char (toLower)


{-----Step 1. PROCESSING-----}
processString :: String -> String
processString str = removeComments $ removeLinebreaks str
-- REQUIREMENTS: Windows and Mac are one of \r\n, \n, or \r to make a new line break. Another OS is maybe different.
removeComments :: String -> String
removeComments str = removeCommentsHelper str False
removeCommentsHelper :: String -> Bool -> String
removeCommentsHelper [] _ = []
removeCommentsHelper (h:[]) _ = h:[]
removeCommentsHelper (h:t) mde
 | h=='#' = removeCommentsHelper t True
 | h=='\r' = '\r':removeCommentsHelper t False
 | h=='\n' = '\n':removeCommentsHelper t False
 | mde==True = removeCommentsHelper t mde
 | otherwise = h:(removeCommentsHelper t mde)
-- NOTE: this is used with only the textarea field of forms since they are giving line breaks these combinations...
removeLinebreaks :: String -> String
removeLinebreaks [] = []
removeLinebreaks (h:t)
 | h=='\n'   = ' ':removeLinebreaks t
 | h=='\r'   = ' ':removeLinebreaks t
 | otherwise = h:removeLinebreaks t


{-----Step 2. VALIDATION-----}
validateQuery :: String -> Bool
validateQuery [] = False
validateQuery str = (validateBracketLocationQuery str)&&(validateNoEmptyBrackets str)
-- this is first validation to check that we have equal opening/closing brackets, and we do not close before opening
validateBracketLocationQuery :: String -> Bool
validateBracketLocationQuery str = validateBracketLocationQueryHelper str 0 0
validateBracketLocationQueryHelper :: String -> Int -> Int -> Bool
validateBracketLocationQueryHelper [] x y = (x==y)
validateBracketLocationQueryHelper (h:t) o c
 | h=='{'       = validateBracketLocationQueryHelper t (o+1) c
 | h=='}'&&o<=c = False
 | h=='}'       = validateBracketLocationQueryHelper t o (c+1)
 | otherwise    = validateBracketLocationQueryHelper t o c
validateNoEmptyBrackets :: String -> Bool
validateNoEmptyBrackets str = validateNoEmptyBracketsHelper str "" []
validateNoEmptyBracketsHelper :: String -> String -> [String] -> Bool
validateNoEmptyBracketsHelper [] acc nst = (length nst)<1
validateNoEmptyBracketsHelper (a:b) acc []
 | a=='{' = validateNoEmptyBracketsHelper b [] [acc]
 | a=='}' = False
 | a==' ' = validateNoEmptyBracketsHelper b acc []
 | otherwise = validateNoEmptyBracketsHelper b (acc++[a]) []
validateNoEmptyBracketsHelper (a:b) acc (i:j)
 | a==' ' = validateNoEmptyBracketsHelper b acc (i:j)
 | a=='{'&&(length acc)==0 = False
 | a=='{'                  = validateNoEmptyBracketsHelper b [] (acc:i:j)
 | a=='}'&&(length acc)==0 = False
 | a=='}'                  = validateNoEmptyBracketsHelper b i j
 | otherwise               = validateNoEmptyBracketsHelper b (acc++[a]) (i:j)


{-----Step 3. PARSING-----}
parseStringToObjects :: String -> [(String,[String])] -> [(String,[String],[String])] -> [(String,String,String)] -> RootObjects
parseStringToObjects [] _ _ _ = throw EmptyQueryException
parseStringToObjects str svrobjs soa vars = composeObjects qry svrobjs soa vars fragments
  where
    (qry,fmts) = getQueryAndFragments str
    fragments = parseFragments fmts svrobjs soa
    -- query = substituteFragments qry fragments svrobjs vars
-- REQUIRES: curly braces are in correct order
getQueryAndFragments :: String -> (String, String)
getQueryAndFragments str = getQueryAndFragmentsHelper str 0 False "" ""
getQueryAndFragmentsHelper :: String -> Int -> Bool -> String -> String -> (String, String)
getQueryAndFragmentsHelper [] _ _ x y = (x, y)
getQueryAndFragmentsHelper (h:t) l m q f
 | h=='{'&&m==False = getQueryAndFragmentsHelper t (l+1) m (q++[h]) f
 | h=='}'&&l==1&&m==False = getQueryAndFragmentsHelper t (l-1) True (q++[h]) f
 | h=='}'&&m==False = getQueryAndFragmentsHelper t (l-1) m (q++[h]) f
 | m==False = getQueryAndFragmentsHelper t l m (q++[h]) f
 | otherwise = getQueryAndFragmentsHelper t l m q (f++[h])
data Fragment = Fragment
    { name :: String
    , targetObject :: ServerObject
    , replacement :: String
    } deriving Show
parseFragments :: String -> [(String,[String])] -> [(String,[String],[String])] -> [Fragment]
parseFragments str svrobjs soa = parseFragmentsHelper str "" 0 [] svrobjs soa
parseFragmentsHelper :: String -> String -> Int -> [Fragment] -> [(String,[String])] -> [(String,[String],[String])] -> [Fragment]
parseFragmentsHelper [] _ _ rslt _ _ = rslt
parseFragmentsHelper (h:t) acc l rslt svrobjs soa
 | h=='{' = parseFragmentsHelper t (acc++[h]) (l+1) rslt svrobjs soa
 | h=='}'&&l==1 = parseFragmentsHelper t [] (l-1) ((createFragment acc svrobjs soa):rslt) svrobjs soa -- completed one fragment
 | h=='}' = parseFragmentsHelper t (acc++[h]) (l-1) rslt svrobjs soa  -- closed a nested object
 | otherwise = parseFragmentsHelper t (acc++[h]) l rslt svrobjs soa
-- with a fragment string that is without closing curly braces, we want a fragments
createFragment :: String -> [(String,[String])] -> [(String,[String],[String])] -> Fragment
createFragment str svrobjs soa = createFragmentHelper str 0 [] False False False False "" "" svrobjs soa
{-
    d is for declaration
    n is for name
    a is for arrangement
    o is for object
-}
createFragmentHelper :: String -> Int -> String -> Bool -> Bool -> Bool -> Bool -> String -> String -> [(String,[String])] -> [(String,[String],[String])] -> Fragment
createFragmentHelper [] l acc d n a o rst1 rst2 svrobjs soa = if (l==1&&d==True&&n==True&&a==True&&o==True) then Fragment { name=rst1,targetObject=(readServerObject rst2 svrobjs soa),replacement=acc } else throw ParseFragmentException
createFragmentHelper (h:t) l acc d n a o rst1 rst2 svrobjs soa
 | d==False&&(h=='f'||h=='r'||h=='a'||h=='g'||h=='m'||h=='e'||h=='n'||h=='t') = createFragmentHelper t l (acc++[h]) d n a o rst1 rst2 svrobjs soa
 | d==False&&h==' '&&(length acc)>0&&acc=="fragment" = createFragmentHelper t l [] True n a o rst1 rst2 svrobjs soa
 | d==False&&h==' '&&(length acc)>0 = throw ParseFragmentException
 | d==False&&h==' '&&(length acc)<1 = createFragmentHelper t l [] d n a o rst1 rst2 svrobjs soa
 | d==False = throw ParseFragmentException
 | n==False&&h==' '&&(length acc)==0 = createFragmentHelper t l [] d n a o rst1 rst2 svrobjs soa
 | n==False&&h==' '&&(length acc)>0 = createFragmentHelper t l [] d True a o acc rst2 svrobjs soa
 | n==False&&(isValidFragmentNameChar h)==False = throw ParseFragmentException
 | n==False = createFragmentHelper t l (acc++[h]) d n a o rst1 rst2 svrobjs soa
 | a==False&&h==' '&&(length acc)==0 = createFragmentHelper t l [] d n a o rst1 rst2 svrobjs soa
 | a==False&&h==' '&&(length acc)>0&&(acc=="on") = createFragmentHelper t l [] d n True o rst1 rst2 svrobjs soa
 | a==False&&h==' '&&(length acc)>0 = throw ParseFragmentException
 | a==False&&(h=='o'||h=='n') = createFragmentHelper t l (acc++[h]) d n a o rst1 rst2 svrobjs soa
 | a==False = throw ParseFragmentException
 | o==False&&((length acc)==0)&&((fromEnum h)>=97||(fromEnum h)<=122) = createFragmentHelper t l (acc++[h]) d n a o rst1 rst2 svrobjs soa
 | o==False&&((length acc)==0)&&(h==' ') = createFragmentHelper t l [] d n a o rst1 rst2 svrobjs soa
 | o==False&&((length acc)==0) = throw ParseFragmentException
 | o==False&&h==' ' = createFragmentHelper t l [] d n a True rst1 acc svrobjs soa
 | o==False&&h=='{' = createFragmentHelper t (l+1) [] d n a True rst1 acc svrobjs soa
 | o==False&&(isValidIdentifierChar h) = createFragmentHelper t l (acc++[h]) d n a o rst1 rst2 svrobjs soa
 | o==False = throw ParseFragmentException
 | h==' '&&l==0 = createFragmentHelper t l [] d n a o rst1 rst2 svrobjs soa
 | h=='{'&&l==0 = createFragmentHelper t (l+1) [] d n a o rst1 rst2 svrobjs soa
 | l==0 = throw ParseFragmentException
 | (isValidIdentifierChar h)||h==' '||h==')'||h=='('||h==':'||h=='$'||h=='@' = createFragmentHelper t l (acc++[h]) d n a o rst1 rst2 svrobjs soa
 | h=='{' = createFragmentHelper t (l+1) (acc++[h]) d n a o rst1 rst2 svrobjs soa
 | h=='}' = createFragmentHelper t (l-1) (acc++[h]) d n a o rst1 rst2 svrobjs soa
 | otherwise = throw ParseFragmentException
isValidFragmentNameChar :: Char -> Bool
isValidFragmentNameChar c = ((fromEnum c)>=65&&(fromEnum c)<=90)||((fromEnum c)>=97&&(fromEnum c)<=122)||((fromEnum c)>=48&&(fromEnum c)<=57)||((fromEnum c)==95)
isValidIdentifierChar :: Char -> Bool
isValidIdentifierChar c = ((fromEnum c)>=65&&(fromEnum c)<=90)||((fromEnum c)>=97&&(fromEnum c)<=122)||((fromEnum c)>=48&&(fromEnum c)<=57)||((fromEnum c)==95)||((fromEnum c)==39)
--  -- call after infering types on nested objects
-- get block in this scope
splitSubject :: String -> String -> Int -> (String,String)
splitSubject [] acc _ = (acc,"")
splitSubject (h:t) acc l
 | l<0 = (acc,h:t)
 | h=='{' = splitSubject t (acc++[h]) (l+1)
 | h=='}' = splitSubject t (acc++[h]) (l-1)
 | otherwise = splitSubject t (acc++[h]) l
-- -- substitute target string with replacement string within subject string...return result
-- parse provided string to obtain query
{-
REQUIRES: Query is balanced and ordered brackets.
input is whole query string with opening and closing brackets
EFFECTS: Return value is list of desired objects with specifications
passing code block to separateRootObjects() where code block is not including query opening and closing brackets
TODO: change Bool to Either with exceptions
-}
composeObjects :: String -> [(String,[String])] -> [(String,[String],[String])] -> [(String,String,String)] -> [Fragment] -> RootObjects
composeObjects [] _ _ _ _ = throw EmptyQueryException
composeObjects str svrobjs soa vars fmts = composeObjectsHelper str 0 svrobjs soa vars fmts
composeObjectsHelper :: String -> Int -> [(String,[String])] -> [(String,[String],[String])] -> [(String,String,String)] -> [Fragment] -> RootObjects
composeObjectsHelper [] _ _ _ _ _ = throw EmptyQueryException
composeObjectsHelper (h:t) l svrobjs soa vars fmts
 | h=='{'&&l==0 = separateRootObjects (extractLevel t) svrobjs soa vars fmts -- find and separate every root object
 | otherwise = composeObjectsHelper t l svrobjs soa vars fmts
-- ...separate and determine operation
-- TODO: implement operations
-- determineOperation :: String -> (Operation,String)
-- determineOperation str = determineOperationHelper str ""
-- determineOperationHelper :: String -> String -> String
-- determineOperationHelper [] acc = ((parseOperation acc1),[]) -- TODO: throw exception on empty query
-- determineOperationHelper (h:t) acc
--  | h=='{' = ((parseOperation acc), (removeLevel t))
--  | otherwise = determineOperationHelper t (acc++[h])
-- ...create several RootObjects from query blocks
-- REQUIRES: brackets are balanced and ordered
-- NOTE: only querying is first supported; mutations are later
-- EFFECTS: passing block to createNestedObject where block is including opening and closing curly brackets
separateRootObjects :: String -> [(String,[String])] -> [(String,[String],[String])] -> [(String,String,String)] -> [Fragment] -> [RootObject]
separateRootObjects str svrobjs soa vars fmts = separateRootObjectsHelper str "" svrobjs soa vars fmts
separateRootObjectsHelper :: String -> String -> [(String,[String])] -> [(String,[String],[String])] -> [(String,String,String)] -> [Fragment] -> [RootObject]
separateRootObjectsHelper [] _ _ _ _ _ = []
separateRootObjectsHelper (h:t) acc svrobjs soa vars fmts
    | h=='{' = (((createNestedObject (acc++[h]++level) svrobjs soa vars fmts) :: RootObject):separateRootObjectsHelper levelTail "" svrobjs soa vars fmts)
    | otherwise = separateRootObjectsHelper t (acc++[h]) svrobjs soa vars fmts
  where
    (level,levelTail) = splitLevel t "" 0
-- create root object from block
-- EFFECTS: passing code block to parseSubFields where block is not including root object opening and closing curly brackets.
createNestedObject :: String -> [(String,[String])] -> [(String,[String],[String])] -> [(String,String,String)] -> [Fragment] -> NestedObject
createNestedObject str svrobjs soa vars fmts = createNestedObjectHelper str "" svrobjs soa vars fmts
createNestedObjectHelper :: String -> String -> [(String,[String])] -> [(String,[String],[String])] -> [(String,String,String)] -> [Fragment] -> NestedObject
createNestedObjectHelper [] _ _ _ _ _ = throw InvalidObjectException  -- we should not encounter this since we already checked against empty brackets
createNestedObjectHelper (h:t) acc svrobjs soa vars fmts
    | h=='{' = (NestedObject ((parseAlias acc) :: Alias) ((parseName acc) :: Name) serverObj ((parseSubSelection acc) :: SubSelection) ((parseSubFields (extractLevel t) svrobjs soa vars fmts serverObj) :: SubFields)) :: RootObject
    | otherwise = createNestedObjectHelper t (acc++[h]) svrobjs soa vars fmts
  where
    serverObj = ((parseServerObject acc svrobjs soa) :: ServerObject)
-- given object header without any braces, we want a name.
parseServerObject :: String -> [(String,[String])] -> [(String,[String],[String])] -> ServerObject
parseServerObject [] svrobjs soa = readServerObject "" svrobjs soa
parseServerObject str svrobjs soa
 | (elem ':' str)==True&&(elem '(' str)==True = readServerObject (removeSpaces $ foldl (\y x -> if x==':' then [] else (y++[x])) "" (foldr (\x y -> if x=='(' then [] else x:y) "" str)) svrobjs soa
 | (elem ':' str)==True = readServerObject (removeSpaces $ foldl (\y x -> if x==':' then [] else (y++[x])) "" str) svrobjs soa
 | otherwise = readServerObject (removeSpaces str) svrobjs soa
-- given object header without any braces, we want the alias if there is one.
parseAlias :: String -> Alias
parseAlias [] = Nothing :: Alias
parseAlias str
    | (elem ':' str)&&(elem '(' str) = parseAlias $ foldr (\x y -> if x=='(' then [] else x:y) "" str
    | (elem ':' str) = Just $ removeSpaces $ foldr (\x y -> if x==':' then [] else x:y) "" str
    | otherwise = Nothing :: Alias
parseName :: String -> Name
parseName [] = []
parseName str
 | (elem ':' str)==True&&(elem '(' str)==True = removeSpaces $ foldl (\y x -> if x==':' then [] else (y++[x])) [] (foldr (\x y -> if x=='(' then [] else x:y) "" str)
 | (elem ':' str)==True = removeSpaces $ foldl (\y x -> if x==':' then [] else (y++[x])) "" str
 | otherwise = removeSpaces str
parseSubSelection :: String -> SubSelection
parseSubSelection [] = Nothing :: SubSelection
parseSubSelection (h:t)
 | h=='('&&(elem ':' t)==True&&(elem ')' t)==True = Just (ScalarType (Nothing :: Alias) ((removeSideSpaces (foldr (\x y -> if x==':' then [] else x:y) "" t)) :: Name) (Nothing :: Transformation) ((Just $ removeSideSpaces $ foldl (\y x -> if x==':' then [] else (y++[x])) "" (foldr (\x y -> if x==')' then [] else x:y) "" t)) :: Argument)) :: SubSelection
 | otherwise = parseSubSelection t
-- REQUIRES: code block on nested object subfields where nested object opening and closing curly brackets are not included
parseSubFields :: String -> [(String,[String])] -> [(String,[String],[String])] -> [(String,String,String)] -> [Fragment] -> ServerObject -> [Field]
parseSubFields [] _ _ _ _ _ = []
parseSubFields str svrobjs soa vars fmts sobj = parseSubFieldsHelper str [] [] svrobjs soa True vars fmts sobj
parseSubFieldsHelper :: String -> String -> String -> [(String,[String])] -> [(String,[String],[String])] -> Bool -> [(String,String,String)] -> [Fragment] -> ServerObject -> [Field]
parseSubFieldsHelper [] [] [] _ _ _ _ _ _ = []
parseSubFieldsHelper [] [] acc _ _ True _ _ _ = [Left $ createScalarType acc :: Field]
parseSubFieldsHelper [] [] acc _ _ False _ _ _ = []
parseSubFieldsHelper [] acc [] _ _ True _ _ _ = [Left $ createScalarType acc :: Field]
parseSubFieldsHelper [] acc [] _ _ False _ _ _ = []
-- There is not a case where both acc1 and acc2 are not empty, but I'll catch anyway
parseSubFieldsHelper [] acc1 acc2 _ _ True _ _ _ = (Left $ createScalarType (acc2++acc1)):[]
parseSubFieldsHelper [] acc1 acc2 _ _ False _ _ _ = []
parseSubFieldsHelper (h:t) acc1 acc2 svrobjs soa inc vars fmts sobj
    | h==':' = parseSubFieldsHelper (removeLeadingSpaces t) (acc2++acc1++[h]) [] svrobjs soa inc vars fmts sobj
    | h==' '&&(length acc1)>0 = parseSubFieldsHelper t [] acc1 svrobjs soa inc vars fmts sobj
    | h==' ' = parseSubFieldsHelper t acc1 acc2 svrobjs soa inc vars fmts sobj
    | h==','&&(length acc1)>0&&(inc==True) = (Left $ createScalarType acc1 :: Field):parseSubFieldsHelper t [] [] svrobjs soa True vars fmts sobj
    | h==','&&(length acc1)>0 = parseSubFieldsHelper t [] [] svrobjs soa True vars fmts sobj
    | h==','&&(length acc2)>0&&(inc==True) = (Left $ createScalarType acc2 :: Field):parseSubFieldsHelper t [] [] svrobjs soa True vars fmts sobj
    | h==','&&(length acc2)>0 = parseSubFieldsHelper t [] [] svrobjs soa True vars fmts sobj
    | h=='('&&(length acc1)>0 = parseSubFieldsHelper selectTail (acc1++[h]++subselect) [] svrobjs soa inc vars fmts sobj
    | h=='('&&(length acc2)>0 = parseSubFieldsHelper selectTail (acc2++[h]++subselect) [] svrobjs soa inc vars fmts sobj
    | h=='{'&&(length acc1)>0&&(inc==True) = (Right $ (Left $ (createNestedObject (acc1++[h]++level) svrobjs soa vars fmts) :: FieldObject) :: Field):parseSubFieldsHelper levelTail [] [] svrobjs soa True vars fmts sobj
    | h=='{'&&(length acc1)>0 = parseSubFieldsHelper levelTail [] [] svrobjs soa True vars fmts sobj
    | h=='{'&&(length acc2)>0&&(inc==True) = (Right $ (Left $ (createNestedObject (acc2++[h]++level) svrobjs soa vars fmts) :: FieldObject) :: Field):parseSubFieldsHelper levelTail [] [] svrobjs soa True vars fmts sobj
    | h=='{'&&(length acc2)>0 = parseSubFieldsHelper levelTail [] [] svrobjs soa True vars fmts sobj
    | h=='@'&&directive==True = parseSubFieldsHelper directiveTail acc1 acc2 svrobjs soa True vars fmts sobj
    | h=='@' = parseSubFieldsHelper directiveTail acc1 acc2 svrobjs soa False vars fmts sobj
    | h=='.'&&(length acc1)>0 = parseSubFieldsHelper t (acc1++[h]) [] svrobjs soa True vars fmts sobj
    | h=='.'&&(length acc2)>0&&inc==True = if isInlineFragment then (Left $ createScalarType acc2 :: Field):(Right (Right $ (createInlinefragmentObject inlinefragmentBody inlinefragmentObj vars fmts svrobjs soa) :: FieldObject) :: Field):parseSubFieldsHelper inlinefragmentTail [] [] svrobjs soa True vars fmts sobj else parseSubFieldsHelper (fContents++fragmentTail) [] [] svrobjs soa inc vars fmts sobj
    | h=='.'&&(length acc2)>0 = if isInlineFragment then (Right (Right $ (createInlinefragmentObject inlinefragmentBody inlinefragmentObj vars fmts svrobjs soa) :: FieldObject) :: Field):parseSubFieldsHelper inlinefragmentTail [] [] svrobjs soa True vars fmts sobj else parseSubFieldsHelper (fContents++fragmentTail) [] [] svrobjs soa inc vars fmts sobj
    | h=='.'&&isInlineFragment==True = (Right (Right $ (createInlinefragmentObject inlinefragmentBody inlinefragmentObj vars fmts svrobjs soa) :: FieldObject) :: Field):parseSubFieldsHelper inlinefragmentTail [] [] svrobjs soa True vars fmts sobj
    | h=='.' = parseSubFieldsHelper (fContents++fragmentTail) [] [] svrobjs soa inc vars fmts sobj
    | h=='}' = parseSubFieldsHelper t acc1 acc2 svrobjs soa inc vars fmts sobj  -- this character is removed when I pull a level
    | (length acc2)>0&&inc==True = (Left $ createScalarType acc2 :: Field):parseSubFieldsHelper t (acc1++[h]) [] svrobjs soa True vars fmts sobj
    | (length acc2)>0 = parseSubFieldsHelper t (acc1++[h]) [] svrobjs soa True vars fmts sobj
    | otherwise = parseSubFieldsHelper t (acc1++[h]) [] svrobjs soa inc vars fmts sobj
  where
    (level,levelTail) = splitLevel t "" 0
    (subselect,selectTail) = getSubSelection t
    (directive,directiveTail) = checkDirective (h:t) vars
    (isInlineFragment,inlinefragmentObj,inlinefragmentBody,inlinefragmentTail) = checkInlinefragment (h:t)
    (fragmentName,fragmentTail) = findFragment (h:t)
    fContents = expandFragment fragmentName sobj fmts

checkInlinefragment :: String -> (Bool,String,String,String)
checkInlinefragment str = checkInlinefragmentHelper str [] False []
checkInlinefragmentHelper :: String -> String -> Bool -> String -> (Bool,String,String,String)
checkInlinefragmentHelper [] _ _ _ = (False,[],[],[])
checkInlinefragmentHelper (h:t) acc sobj obj
    | (length acc)<3&&h=='.' = checkInlinefragmentHelper t (acc++[h]) False []
    | (length acc)<3 = throw ParseFragmentException
    | (length acc)==3&&h/=' ' = (False,[],[],(acc++(h:t)))
    | (length acc)==3 = checkInlinefragmentHelper t (acc++[h]) False []
    | (length acc)==4&&h==' ' = checkInlinefragmentHelper t acc False []
    | (length acc)==4&&h=='o' = checkInlinefragmentHelper t (acc++[h]) False []
    | (length acc)==4 = throw ParseFragmentException
    | (length acc)==5&&h=='n' = checkInlinefragmentHelper t (acc++[h]) False []
    | (length acc)==5 = throw ParseFragmentException
    | (length acc)==6&&h==' ' = checkInlinefragmentHelper t (acc++[h]) False []
    | (length acc)==6 = throw ParseFragmentException
    | (length acc)==7&&h==' ' = checkInlinefragmentHelper t acc False []
    | (length acc)==7&&((fromEnum h)>=97||(fromEnum h)<=122) = checkInlinefragmentHelper t (acc++[h]) False [h]
    | (length acc)==7 = throw ParseFragmentException
    | (length acc)>7&&sobj==False&&(isValidIdentifierChar h) = checkInlinefragmentHelper t acc False (obj++[h])
    | (length acc)>7&&sobj==False&&h==' ' = checkInlinefragmentHelper t acc True obj
    | (length acc)>7&&h==' ' = checkInlinefragmentHelper t acc True obj
    | (length acc)>7&&h/='{' = throw ParseFragmentException
    | (length acc)>7 = (True,obj,contents,tail)
    | otherwise = throw ParseFragmentException
  where
    (contents,tail) = splitSubject t [] 0
createInlinefragmentObject :: String -> String -> [(String,String,String)] -> [Fragment] -> [(String,[String])] -> [(String,[String],[String])] -> InlinefragmentObject
createInlinefragmentObject bdy obj vars fmts svrobjs soa = (InlinefragmentObject (readServerObject obj svrobjs soa) ((parseSubFields bdy svrobjs soa vars fmts obj) :: SubFields))

findFragment :: String -> (String,String)
findFragment [] = throw ParseFragmentException
findFragment str = findFragmentHelper str []
findFragmentHelper :: String -> String -> (String,String)
findFragmentHelper [] acc = (acc,[])
findFragmentHelper (h:t) acc
  | (length acc)<3&&h=='.' = findFragmentHelper t (acc++[h])
  | (length acc)<3 = throw ParseFragmentException
  | (length acc)==3&&(isValidFragmentNameChar h) = findFragmentHelper t (acc++[h])
  | (length acc)==3 = throw ParseFragmentException
  | (length acc)>3&&(isValidFragmentNameChar h) = findFragmentHelper t (acc++[h])
  | (length acc)>3&&h==' ' = (acc,t)
  | otherwise = throw ParseFragmentException

expandFragment :: String -> ServerObject -> [Fragment] -> String
expandFragment _ _ [] = throw ParseFragmentException
expandFragment fnm sobj (h:t) = if (targetObject h)==sobj&&fnm==("..."++(name h)) then (replacement h) else expandFragment fnm sobj t

removeLeadingSpaces :: String -> String
removeLeadingSpaces [] = []
removeLeadingSpaces (h:t) = if h==' ' then removeLeadingSpaces t else (h:t)
-- EFFECTS: return subselection and String remainder
getSubSelection :: String -> (String,String)
getSubSelection str = getSubSelectionHelper str ""
getSubSelectionHelper :: String -> String -> (String,String)
getSubSelectionHelper [] acc = ([],[])
getSubSelectionHelper (h:t) acc
    | h==')' = (acc++[h], t)
    | otherwise = getSubSelectionHelper t (acc++[h])
-- split level at and without uneven brace.
splitLevel :: String -> String -> Int -> (String,String)
splitLevel [] acc _ = (acc,[])
splitLevel (h:t) acc l
    | l<0 = (acc,(h:t))
    | h=='{' = splitLevel t (acc++[h]) (l+1)
    | h=='}' = splitLevel t (acc++[h]) (l-1)
    | otherwise = splitLevel t (acc++[h]) l
-- determine if directive result is to include or exclude
checkDirective :: String -> [(String,String,String)] -> (Bool, String)
checkDirective qry vars = if (isDirective qry)==False then (True,qry) else checkDirectiveHelper (getDirective qry) vars
checkDirectiveHelper :: (String,String,String) -> [(String,String,String)] -> (Bool,String)
checkDirectiveHelper (dir,(h:t),tail) vars
    | directive=="include"&&value=="true"=(True,tail)
    | directive=="include"&&value=="false"=(False,tail)
    | directive=="skip"&&value=="true"=(False,tail)
    | directive=="skip"&&value=="false"=(True,tail)
    | otherwise = throw InvalidScalarException
  where
    directive = toLowercase dir
    value = if h=='$' then toLowercase $ getVariableValue vars (h:t) else toLowercase (h:t)
isDirective :: String -> Bool
isDirective [] = False
isDirective (h:t)
    | h=='@' = True
    | h==' ' = isDirective t
    | otherwise = False
getDirective :: String -> (String,String,String)
getDirective (h:t)
    | h==' ' = getDirective t
    | h=='@' = (dir,val,tail)
  where
    dir = removeSideSpaces $ foldl (\y x -> if x=='@' then [] else y++[x]) "" $ getPrefix (h:t) '('
    val = removeSideSpaces $ foldl (\y x -> if x==':' then [] else y++[x]) "" $ getPrefix t ')'
    tail = getSuffix t ')'
getPrefix :: String -> Char -> String
getPrefix [] _ = []
getPrefix (h:t) chr = if (h==chr) then [] else h:getPrefix t chr
getSuffix :: String -> Char -> String
getSuffix [] _ = []
getSuffix (h:t) chr = if h==chr then t else getSuffix t chr
toLowercase :: String -> String
toLowercase str = [toLower c | c <- str]
getVariableValue :: [(String,String,String)] -> String -> String
getVariableValue [] _ = throw InvalidVariableNameException
getVariableValue ((name,typ,val):t) var
    | (name==var)&&(typ=="Bool") = val
    | (name==var) = throw MismatchedVariableTypeException
    | otherwise = getVariableValue t var
-- pull level and leave out closing brace.
extractLevel :: String -> String
extractLevel [] = []
extractLevel str = extractLevelHelper str 0
extractLevelHelper :: String -> Int -> String
extractLevelHelper [] _ = []
extractLevelHelper (h:t) l
 | h=='{' = h:extractLevelHelper t (l+1)
 | h=='}'&&l==0 = []
 | h=='}' = '}':extractLevelHelper t (l-1)
 | otherwise = h:extractLevelHelper t l
-- -- remove level and leave out closing brace
-- removeLevel :: String -> String
-- removeLevel [] = []
-- removeLevel str = removeLevelHelper str 0
-- removeLevelHelper :: String -> Int -> String
-- removeLevelHelper [] _ = []
-- removeLevelHelper (h:t) l
--  | l<0 = h:t
--  | h=='{' = removeLevelHelper t (l+1)
--  | h=='}' = removeLevelHelper t (l-1)
--  | otherwise = removeLevelHelper t l
removeSpaces :: String -> String
removeSpaces str = [x | x <- str, x/=' ']
createScalarType :: String -> ScalarType
createScalarType [] = throw InvalidScalarException
createScalarType str = ScalarType (parseAlias str :: Alias) (parseName str :: Name) (parseTransformation str :: Transformation) (parseArgument str :: Argument)
parseTransformation :: String -> Transformation
parseTransformation [] = Nothing :: Transformation
parseTransformation str
    | (elem '(' str)&&(elem ':' str) = (Just $ removeSideSpaces $ foldr (\x y -> if x==':' then [] else x:y) "" $ foldl (\y x -> if x=='(' then [] else y++[x]) "" str) :: Transformation
    | (elem '(' str) = throw SyntaxException
    | otherwise = Nothing :: Transformation
parseArgument :: String -> Argument
parseArgument [] = Nothing :: Argument
parseArgument str
    | (elem ')' str)&&(elem ':' str) = (Just $ removeSideSpaces $ foldr (\x y -> if x==')' then [] else x:y) "" $ foldl (\y x -> if x==':' then [] else y++[x]) "" str) :: Argument
    | (elem ')' str) = throw SyntaxException
    | otherwise = Nothing :: Argument
removeSideSpaces :: String -> String
removeSideSpaces str = foldl (\y x -> if (x==' '&&(length y)==0) then [] else y++[x]) "" $ foldr (\x y -> if (x==' '&&(length y)==0) then [] else x:y) "" str
-- parseOperation :: String -> Operation
-- TODO: support mutations


{-----Step 4. CROSS-CHECKING-----}
-- done by ServerObjectValidator.hs

{-----Step 5. MAKE QUERY-----}
-- done by SQLQueryComposer.hs for sql queries

{-----Step 6. PROCESS RESULTS-----}
-- done by PersistentDataProcessor.hs