{-# LANGUAGE FlexibleContexts #-}
module TW.Check where

import TW.Ast
import TW.BuiltIn

import Control.Monad.Except
import qualified Data.Map as M

data DefinedType
   = DefinedType
   { dt_name :: QualTypeName
   , dt_args :: [TypeVar]
   } deriving (Show, Eq)

builtInToDefTy :: BuiltIn -> DefinedType
builtInToDefTy bi =
    DefinedType
    { dt_name = bi_name bi
    , dt_args = bi_args bi
    }

typeDefToDefTy :: Maybe ModuleName -> TypeDef -> DefinedType
typeDefToDefTy qualOwner td =
    let mkTy =
            case qualOwner of
              Nothing -> QualTypeName (ModuleName [])
              Just qt -> QualTypeName qt
    in case td of
         TypeDefEnum ed ->
             DefinedType (mkTy (ed_name ed)) (ed_args ed)
         TypeDefStruct sd ->
             DefinedType (mkTy (sd_name sd)) (sd_args sd)

checkModules :: [Module] -> Either String [Module]
checkModules modules =
    runExcept $
    forM modules $ \m ->
    do let currentMStr = printModuleNameS $ m_name m
       defTypes <-
           M.fromList . map (\dt -> (dt_name dt, dt_args dt)) <$>
           getDefinedTypes m
       let isValidType args t =
               case t of
                 TyVar tv ->
                     unless (tv `elem` args) $
                     throwError $ "Undefined type variable " ++ show tv ++ " in " ++ currentMStr
                 TyCon qt qtArgs ->
                     case M.lookup qt defTypes of
                       Nothing ->
                           throwError $ "Undefined type variable " ++ show qt ++ " in " ++ currentMStr
                       Just tvars ->
                           do forM_ qtArgs (isValidType args)
                              when (length tvars /= length qtArgs) $
                                   throwError $
                                   "Type " ++ show qt ++ " got applied wrong number of arguments in " ++ currentMStr
           checkRoute r =
               case r of
                 ApiRouteDynamic t ->
                   unless (isPathPiece t) $
                   throwError $
                   "Invalid route parameter " ++ show t ++ ". Route parameters can only be primitive types!"
                 _ -> return ()
       forM_ (m_typeDefs m) $ \td ->
           case td of
             TypeDefEnum ed ->
                 forM_ (ed_choices ed) $ \ch ->
                 forM_ (ec_arg ch) (isValidType (ed_args ed))
             TypeDefStruct sd ->
                 forM_ (sd_fields sd) $ \fld ->
                 isValidType (sd_args sd) (sf_type fld)
       forM_ (m_apis m) $ \api ->
          forM_ (ad_endpoints api) $ \ep ->
          do mapM_ (isValidType []) (aed_req ep)
             isValidType [] (aed_resp ep)
             mapM_ checkRoute (aed_route ep)
       return m
    where
      getDefinedTypes m =
          do importedTypes <-
                 forM (m_imports m) $ \im ->
                 case M.lookup im moduleMap of
                   Nothing ->
                       throwError $
                       "Unknown module " ++ printModuleNameS im
                       ++ " referenced from " ++ (printModuleNameS $ m_name m)
                   Just imModel ->
                       return $ map (typeDefToDefTy (Just im)) $ m_typeDefs imModel
             return $ concat importedTypes
                        ++ (map (typeDefToDefTy Nothing) $ m_typeDefs m)
                        ++ map builtInToDefTy allBuiltIns
      moduleMap =
          M.fromList $
          map (\m -> (m_name m, m)) modules