{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving #-}
module HackMail.Data.MainTypes   ( module HackMail.Data.Path
                                 , module HackMail.Data.Email
                                 , Options (..), getOpts
                                 , Config (..)
                                 , Filter (..), runFilter) where

import Control.Arrow
import Control.Applicative
import Control.Monad
import Control.Monad.Reader

import Data.List
import Data.Typeable

-- TODO: Export Deliverable from here to?
import HackMail.Control.Misc
import HackMail.Data.Path
import HackMail.Data.Email    
import HackMail.Data.ParseEmail

data SortOpt a = EqOpt a | RegOpt a
        deriving Show

data Options = Opt { daemonMode :: Bool
                   , incomingMailLoc :: Maybe FilePath -- only needed if daemonMode |- True
                   , altFMainLoc :: Maybe FilePath -- not currently used.
                   }
        deriving (Eq, Show)     

-- |A type mostly used in Hackmain.hs, stores some information about paths. Only here to avoid nasty
-- mutually recursive modules. (TODO: Move this and associated functions another module?) 
data Config = Conf { inboxLoc           :: Path 
                   , filterMainLoc      :: FilePath
                   , filterMain         :: Filter ()     
                   } -- what exactly do I need to store in the config? 
        deriving (Typeable)

instance Show Config where
        show (Conf inbox filterMain _) = "Conf | inbox :=" ++ (show inbox) 
                                      ++ " FilterMain := " ++ filterMain         

getOpts =   parseOpts
        <<< map optNormalForm           <<< pairToList 
        <<< (dupe map) (EqOpt, RegOpt)  <<< (copy filter) (eqOpt, regOpt) 
        where   -- catches if an option is of the form -x=blah 
                eqOpt = any (=='=')
                -- catches options of the form "-d foo"
                regOpt = not . eqOpt
                isOpt (c:_) = (c `elem` "+-")

optNormalForm :: SortOpt String -> (String, String)
optNormalForm (EqOpt s)  = both tail . break (=='=') $ s 
optNormalForm (RegOpt s) = both tail . break (==' ') $ s 

parseOpts :: [(String, String)] -> Options
parseOpts s = Opt dMode incMailLoc altFMain 
        where   dMode      = maybeToBool . findOpt "d" $ s
                incMailLoc = findOpt "i" s 
                altFMain   = findOpt "c" s      

findOpt :: String -> [(String, String)] -> Maybe String 
findOpt b s = snd <$> find (\(x,y) -> x == b) s


-- has to be here, otherwise it cycles imports.

-- |A simple type to contain the email context. May become bigger, to store configuration details,
-- etc.
newtype Filter a = Filter (ReaderT (Config, Email) IO a)
        deriving (Monad, Functor, Typeable, MonadReader (Config, Email),  MonadIO)

runFilter :: Filter a -> (Config, Email) -> IO a
runFilter (Filter f) = runReaderT f
--- Typable instance time.