{-# LANGUAGE MultiParamTypeClasses #-}
{-|
    Module      :  Control.ERNet.Deployment.Local.Manager
    Description :  manager implementation using local threads and STM 
    Copyright   :  (c) Michal Konecny
    License     :  BSD3

    Maintainer  :  mik@konecny.aow.cz
    Stability   :  experimental
    Portability :  portable

    A simple implementation of 
    "Control.ERNet.Foundations.Manager.Manager",
    deploying all processes locally.

-}

module Control.ERNet.Deployment.Local.Manager 
(
    ManagerLocal()
)
where

import Control.ERNet.Deployment.Local.Logger
import Control.ERNet.Deployment.Local.Channel

import Control.ERNet.Foundations.Protocol
import Control.ERNet.Foundations.Event
import qualified Control.ERNet.Foundations.Event.Logger as LG
import qualified Control.ERNet.Foundations.Channel as CH
import Control.ERNet.Foundations.Process
import Control.ERNet.Foundations.Manager

import Control.Concurrent as Concurrent

import qualified Data.Map as Map

newtype ManagerLocal =
    ManagerLocal ManagerName
    
instance 
    Manager 
        ManagerLocal LoggerLocal 
        ChannelLocal ChannelLocal 
        ChannelLocalAnyProt ChannelLocalAnyProt
    where
    new name =
        do
        putStrLn $ "simulating the creation of network manager " ++ name
        return (ManagerLocal name, "ernet-local:/" ++ name)
    connectNeighbour (ManagerLocal name) neighbourID =
        do
        putStrLn $ 
            "simulating the connection of neighbour " 
            ++ neighbourID ++ " to the manager " ++ name
        return True
    runProcess (ManagerLocal name) process =
        do
        logger <- LG.new
        forkIO $ (erprocDeploy process) (erprocName process) [] [] (startNet logger)
        return logger
        
startNet 
        logger 
        locationDescr
        [] _ processesMappings =
    do
    deployProcesses logger locationDescr "" [] [] processesMappings
    return ()

startNet _ _ _ _ _ = 
    error $ 
        "Control.ERNet.Deployment.ManagerLocal: startNet: Illegal attempt to kick-start a network: outermost network cannot have input sockets" 

expandProcess 
        logger processNamePrefix inCHAs outCHAs -- these 4 provided by manager
        locationDescr inputTypesNames outputTypesNames processesMappings =
    do
    dispatcher Nothing
    where
    (outChTs, outChNs) = unzip outputTypesNames
    (inChTs, inChNs) = unzip inputTypesNames
    
    dispatcher maybeN2channel =
        do
        -- wait for a query:
--        putStrLn $ "ERProcessNet: " ++ defName ++ ": waiting for a query on " ++ show (length resCHAs) ++ " channels" 
        (chN, qryData) <- CH.waitForQueryMulti outCHAs
--        putStrLn $ "ERProcessNet: " ++ defName ++ ": forwarding query on channel " ++ show chN 
        n2channel <- -- number |-> internal or input channel
            case maybeN2channel of
                Nothing -> -- the first query -> plumb the subnet
                    deployProcesses 
                        logger locationDescr processNamePrefix 
                        inCHAs inChNs processesMappings 
                Just n2channel -> return n2channel
        -- pass on the query to the subnet:
        let fwdCHA = fst $ n2channel $ outChNs !! chN
        let outCHA = outCHAs !! chN
        let chT = outChTs !! chN
        forkIO $ forwardQueryAnswer outCHA fwdCHA chT qryData
        dispatcher (Just n2channel)
        
    forwardQueryAnswer causeCHA fwdCHA chT (causeQryId, qryAnyProt) =
        do
        argQryId <- 
            CH.makeQueryAnyProt locationDescr causeCHA causeQryId fwdCHA qryAnyProt
        -- wait for answer from inside:
        (_, ansAnyProt) <- CH.waitForAnswerMulti causeCHA causeQryId [(fwdCHA, argQryId)]
        -- forward the answer out:
        CH.answerQueryAnyProt locationDescr False causeCHA (causeQryId, ansAnyProt)

deployProcesses :: 
    (CH.ChannelForScheduler lg sIn sOut sInAnyProt sOutAnyProt) =>
    lg -> 
    String -> 
    String -> 
    [sInAnyProt] -> 
    [Int] -> 
    [(ERProcess sInAnyProt sOutAnyProt, ([Int], [Int]))] -> 
    IO (Int -> (sInAnyProt, sOutAnyProt))
deployProcesses logger locationDescr namePrefix inCHAs inChNs processesMappings =
    do
    -- create required internal channels:
    internalCHAs <- 
        mapM makeNewChannel internalTypesNamesProcesses
    let n2channel = makeChannelMap internalCHAs
    -- deploy all internal processes:
    mapM (deployProcess n2channel) $ processesMappings
    return n2channel
    where
    makeNewChannel (((chN, chT), (procName, outChN))) =
        CH.new logger (namePrefix ++ procName) outChN chT
    internalTypesNamesProcesses = 
        concat $ map getProcessChanTypesNames processesMappings
        where
        getProcessChanTypesNames (process, (inNs, outNs)) = 
            (zip (zip outNs (erprocOutputTypes process)) $ 
                zip (repeat $ erprocName process) [0..])
    makeChannelMap internalCHAs n =
        case Map.lookup n n2chMap of
             Nothing -> 
                error $ locationDescr ++ " deployProcess: unknown channel number: " ++ show n
             Just ch -> ch
        where
        n2chMap = 
            Map.fromList $ 
                (zip internalChNs internalCHAs) ++ 
                (zip inChNs $ zip inCHAs (repeat errorOUT))
        errorOUT = 
            error "ManagerLocal: makeChannelMap: input channel treated as output channel"
    internalChNs = 
        concat $ map (snd . snd) processesMappings
    deployProcess n2channel (process, (processInChNs, processOutChNs)) =
        do
        forkIO $ 
            (erprocDeploy process) 
                name
                processInCHAs processOutCHAs
                (expandProcess logger (name ++ ".") processInCHAs processOutCHAs)
        where
        name = namePrefix ++ erprocName process
        processInCHAs = map (fst . n2channel) processInChNs
        processOutCHAs = map (snd . n2channel) processOutChNs