-----------------------------------------------------------------------------

--

-- Module      :  Transient.Move.Services

-- Copyright   :

-- License     :  MIT

--

-- Maintainer  :  agocorona@gmail.com

-- Stability   :

-- Portability :

--

-- |

--

-----------------------------------------------------------------------------

{-# LANGUAGE ScopedTypeVariables, CPP, FlexibleInstances, UndecidableInstances #-}



#ifndef ghcjs_HOST_OS



module Transient.Move.Services  where



import Transient.Internals

import Transient.Move.Internals

-- import Transient.Backtrack

-- import Transient.Internals(RemoteStatus(..), Log(..))

import Transient.Move.Utils



import Control.Monad.IO.Class

import System.IO.Unsafe

import Control.Concurrent.MVar

import Control.Applicative

import System.Process

import Control.Concurrent(threadDelay)

import Control.Exception hiding(onException)

import Data.IORef



monitorService= [("service","monitor")

                ,("executable", "monitorService")

                ,("package","https://github.com/transient-haskell/transient-universe")]





monitorPort= 3000



initService :: String -> Service -> Cloud Node

initService ident service=

    cached <|> installIt

    where

    cached= local $ do

        ns <- findInNodes service 

        if null ns then  empty

        else return $ head ns

    installIt= do

        ns <- requestInstance ident service 1 

        if null ns then empty else return $ head ns



requestInstance :: String -> Service -> Int -> Cloud [Node]

requestInstance ident service num=  loggedc $ do

    --    return () !> "requestInstance"

       local $ onException $ \(e:: ConnectionError) ->  startMonitor >> continue   --   !> ("EXCEPTIOOOOOOOOOOON",e)

       nodes <- callService' ident monitorNode (ident,service,num)

       local $ addNodes nodes      -- !> ("ADDNODES",service)

       return nodes



startMonitor :: MonadIO m => m ()

startMonitor=  liftIO $ do

        (_,_,_,h) <- createProcess . shell $ "monitorService -p start/localhost/"++ show monitorPort

        writeIORef monitorHandle $ Just h

        threadDelay 2000000



monitorHandle= unsafePerformIO $ newIORef Nothing



endMonitor= do

    mm <- readIORef monitorHandle

    case mm of

        Nothing -> return ()  

        Just h  -> interruptProcessGroupOf h



findInNodes :: Service -> TransIO [Node]

findInNodes service =  do

    --   return () !> "FINDINNODES"

      nodes <-  getNodes

      return $ filter (\node  -> head service == head  (nodeServices node)) nodes

     







rfriends        =   unsafePerformIO $ newIORef ([] ::[String])

rservices       =   unsafePerformIO $ newIORef ([] ::[Service])

ridentsBanned   =   unsafePerformIO $ newIORef ([] ::[String])

rServicesBanned =   unsafePerformIO $ newIORef ([] ::[Service])



inputAuthorizations= do

    oneThread $ option "auth" "add authorizations for users and services"

    showPerm <|> friends <|> services <|> identBanned <|> servicesBanned

    empty



    where

    friends= do

      option "friends" "friendsss"

      fr <- input (const True) "enter the friend list: "

      liftIO $ writeIORef rfriends (fr :: [String])



    services= do

      option "services" "services"

      serv <- input (const True) "enter service list: "

      liftIO $ writeIORef rservices (serv :: [Service])



    identBanned= do

      option "bannedIds"  "banned users"

      ban <- input (const True) "enter the users banned: "

      liftIO $ writeIORef ridentsBanned (ban ::[String ])

      rs <- liftIO $ readIORef ridentsBanned

      liftIO $ print rs



    servicesBanned= do

      option "bannedServ"  "banned services"

      ban <- input (const True) "enter the services banned: "

      liftIO $ writeIORef rServicesBanned (ban :: [Service])



    showPerm= do

     option "show"  "show permissions"

     friends            <- liftIO $ readIORef rfriends

     services           <- liftIO $ readIORef rservices

     identsBanned       <- liftIO $ readIORef ridentsBanned

     servicesBanned     <- liftIO $ readIORef rServicesBanned

     liftIO $ putStr "allowed:          " >> print friends

     liftIO $ putStr "banned:           " >> print identsBanned

     liftIO $ putStr "services allowed: " >> print services

     liftIO $ putStr "services banned:  " >> print servicesBanned



rfreePort :: MVar Int

rfreePort = unsafePerformIO $ newMVar  (monitorPort +1)



freePort :: MonadIO m => m Int

freePort= liftIO $ modifyMVar rfreePort $ \ n -> return (n+1,n)





authorizeService :: MonadIO m => String -> Service -> m Bool

authorizeService ident service=   do



     friends            <- liftIO $ readIORef rfriends

     services           <- liftIO $ readIORef rservices

     identsBanned       <- liftIO $ readIORef ridentsBanned

     servicesBanned     <- liftIO $ readIORef rServicesBanned



     return $ if (null friends || ident `elem` friends)

        && (null services || service `elem` services)

        && (null identsBanned || ident `notElem` identsBanned)

        && (null servicesBanned || service `notElem` servicesBanned)

      then True  else False

  where

  notElem a b= not $ elem a b





callService

    :: (Loggable a, Loggable b)

    => String -> Service -> a  -> Cloud b

callService ident service params = do

    node <-  initService ident service       -- !> ("callservice initservice", service)

    callService' ident node params           -- !>  ("NODE FOR SERVICE",node)



monitorNode= unsafePerformIO $ createNodeServ "localhost"

            (fromIntegral monitorPort)

            monitorService



callService' ident node params = do

    log <- onAll $ do

             log  <- getSData <|> return emptyLog

             setData emptyLog  

             return log



    r <- wormhole node $  do

             local $ return params

             

             teleport

          --   local empty  `asTypeOf` typea params

             local empty



    restoreLog log                        --  !> "RESTORELOG"



    return  r

    where

    typea :: a -> Cloud a

    typea = undefined

    restoreLog (Log _ _ logw hash)= onAll $ do

       Log _ _ logw' hash' <- getSData <|> return emptyLog



       let newlog= reverse logw' ++ logw

--       return ()                 !> ("newlog", logw,logw')

       setData $ Log False newlog newlog (hash + hash')



    emptyLog= Log False [] [] 0



-- catchc :: Exception e => Cloud a -> (e -> Cloud a) -> Cloud a

-- catchc a b= Cloud $ catcht (runCloud' a) (\e -> runCloud' $ b e)



runEmbeddedService :: (Loggable a, Loggable b) =>  Service -> (a -> Cloud b) -> Cloud b

runEmbeddedService servname serv =  do

   node <- localIO $ do

          port <- freePort

          createNodeServ "localhost" (fromIntegral port) servname

   listen node

   wormhole (notused 4) $ loggedc $ do

      x <- local $ return (notused 0)

      r <- onAll $ runCloud (serv x) <** setData WasRemote

      local $ return r

      teleport

      return r



  



notused n= error $  "runService: "++ show (n::Int) ++ " variable should not be used"



runService :: (Loggable a, Loggable b) =>  Service -> Int -> (a -> Cloud b) -> Cloud b

runService servname defPort serv =  do

   onAll $ onException $ \(e :: SomeException)->  liftIO $ print e 

   initNodeServ servname

   service 

   where

   service=

       wormhole (notused 1) $  do

          x <- local . return $ notused 2

          r <- local $ runCloud  (serv x) -- <** setData WasRemote

          setData emptyLog

          local $ return r

          teleport

          return r



   emptyLog= Log False [] [] 0



   initNodeServ  servs=do

      mynode <- local  getNode



      local $ do

         conn <- defConnection

         liftIO $ writeIORef (myNode conn) mynode

         setState conn

      onAll inputAuthorizations <|> (inputNodes >> empty) <|> return ()

      listen mynode  



      where

      getNode :: TransIO Node

      getNode =  if isBrowserInstance then liftIO createWebNode else do

          oneThread $ option "start" "re/start node"

          host <- input' (Just "localhost") (const True) "hostname of this node (must be reachable) (\"localhost\"): "

          port <- input' (Just 3000) (const True)  "port to listen? (3000) "

          liftIO $ createNodeServ host port servs



      inputNodes= do

           onServer $ do

                  local $ option "add"  "add a new monitor node"



                  host <- local $ do

                            r <- input (const True) "Host to connect to: (none): "

                            if r ==  "" then stop else return r



                  port <-  local $ input (const True) "port? "



                  nnode <- localIO $ createNodeServ host port monitorService

                  local $ do

                       liftIO $ putStr "Added node: ">> print nnode

                       addNodes [nnode]

           empty

#else

requestInstance :: String -> Service -> Int -> Cloud [Node]

requestInstance ident service num= logged empty

#endif