{-# LANGUAGE TupleSections #-}
module YesodDsl.ClassImplementer (implementClasses) where
import Data.List
import YesodDsl.AST
import Data.Maybe
import Data.Generics
import Data.Generics.Uniplate.Data
import qualified Data.Map as Map
import qualified Data.List as L

lookupField' :: Module -> EntityName -> FieldName -> Maybe Field
lookupField' m en fn = listToMaybe [ f | e <- modEntities m,
                                    f <- entityFields e,
                                    entityName e == en,
                                    fieldName f == fn ] 


implementClasses :: Module -> Module
implementClasses m = let m' = m {
        modEntities  = [ implInEntity m (modClasses m) e | e <- modEntities m ]
    } in m' {
        modRoutes = everywhere (mkT $ trSq m') $ modRoutes m'
    }

trSq :: Module -> SelectQuery -> SelectQuery
trSq m sq = sq {
        sqFields = concatMap trSelectField $ sqFields sq,
        sqJoins =  map snd newJoins,
        sqWhere = everywhere (mkT trExpr) $ sqWhere sq
    }
    where
        vnMap :: [(VariableName, (VariableName, Maybe EntityName))]
        vnMap = mapMaybe fst newJoins
        aliases = map (\(er,vn) -> (entityRefName er, vn)) $ sqFrom sq : [ (joinEntity j,joinAlias j) | j <- sqJoins sq ]
        newAliases vn = Map.findWithDefault [(vn,Nothing)] vn $ Map.fromListWith (++) $ [ (s,[d]) | (s,d) <- vnMap ]
        allAliases = aliases ++ catMaybes [ men >>= Just . (,vn) | (_,(vn,men)) <- vnMap ]
        newJoins = concatMap expandJoin $ sqJoins sq
        expandJoin j = fromMaybe [(Nothing, j)] $ do
            c <- classLookup (modClasses m) $ entityRefName $ joinEntity j
            Just $ [ 
                    let a = joinAlias j
                        a' = joinAlias j ++ "_" ++ entityName e
                    in (Just (a, (a', Just $entityName e)), j {
                        joinAlias = a',
                        joinEntity = (Left $ entityName e),
                        joinExpr = joinExpr j >>= Just . (everywhere $ (mkT $ trClassField (entityName e)) . (mkT $ trVar a a'))
                    }) | e <- modEntities m, className c `elem` entityInstances e
                ]
        
        trClassField en fr = case fr of
            SqlField v'@(Var vn _ _) fn -> fromMaybe fr $ do
                (en',_) <- L.find ((==vn) . snd) aliases
                f <- lookupField' m en' (lowerFirst en ++ upperFirst fn)
                Just $ SqlField v' $ fieldName f
            _ -> fr    
                
        trVar srcVn dstVn fr = case fr of
            SqlField (Var vn _ _) fn -> if vn == srcVn then SqlField (Var dstVn (Left "") False) fn else fr
            SqlId (Var vn _ _) -> if vn == srcVn then SqlId (Var dstVn (Left "") False) else fr
            _ -> fr
        aliasName :: FieldName -> Maybe VariableName -> Maybe EntityName -> Maybe VariableName    

        aliasName fn man (Just en) = Just $ fromMaybe (fn ++ en) $ man >>= Just . (++en)
        aliasName _ man Nothing = man
    
        trSelectField sf = 
            case sf of
                SelectAllFields (Var vn _ _) -> [
                        SelectAllFields (Var vn' (Left "") False)
                        | (vn',_) <- newAliases vn
                    ]
                SelectField (Var vn _ _) fn man -> [
                        SelectField (Var vn' (Left "") False) fn $ aliasName fn man men
                        | (vn',men) <- newAliases vn,
                          validField (vn',fn)
                    ]
                SelectIdField (Var vn _ _) man -> [
                        SelectIdField (Var vn' (Left "") False) $ aliasName "id" man men
                        | (vn',men) <- newAliases vn
                    ]
                SelectValExpr _ _ -> [sf]  

        trExpr e = 
            let r = catMaybes [
                        let e' = everywhere (mkT $ trVar s d) e
                        in if e' /= e && validExpr e' then Just e' else Nothing
                        | (s,(d,_)) <- vnMap
                    ] 
                in if null r then eelse foldl1 OrExpr r
                      
        validExpr e = let fs = [ (vn,fn) | SqlField (Var vn _ _) fn <- universeBi e ]
                      in all validField fs   
        validField (vn,fn) = fromMaybe False $ do
            (en,_) <- L.find ((==vn) . snd) allAliases
            _ <- lookupField' m en fn
            Just True

classLookup :: [Class] -> ClassName -> Maybe Class
classLookup classes name =  find (\i -> name == className i) classes


expandClassField :: Module -> ClassName -> Entity ->  Field -> [Field]
expandClassField m cn e f@(Field _ _ internal _ (EntityField iName) _) 
    | not $ fieldOptional f = error $ show (entityLoc e) ++ ": non-maybe reference to class not allowed"
    | otherwise = [ mkField re | re <- modEntities m,  
                                 iName `elem` (entityInstances re) ]
    where mkField re = Field {
            fieldLoc = fieldLoc f,
            fieldOptional = True,
            fieldInternal = internal,
            fieldName = lowerFirst (entityName re) ++ upperFirst (fieldName f),
            fieldContent = EntityField (entityName re),
            fieldClassName = Just (cn, fieldName f)
        } 
expandClassField _ _ _ _ = []

expandClassRefFields :: Module -> Entity -> Field -> [Field]
expandClassRefFields m e f = expand (fieldContent f)
    where       
        expand (EntityField "ClassInstance") = [ 
                f { 
                    fieldContent = EntityField (entityName e)
                }
            ]
        expand (EntityField name) = case classLookup (modClasses m) name of
            Just _ -> expandClassField m name e f 
            Nothing -> [f]
        expand _ = [f]                           
            
implInEntity :: Module -> [Class] -> Entity -> Entity
implInEntity m classes' e = e { 
        entityFields  = concatMap (expandClassRefFields m e) $ 
                            entityFields e ++ extraFields,
        entityClassFields = filter isClassField $ entityFields e,
        entityUniques = entityUniques e ++ (map addEntityNameToUnique $ concatMap classUniques validClasses)
    }
    where
        instances = entityInstances e
        classes = sortBy (\c1 c2 -> maybeCompare (elemIndex (className c1) instances) 
                                                 (elemIndex (className c2) instances))
                         classes'
        maybeCompare (Just a1) (Just a2) = compare a1 a2
        maybeCompare (Just _) Nothing = Prelude.LT
        maybeCompare Nothing (Just _) = Prelude.GT
        maybeCompare Nothing Nothing = Prelude.EQ
        validClasses = mapMaybe (classLookup classes) $ entityInstances e
        extraFields = concatMap classFields validClasses
        isClassField (Field _ _ _ _ (EntityField iName) _) = iName `elem` (map className $ modClasses m)
        isClassField _ = False
        addEntityNameToUnique (Unique name fields) = Unique (entityName e ++ name) fields