{-# LANGUAGE ScopedTypeVariables, TypeFamilies #-}
module Imm.Core (
-- * Types
    FeedConfig,
    FeedList,
-- * Actions
    dispatch,
    importOPML,
    check,
    showStatus,
    markAsRead,
    markAsUnread,
    update,
) where

-- {{{ Imports
import Imm.Config
import Imm.Database
import Imm.Error
import Imm.Feed (FeedParser(..))
import qualified Imm.Feed as Feed
import Imm.Maildir (MaildirWriter(..))
import qualified Imm.Maildir as Maildir
import Imm.Mail (MailFormatter(..))
import qualified Imm.Mail as Mail
import Imm.OPML as OPML
import Imm.Util

import Control.Concurrent.Async
import Control.Monad hiding(forM_, mapM_)
import Control.Monad.Error hiding(forM_, mapM_)
-- import Control.Monad.Reader hiding(forM_, mapM_)

import Data.Foldable hiding(foldr)
import Data.Time as T

import Prelude hiding(log, mapM_, sum)

import System.Log.Logger

import Text.Feed.Query as F
import Text.Feed.Types as F
-- }}}

-- {{{ Types
type FeedConfig = (Config -> Config, FeedID)
type FeedList   = [FeedConfig]
-- }}}


dispatch :: (Config -> Config) -> Feed.Action -> FeedList -> IO ()
dispatch baseConfig Feed.Check        feeds = void $ mapConcurrently (check baseConfig) feeds
dispatch baseConfig Feed.ShowStatus   feeds = mapM_ (showStatus baseConfig) feeds
dispatch baseConfig Feed.MarkAsRead   feeds = mapM_ (markAsRead baseConfig) feeds
dispatch baseConfig Feed.MarkAsUnread feeds = mapM_ (markAsUnread baseConfig) feeds
dispatch baseConfig Feed.Update       feeds = void $ mapConcurrently (update baseConfig) feeds


importOPML :: (MonadBase IO m, MonadPlus m) => String -> m ()
importOPML = mapM_ addFeeds . OPML.read


check :: (Config -> Config) -> FeedConfig -> IO ()
check baseConfig (f, feedID) = withError "imm.core". withConfig (f . baseConfig) $ Feed.download feedID >>= Feed.check


showStatus :: (Config -> Config) -> FeedConfig -> IO ()
showStatus baseConfig (f, feedID) = withConfig (f . baseConfig) (io . noticeM "imm.core" =<< Feed.showStatus feedID)


markAsRead :: (Config -> Config) -> FeedConfig -> IO ()
markAsRead baseConfig (f, feedID) = withError "imm.core" . withConfig (f . baseConfig) $ Feed.markAsRead feedID


markAsUnread :: (Config -> Config) -> FeedConfig -> IO ()
markAsUnread baseConfig (f, feedID) = withError "imm.core" . withConfig (f . baseConfig) $ Feed.markAsUnread feedID


-- | Write mails for each new item, and update the last check time in state file.
update :: (Config -> Config) -> FeedConfig -> IO ()
update baseConfig (f, feedID) = withError "imm.core" . withConfig (f . baseConfig) $ do
    -- io . noticeM "imm.core" $ "Updating: " ++ show feedID
    (uri, feed) <- Feed.download feedID

    Maildir.init

    io . debugM "imm.core" $ Feed.describe feed

    lastCheck <- getLastCheck uri
    (results :: [Integer]) <- forM (feedItems feed) $ \item -> do
        date <- Feed.getDate item
        (date > lastCheck) ? (updateItem (item, feed) >> return 1) ?? return 0
    io . noticeM "imm.core" $ "==> " ++ show (sum results) ++ " new item(s) for <" ++ show feedID ++ ">"
    Feed.markAsRead uri


updateItem :: (Applicative m, FeedParser m, MaildirWriter m, MailFormatter m, MonadBase IO m, MonadError ImmError m) => (Item, Feed) -> m ()
updateItem (item, feed) = do
    timeZone <- io getCurrentTimeZone
    Maildir.write =<< Mail.build timeZone (item, feed)