module Eventloop.Module.File.File
    ( setupFileModuleConfiguration
    , fileModuleIdentifier
    , fileEventRetriever
    , fileEventSender
    , fileTeardown
    ) where

import Data.Maybe
import Control.Concurrent.Datastructures.BlockingConcurrentQueue
import Control.Concurrent.STM
import System.IO

import Eventloop.Module.File.Types
import Eventloop.Types.Common
import Eventloop.Types.Events
import Eventloop.Types.System


setupFileModuleConfiguration :: EventloopSetupModuleConfiguration
setupFileModuleConfiguration = ( EventloopSetupModuleConfiguration
                                    fileModuleIdentifier
                                    (Just fileInitializer)
                                    (Just fileEventRetriever)
                                    Nothing
                                    Nothing
                                    (Just fileEventSender)
                                    (Just fileTeardown)
                                 )

fileModuleIdentifier :: EventloopModuleIdentifier
fileModuleIdentifier = "file"


fileInitializer :: Initializer
fileInitializer sharedConst sharedIO
    = do
        inQueue <- createBlockingConcurrentQueue
        return (sharedConst, sharedIO, FileConstants inQueue, FileState [])


fileEventRetriever :: EventRetriever
fileEventRetriever sharedConst sharedIOT ioConst ioStateT
    = do
        fileInEvents <- takeAllFromBlockingConcurrentQueue queue
        return (map InFile fileInEvents)
    where
        queue = fileInQueue ioConst


fileEventSender :: EventSender
fileEventSender _ _ _ _ Stop = return ()
fileEventSender sharedConst sharedIOT ioConst ioStateT (OutFile out)
    = do
        (FileState openFiles) <- readTVarIO ioStateT
        (openFiles', inEvents) <- fileEventSender' openFiles out
        atomically $ writeTVar ioStateT (FileState openFiles')
        putAllInBlockingConcurrentQueue inQueue inEvents
    where
        inQueue = fileInQueue ioConst


fileEventSender' :: [OpenFile] -> FileOut -> IO ([OpenFile], [FileIn])
fileEventSender' openFiles (OpenFile filepath iomode)
    = do
        handle <- openFile filepath iomode
        let
            fileOpenedEvent = FileOpened filepath True
            openFiles' = openFiles ++ [(filepath, handle, iomode)]
        return (openFiles', [fileOpenedEvent])

fileEventSender' openFiles (CloseFile filepath)
    | openFileM == Nothing = return ([], [])
    | otherwise = do
                   hClose handle
                   return (openFiles', [closedFileEvent])
    where
        openFileM = retrieveOpenedFile openFiles filepath
        (fp, handle, iomode) = fromJust openFileM
        openFiles' = removeOpenedFile openFiles filepath
        closedFileEvent = FileClosed filepath True

fileEventSender' openFiles (RetrieveContents filepath)
    = doReadAction filepath openFiles RetrievedContents retrieveContents

fileEventSender' openFiles (RetrieveLine filepath)
     = doReadAction filepath openFiles RetrievedLine hGetLine

fileEventSender' openFiles (RetrieveChar filepath)
    = doReadAction filepath openFiles RetrievedChar hGetChar

fileEventSender' openFiles (IfEOF filepath)
    = getFromFile filepath openFiles fileIsOpened IsEOF hIsEOF

fileEventSender' openFiles (WriteTo filepath contents)
    | fileIsWriteable openFiles filepath = do
        hPutStr handle contents
        return (openFiles, [WroteTo filepath True])
    | otherwise = return (openFiles, [])
    where
        Just (fp, handle, iomode) = retrieveOpenedFile openFiles filepath


doReadAction :: FilePath
             -> [OpenFile]
             -> (FilePath -> a -> FileIn)
             -> (Handle -> IO a)
             -> IO ([OpenFile], [FileIn])
doReadAction filepath openFiles inEvent readAction
    = getFromFile filepath openFiles fileIsReadable inEvent readAction


getFromFile :: FilePath ->
               [OpenFile] ->
               ([OpenFile] -> FilePath -> Bool) -> {- Check if the action should be done -}
               (FilePath -> a -> FileIn) -> {- The inEvent Constructor -}
               (Handle -> IO a) ->  {- The action which will grant a result -}
               IO ([OpenFile], [FileIn])
getFromFile filepath openFiles fileCheck inEvent action
    | fileCheck openFiles filepath = do
        result <- action handle
        return (openFiles, [inEvent filepath result])
    | otherwise                 = return (openFiles, [])
    where
        Just (fp, handle, iomode) = retrieveOpenedFile openFiles filepath


fileIsReadable :: [OpenFile] -> FilePath -> Bool
fileIsReadable opened filepath | fileIsOpened opened filepath = iomode == ReadMode || iomode == ReadWriteMode
                               | otherwise                    = False
                              where
                                  Just (fp, handle, iomode) = retrieveOpenedFile opened filepath


fileIsWriteable :: [OpenFile] -> FilePath -> Bool
fileIsWriteable opened filepath | fileIsOpened opened filepath = iomode == WriteMode || iomode == ReadWriteMode || iomode == AppendMode
                                | otherwise                    = False
                                where
                                    Just (fp, handle, iomode) = retrieveOpenedFile opened filepath


fileIsOpened :: [OpenFile] -> FilePath -> Bool
fileIsOpened opened filepath = not (openedFileM == Nothing)
                             where
                                openedFileM = retrieveOpenedFile opened filepath


retrieveContents :: Handle -> IO [[Char]]
retrieveContents handle = do
                            line <- hGetLine handle
                            isEOF <- hIsEOF handle
                            if isEOF
                                then
                                    return [line]
                                else do
                                    lines <- retrieveContents handle
                                    return (line:lines)


retrieveOpenedFile :: [OpenFile] -> FilePath -> Maybe OpenFile
retrieveOpenedFile [] _ = Nothing
retrieveOpenedFile (openfile@(fp, h, iom):ofs) ufp | ufp == fp = Just openfile
                                                   | otherwise = retrieveOpenedFile ofs ufp


removeOpenedFile :: [OpenFile] -> FilePath -> [OpenFile]
removeOpenedFile [] _ = []
removeOpenedFile (openfile@(fp, h, iom):ofs) ufp | ufp == fp = ofs
                                                 | otherwise = openfile:(removeOpenedFile ofs ufp)


fileTeardown :: Teardown
fileTeardown sharedConst sharedIO ioConst ioState
    = do
         closeAllFiles handles
         return (sharedIO)
    where
        handles = map (\(fp, h, iom) -> h) (opened ioState)


closeAllFiles :: [Handle] -> IO ()
closeAllFiles [] = return ()
closeAllFiles (h:hs) = do
                         hClose h
                         closeAllFiles hs