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

{- |
  Module     : Holumbus.FileSystem.Node.NodeData
  Copyright  : Copyright (C) 2008 Stefan Schmidt
  License    : MIT

  Maintainer : Stefan Schmidt (stefanschmidt@web.de)
  Stability  : experimental
  Portability: portable
  Version    : 0.1


-}

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

module Holumbus.FileSystem.Node.NodeData
(
-- * data types
  NodeData

-- * creation and destruction
, newNode
)
where

import           Prelude hiding (appendFile)

import           Control.Concurrent
import           Control.Monad
import           Data.Maybe
import           System.Log.Logger

import           Holumbus.Common.Debug
import           Holumbus.Common.Utils
import           Holumbus.FileSystem.Node
import           Holumbus.FileSystem.Node.NodePort
import qualified Holumbus.FileSystem.Controller as C
import qualified Holumbus.FileSystem.Controller.ControllerPort as CP
import qualified Holumbus.FileSystem.Messages as M
import qualified Holumbus.FileSystem.Storage as S
import qualified Holumbus.FileSystem.Storage.FileStorage as FS

import           Holumbus.Network.Communication

localLogger :: String
localLogger = "Holumbus.FileSystem.Node.NodeData"


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

      
-- | Node datatype.
data NodeData = NodeData {
    nd_Client          :: Client
  , nd_Storage         :: FS.FileStorage
  }

data Node = Node (MVar NodeData)

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

