module Components.Parsers.ServerSchemaJsonParser (fetchArguments) where

import Control.Exception (throw)
import Data.List (foldl')
import Text.JSON (
    JSValue(JSObject),
    JSObject,
    Result(Ok,Error),
    valFromObj,
    decode
  )
import Model.ServerExceptions (
    QueryException(
      ImportSchemaServerNameException,
      ImportSchemaException,
      ImportSchemaChildrenException,
      ImportSchemaPseudonymsException,
      ImportSchemaScalarFieldsException,
      ImportSchemaDatabaseTablesException,
      ImportSchemaObjectFieldsException,
      ImportSchemaDatabaseRelationshipsException,
      ImportSchemaDuplicateException
    )
  )


fetchArguments :: FilePath -> IO ([(String,[String])],[(String,[(String,String,[(String,[(String,String,String,String)])])])],[(String,[String])],[(String,String)],[(String,String,[String])],[(String,[String],[String])])
fetchArguments fp = do
    schema <- Prelude.readFile fp
    return $ parseSchema schema
parseSchema :: String -> ([(String,[String])],[(String,[(String,String,[(String,[(String,String,String,String)])])])],[(String,[String])],[(String,String)],[(String,String,[String])],[(String,[String],[String])])
parseSchema str = parseHelper $ checkJSValueListValue (decode str :: Result (JSObject JSValue))
checkJSValueListValue :: Result (JSObject JSValue) -> (JSObject JSValue)
checkJSValueListValue (Error str) = throw ImportSchemaException
checkJSValueListValue (Ok a) = a
parseHelper :: JSObject JSValue -> ([(String,[String])],[(String,[(String,String,[(String,[(String,String,String,String)])])])],[(String,[String])],[(String,String)],[(String,String,[String])],[(String,[String],[String])])
parseHelper json = if (isValidParentScalars soa sss)&&(isWithoutDups svrobjs sss sos sor soa) then (svrobjs,sss,sos,sdbn,sor,soa) else throw ImportSchemaChildrenException
  where
    (svrobjs,sss,sos,sdbn,sor) = parsePrimitivesIterator [] [] [] [] [] $ getObjects (valFromObj "PrimitiveObjects" json :: Result [JSObject JSValue])
    soa = parseParentsIterator [] $ getObjects (valFromObj "ParentalObjects" json :: Result [JSObject JSValue])
parsePrimitivesIterator :: [(String,[String])] -> [(String,[(String,String,[(String,[(String,String,String,String)])])])] -> [(String,[String])] -> [(String,String)] -> [(String,String,[String])] -> [JSObject JSValue] -> ([(String,[String])],[(String,[(String,String,[(String,[(String,String,String,String)])])])],[(String,[String])],[(String,String)],[(String,String,[String])])
parsePrimitivesIterator svrobjs sss sos sdbn sor [] = (svrobjs,sss,sos,sdbn,sor)
parsePrimitivesIterator svrobjs sss sos sdbn sor (obj:t) = if foldl' (\acc (prev,_)->(prev==name)||acc) False sdbn then throw ImportSchemaDuplicateException else parsePrimitivesIterator ((name,pseudonyms):svrobjs) ((name,scalars):sss) ((name,nestedobjects):sos) ((name,table):sdbn) (sor++(processRelationships relationships)) t
  where
    name = getString (valFromObj "ServerName" obj :: Result String) 0
    pseudonyms = getStringList (valFromObj "Pseudonyms" obj :: Result [String]) 0
    scalars = checkScalars (valFromObj "ScalarFields" obj :: Result [JSValue])
    nestedobjects = getStringList (valFromObj "ObjectFields" obj :: Result [String]) 1
    table = getString (valFromObj "DatabaseTable" obj :: Result String) 2
    relationships = getListStringList (valFromObj "DatabaseRelationships" obj :: Result [[String]])
parseParentsIterator :: [(String,[String],[String])] -> [JSObject JSValue] -> [(String,[String],[String])]
parseParentsIterator soa [] = soa
parseParentsIterator soa (obj:t) = if foldl' (\acc (prev,_,_)->(prev==name)||acc) False soa then throw ImportSchemaDuplicateException else parseParentsIterator ((name,pseudonyms,children):soa) t
  where
    name = getString (valFromObj "ServerName" obj :: Result String) 0
    pseudonyms = getStringList (valFromObj "Pseudonyms" obj :: Result [String]) 0
    children = getStringList (valFromObj "ServerChildren" obj :: Result [String]) 2
getObjects :: Result [JSObject JSValue] -> [JSObject JSValue]
getObjects (Error _) = throw ImportSchemaServerNameException
getObjects (Ok objects) = objects
getString :: Result String -> Int -> String
getString (Ok str) _ = str
getString (Error str) 0 = throw ImportSchemaServerNameException
getString (Error str) 1 = throw ImportSchemaScalarFieldsException
getString (Error str) 2 = throw ImportSchemaDatabaseTablesException
getString _ _ = throw ImportSchemaException
getStringList :: Result [String] -> Int -> [String]
getStringList (Ok rlt) _ = rlt
getStringList _ 0 = throw ImportSchemaPseudonymsException
getStringList _ 1 = throw ImportSchemaObjectFieldsException
getStringList _ 2 = throw ImportSchemaChildrenException
getStringList _ _ = throw ImportSchemaException
getListStringList :: Result [[String]] -> [[String]]
getListStringList (Error str) = throw ImportSchemaDatabaseRelationshipsException
getListStringList (Ok rlt) = rlt
checkScalars :: Result [JSValue] -> [(String,String,[(String,[(String,String,String,String)])])]
checkScalars (Error str) = throw ImportSchemaScalarFieldsException
checkScalars (Ok a) = getScalars a
getScalars :: [JSValue] -> [(String,String,[(String,[(String,String,String,String)])])]
getScalars [] = []
getScalars ((JSObject obj):t) = (getString (valFromObj "Name" obj :: Result String) 1,getType (valFromObj "Type" obj :: Result String),getScalarArguments $ getObjects (valFromObj "Arguments" obj :: Result [JSObject JSValue])):getScalars t
getScalarArguments :: [JSObject JSValue] -> [(String,[(String,String,String,String)])]
getScalarArguments [] = []
getScalarArguments (h:t) = (name,options):getScalarArguments t
  where
    name = getString (valFromObj "Name" h :: Result String) 1
    options = getScalarArgumentOptions (getObjects (valFromObj "Options" h :: Result [JSObject JSValue]))
getScalarArgumentOptions :: [JSObject JSValue] -> [(String,String,String,String)]
getScalarArgumentOptions [] = []
getScalarArgumentOptions (h:t) = (name,typ,prefix,suffix):getScalarArgumentOptions t
  where
    name = getString (valFromObj "Name" h :: Result String) 1
    typ = getType (valFromObj "Type" h :: Result String)
    prefix = getString (valFromObj "Prefix" h :: Result String) 1
    suffix = getString (valFromObj "Suffix" h :: Result String) 1
getType :: Result String -> String
getType (Ok "Text") = "Text"
getType (Ok "ByteString") = "ByteString"
getType (Ok "Int") = "Int"
getType (Ok "Double") = "Double"
getType (Ok "Rational") = "Rational"
getType (Ok "Bool") = "Bool"
getType (Ok "Day") = "Day"
getType (Ok "TimeOfDay") = "TimeOfDay"
getType (Ok "UTCTime") = "UTCTime"
-- getType (Error str) = throw ImportSchemaScalarFieldsException
getType _ = throw ImportSchemaScalarFieldsException
processRelationships :: [[String]] -> [(String,String,[String])]
processRelationships lst = foldr (\x y -> (getFirst x,getThird x, x):y) [] lst
getFirst :: [String] -> String
getFirst (h:t) = h
getFirst _ = throw ImportSchemaException
getThird :: [String] -> String
getThird (h1:h2:h3:t) = h3
getThird _ = throw ImportSchemaException
-- no duplicated names in schema
isWithoutDups :: [(String,[String])] -> [(String,[(String,String,[(String,[(String,String,String,String)])])])] -> [(String,[String])] -> [(String,String,[String])] -> [(String,[String],[String])] -> Bool
isWithoutDups sobjs sss sos sor soa = (isNoDupsNames $ foldr (++) [] ([x | (_,x)<-sobjs]++[x | (_,x,_)<-soa]))&&(isNoDupsScalars sss)&&(isNoDupsNestedObjects sos)&&(isNoDupsRelationships sor)
isNoDupsNames :: [String] -> Bool
isNoDupsNames [] = True
isNoDupsNames (nm:rst) = (foldl' (\y x->x/=nm&&y) True rst)&&isNoDupsNames rst
isNoDupsScalars :: [(String,[(String,String,[(String,[(String,String,String,String)])])])] -> Bool
isNoDupsScalars sss = foldl' (\y (_,x)->y&&isNoDupsScalarNames x) True sss
isNoDupsScalarNames :: [(String,String,[(String,[(String,String,String,String)])])] -> Bool
isNoDupsScalarNames [] = True
isNoDupsScalarNames ((name,_,args):rst) = (foldl' (\y (x,_,_)->name/=x&&y) True rst)&&(isNoDupsScalarArguments args)&&isNoDupsScalarNames rst
isNoDupsScalarArguments :: [(String,[(String,String,String,String)])] -> Bool
isNoDupsScalarArguments [] = True
isNoDupsScalarArguments ((name,opts):rst) = (foldl' (\y (n,o)->name/=n&&y&&isNoDupsArgumentOptions o) True rst)&&isNoDupsScalarArguments rst
isNoDupsArgumentOptions :: [(String,String,String,String)] -> Bool
isNoDupsArgumentOptions [] = True
isNoDupsArgumentOptions ((name,_,_,_):rst) = (foldl' (\y (n,_,_,_)->name/=n&&y) True rst)&&isNoDupsArgumentOptions rst
isNoDupsNestedObjects :: [(String,[String])] -> Bool
isNoDupsNestedObjects [] = True
isNoDupsNestedObjects ((_,[]):rst) = isNoDupsNestedObjects rst
isNoDupsNestedObjects ((obj,no:objs):rst) = (elem no objs)==False&&isNoDupsNestedObjects ((obj,objs):rst)
isNoDupsRelationships :: [(String,String,[String])] -> Bool
isNoDupsRelationships [] = True
isNoDupsRelationships ((from,to,_):rst) = (foldl' (\y (a,b,_)->(from/=a||to/=b)&&y) True rst)&&isNoDupsRelationships rst
-- shared scalars are same type
isValidParentScalars :: [(String,[String],[String])] -> [(String,[(String,String,[(String,[(String,String,String,String)])])])] -> Bool
isValidParentScalars [] _ = True
isValidParentScalars ((_,_,[]):t) sss = isValidParentScalars t sss
isValidParentScalars ((_,_,_:[]):t) sss = isValidParentScalars t sss
isValidParentScalars ((_,_,(h:cld)):t) sss = (isValidScalarsType (getPrimitiveScalars h sss) cld sss)&&isValidParentScalars t sss
getPrimitiveScalars :: String -> [(String,[(String,String,[(String,[(String,String,String,String)])])])] -> [(String,String)]
getPrimitiveScalars _ [] = throw ImportSchemaChildrenException
getPrimitiveScalars name ((nm,st):t) = if name==nm then foldl' (\y (n,tp,_)->(n,tp):y) [] st else getPrimitiveScalars name t
isValidScalarsType :: [(String,String)] -> [String] -> [(String,[(String,String,[(String,[(String,String,String,String)])])])] -> Bool
isValidScalarsType _ [] _ = True
isValidScalarsType [] _ _ = True
isValidScalarsType ((name,typ):t) cld sss = foldl' (\y x->foldl' (\b (n,tp)->if n==name then typ==tp&&y else y) True (getPrimitiveScalars x sss)) True cld