module Hascat.System.Controller
  ( loadApp,
    insertApp,
    startApp,
    stopApp,
    reloadApp, 
    pauseApp,
    resumeApp,
    undeployApp,
    findApp,
    State(..),
    StateVar )
where

import Control.Concurrent.MVar
import Control.OldException
import Data.List
import Hascat.App
import Hascat.Config
import Hascat.System.App
import Hascat.Toolkit
import Network.URI
import System.Time
import System.Posix.Types
import Control.Concurrent.MVar
import Data.IORef


type StateVar = MVar State

data State = State {
                   stProcessID :: ProcessID,
                   stGeneral   :: General,
                   stApps      :: [App] }


reloadApp :: StateVar -> ContextPath -> IO StateVar
reloadApp stateVar contextPath = 
  blockAndModifyApp stateVar contextPath (\app -> (reload app >>=  start))

startApp :: StateVar -> ContextPath -> IO StateVar
startApp stateVar contextPath = 
  blockAndModifyApp stateVar contextPath start

stopApp :: StateVar -> ContextPath -> IO StateVar
stopApp stateVar contextPath = 
  blockAndModifyApp stateVar contextPath stop

pauseApp :: StateVar -> ContextPath -> IO StateVar
pauseApp stateVar contextPath = 
  blockAndModifyApp stateVar contextPath pause

resumeApp :: StateVar -> ContextPath -> IO StateVar
resumeApp stateVar contextPath = 
  blockAndModifyApp stateVar contextPath resume

loadApp :: StateVar -> AppConfig -> IO StateVar
loadApp stateVar config = do  
  bracketOnError
    (takeMVar stateVar)
    (putMVar stateVar)
    (\state -> do
       state' <- loadApp' state stateVar config
       putMVar stateVar state')
  return stateVar
    where
    loadApp' :: State -> StateVar -> AppConfig -> IO State
    loadApp' state@(State conf 
             gen@(General _ (ServerRoot serverRoot) pluginLoader) apps)
             stateVar config = do
      let code = serverRoot // getAppRoot config // getAppCode config
      (modul,handlers) <- case getAppType config of
                    AppConfig_type_normal -> 
                      loadFromModule pluginLoader code "handlers"
                    AppConfig_type_system -> do
                      (modul, (SystemHandler respond)) 
                        <- loadFromModule pluginLoader code
                                                   "systemHandler"
                      return  (modul,(Handlers (systemInit stateVar) 
                              respond 
                              defaultDone))
      apps' <- insertApp' (App config modul handlers Nothing False) apps
      return (State conf gen apps')
      where
        systemInit :: StateVar -> InitHandler StateVar
        systemInit stateVar _ = return stateVar


undeployApp :: StateVar -> ContextPath -> IO StateVar
undeployApp stateVar contextPath =  do
  bracketOnError
    (takeMVar stateVar)
    (putMVar stateVar)
    (\(State conf gen apps) -> do 
       apps' <- undeployApp' contextPath apps
       putMVar stateVar (State conf gen apps'))
  return stateVar
  where
  undeployApp' :: ContextPath -> [App] -> IO [App]
  undeployApp' _ [] = fail "Not found"
  undeployApp' contextPath (app:apps) =
      if hasContextPath contextPath app then
        if isRunning app then fail "Application is running"
        else return apps
      else do
        apps' <- undeployApp' contextPath apps
        return (app:apps')


insertApp :: StateVar -> App -> IO StateVar
insertApp stateVar newApp = do
  bracketOnError
    (takeMVar stateVar)
    (putMVar stateVar)
      (\(State conf gen apps) -> do 
       apps' <- insertApp' newApp apps
       putMVar stateVar (State conf gen apps'))
  return stateVar
  
insertApp' :: App -> [App] -> IO [App]
insertApp' newApp apps = return (uniqueInsert apps)
  where
    uniqueInsert :: [App] -> [App]
    uniqueInsert [] = [newApp]
    uniqueInsert (app:apps) =
        let (ContextPath newContextPath) = getAppContextPath (appConfig newApp)
            (ContextPath appContextPath) = getAppContextPath (appConfig app)
        in case cmp newContextPath appContextPath of
             EQ -> fail ("Duplicate context path " ++ newContextPath)
             GT -> app:uniqueInsert apps
             LT -> newApp:app:apps

    cmp :: String -> String -> Ordering
    cmp a b = let n = min (length a) (length b)
              in case compare (take n a) (take n b) of
                   EQ -> compare (length b) (length a)
                   o  -> o

blockAndModifyApp :: StateVar -> ContextPath -> (App -> IO App) -> IO StateVar
blockAndModifyApp stateVar contextPath action = do
  bracketOnError
    (takeMVar stateVar)
    (putMVar stateVar)
    (\(State conf gen apps) -> do 
       apps' <- modifyApp contextPath action apps
       putMVar stateVar (State conf gen apps'))
  return stateVar
    where
    modifyApp :: ContextPath -> (App -> IO App) -> [App] -> IO [App]
    modifyApp _ _ [] = fail "Not found"
    modifyApp contextPath action (app:apps) =
        if hasContextPath contextPath app then do
          app' <- action app
          return (app':apps)
        else do
          apps' <- modifyApp contextPath action apps
          return (app:apps')
  

findApp :: [App] -> (App -> Bool) -> Maybe App
findApp apps pred = find pred apps