newNode :: StreamName -> Maybe SocketId -> FS.FileStorage -> IO Node
newNode sn soid stor
  = do
    -- initialise the client
    nodedata <- newEmptyMVar
    client <- newClient sn soid (dispatch (Node nodedata))
    -- open storage
    stor' <- S.openStorage stor             
    putMVar nodedata (NodeData client stor')
    return (Node nodedata)


dispatch 
  :: Node
  -> M.NodeRequestMessage 
  -> IO (Maybe M.NodeResponseMessage)
dispatch nd msg
  = do
    case msg of
      (M.NReqCreate i c) ->
        do
        createFile i c nd
        return $ Just M.NRspSuccess        
      (M.NReqCreateS l) ->
        do
        createFiles l nd
        return $ Just M.NRspSuccess
      (M.NReqAppend i c) -> 
        do
        appendFile i c nd
        return $ Just M.NRspSuccess
      (M.NReqDelete i b) ->
        do
        deleteFile i b nd
        return $ Just M.NRspSuccess
      (M.NReqCopy i cp) ->
        do
        copyFile i cp nd
        return $ Just M.NRspSuccess
      (M.NReqContains i)-> 
        do
        b <- containsFile i nd
        return $ Just $ M.NRspContains b
      (M.NReqGetFileContent i) ->
        do
        c <- getFileContent i nd
        return $ Just $ M.NRspGetFileContent c
      (M.NReqGetMultiFileContent l) ->
        do
--        mvar <- newEmptyMVar
--        _ <- forkIO $ multiThread l nd mvar
--        lc <- takeMVar mvar
        lc <- getMultiFileContent l nd
        return $ Just $ M.NRspGetMultiFileContent lc
      (M.NReqGetFileData i) ->
        do
        d <- getFileData i nd
        return $ Just $ M.NRspGetFileData d
      (M.NReqGetFileIds) ->
        do
        ls <- getFileIds nd
        return $ Just $ M.NRspGetFileIds ls
      _ -> return Nothing 



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


changeStorage :: (FS.FileStorage -> IO FS.FileStorage) -> Node -> IO ()
changeStorage f (Node node)
  = modifyMVar node $ 
      \nd -> 
      do
      stor' <- f (nd_Storage nd)
      return (nd {nd_Storage = stor'}, ())


readStorage :: (FS.FileStorage-> IO a) -> Node -> IO a
readStorage f (Node node)
  = do
    a <- withMVar node $ \nd -> f (nd_Storage nd)    
    return a

logStorage :: Node -> IO ()
logStorage (Node node) = withMVar node $ \nd ->  logStorage2 (nd_Client nd) 
  where
  logStorage2 (Client client) = withMVar client $ \cd -> debugM "measure.readStorage" ((show . cd_SiteId) cd)


{-
multiThread :: [S.FileId] -> Node -> MVar [(S.FileId,S.FileContent)] -> IO ()
multiThread l' nd mvar = do
  l <- getMultiFileContent l' nd
  putMVar mvar l
  return ()
-}

-- ----------------------------------------------------------------------------
-- Typeclass instanciation (NodeClass)
-- ----------------------------------------------------------------------------


instance NodeClass Node where
  

  closeNode (Node node)
    = modifyMVar node $
        \nd ->
        do
        infoM localLogger "closing filesystem node"
        closeClient (nd_Client nd)
        return (nd,())
      
     
  --createFile :: S.FileId -> S.FileContent -> Node -> IO ()
  createFile i c n@(Node node)
    = do
      client <- withMVar node $ \nd -> return (nd_Client nd)
      nid <- getClientId client
      case nid of
        (Nothing) ->
          do
          --TODO better exception-handling
          errorM localLogger $ "createFile \"" ++ i ++ "\" - no nid"
          return ()
        (Just nid') ->
          do
          changeStorage (\stor -> S.createFile stor i c) n 
          server <- getServerPort client
          let cp = CP.newControllerPortFromServerPort server
          C.createFile i nid' cp
          return ()
          
--  createFiles l n = mapM_ (\(fid,c) -> createFile fid c n) l
  --createFiles :: [(S.FileId,S.FileContent)] -> Node -> IO ()
  createFiles l n@(Node node)
    = do
      client <- withMVar node $ \nd -> return (nd_Client nd)
      nid <- getClientId client
      case nid of
        (Nothing) ->
          do
          --TODO better exception-handling
          errorM localLogger $ "createFiles - no nid"
          return ()
        (Just nid') ->
          do
          mapM_ (\(i,c) -> changeStorage (\stor -> S.createFile stor i c) n) l
          server <- getServerPort client
          let cp = CP.newControllerPortFromServerPort server
          let fns = map (\(i,_) -> (i,nid')) l
          C.createFiles fns  cp
          return ()


  --appendFile :: S.FileId -> S.FileContent -> Node -> IO ()
  appendFile i c n@(Node node)
    = do
      client <- withMVar node $ \nd -> return (nd_Client nd)
      nid <- getClientId client
      case nid of
        (Nothing) ->
          do
          --TODO better exception-handling          
          errorM localLogger $ "appendFile \"" ++ i ++ "\" - no nid"
          return ()
        (Just nid') ->
          do
          changeStorage (\stor -> S.appendFile stor i c) n
          server <- getServerPort client
          let cp = CP.newControllerPortFromServerPort server
          -- Inform Controller that file has changed.
          C.appendFile i nid' cp          
          return ()


  --deleteFile :: S.FileId -> Node -> IO ()
  deleteFile i b n@(Node node)
    = do
      client <- withMVar node $ \nd -> return (nd_Client nd)
      nid <- getClientId client
      case nid of
        (Nothing) ->
          do
          --TODO better exception-handling
          errorM localLogger $ "deleteFile \"" ++ i ++ "\" - no nid"
          return ()
        (Just nid') ->
          do
          changeStorage (\stor -> S.deleteFile stor i) n
          if (b) 
            then do
              server <- getServerPort client
              let cp = CP.newControllerPortFromServerPort server
              C.deleteFile i nid' cp
              return ()
            else return ()


  copyFile i cp n
    = do
      let np = newNodePort cp
      c <- getFileContent i np
      -- TODO do not create new file data 
      --d <- getFileData i np
      if (isJust c) 
        then do
          createFile i (fromJust c) n
          return ()
        else do
          errorM localLogger $ "copyFile: no content for \"" ++ i ++ "\" found"
          return ()


  --containsFile :: S.FileId -> Node -> IO Bool
  containsFile i n
    = do
      readStorage (\stor -> S.containsFile stor i) n


  --getFileContent :: S.FileId -> Node -> IO (Maybe S.FileContent)
  getFileContent i n
    = do
      c <- readStorage (\stor -> S.getFileContent stor i) n
      debugM localLogger $ "getFileContent: " ++ show c
      logStorage n 
      return c

  --getMultiFileContent :: [S.FileId] -> Node -> IO [(S.FileId,S.FileContent)]
  getMultiFileContent l n
    = do
      result <- foldM f [] l
      debugM localLogger $ "getFileContents: " ++ show result
      logStorage n
      return result
      where
      f :: [(S.FileId,S.FileContent)] -> S.FileId -> IO [(S.FileId,S.FileContent)]
      f res filename = do
        maybecontent <- readStorage (\stor -> S.getFileContent stor filename) n
        case maybecontent of
          (Just c) -> return ((filename,c):res)
          Nothing   -> return res


  --getFileData :: S.FileId -> Node -> IO (Maybe S.FileData)
  getFileData i nd
    = do
      readStorage (\stor -> S.getFileData stor i) nd


  --getFileIds :: Node -> IO [S.FileId]
  getFileIds nd
    = do
      readStorage (\stor -> S.getFileIds stor) nd




instance Debug Node where
  printDebug (Node node)
    = do
      putStrLn "Node-Object (full)"
      withMVar node $
        \nd ->
        do
        printDebug (nd_Client nd)
        putStrLn $ prettyRecordLine gap "Storage:" (nd_Storage nd)
        where
        gap = 20
  getDebug (Node node)
    = do      
      tmp <- withMVar node $
        \nd ->
        do
        tmp <- getDebug (nd_Client nd)
        return (tmp++"\n"++ (prettyRecordLine gap "Storage:" (nd_Storage nd)) ++"\n")
      return ("Node-Object (full)"++"\n"++tmp++"\n")
        where
        gap = 20