{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts          #-}
{-# LANGUAGE FlexibleInstances         #-}
{-# LANGUAGE MultiParamTypeClasses     #-}
{-# LANGUAGE NoImplicitPrelude         #-}
{-# LANGUAGE OverloadedStrings         #-}
{-# LANGUAGE RankNTypes                #-}
-- |
-- = Getting started
--
-- == Dynamic reconfiguration
--
-- This program is dynamically configured using the <https://hackage.haskell.org/package/dyre dyre> library.
--
-- You may want to check out <https://hackage.haskell.org/package/dyre/docs/Config-Dyre.html this documentation> to know how to get started.
--
-- Your personal configuration is located at @$XDG_CONFIG_HOME\/imm\/imm.hs@.
--
-- == Handle pattern
--
-- The behavior of this program can be customized through the [Handle pattern](https://jaspervdj.be/posts/2018-03-08-handle-pattern.html).
module Imm.Boot (imm) where

-- {{{ Imports
import qualified Imm.Core                   as Core
import           Imm.Database               as Database
import           Imm.Database.FeedTable     as Database
import           Imm.Dyre                   as Dyre
import           Imm.Feed
import           Imm.Hooks as Hooks
import           Imm.HTTP                   as HTTP
import           Imm.Logger                 as Logger
import           Imm.Options                as Options hiding (logLevel)
import           Imm.Prelude
import           Imm.Pretty
import           Imm.XML as XML

import           Data.Conduit.Combinators   (stdin)
import           System.IO                  (hFlush)
-- }}}

-- | Main function, meant to be used in your personal configuration file.
--
-- Here is an example:
--
-- > import           Imm.Boot
-- > import           Imm.Database.JsonFile as Database
-- > import           Imm.Feed
-- > import           Imm.Hooks.SendMail as Hooks
-- > import           Imm.HTTP.Simple as HTTP
-- > import           Imm.Logger.Simple as Logger
-- > import           Imm.XML.Conduit as XML
-- >
-- > main :: IO ()
-- > main = do
-- >   logger     <- Logger.mkHandle <$> defaultLogger
-- >   database   <- Database.mkHandle <$> defaultDatabase
-- >   httpClient <- HTTP.mkHandle <$> defaultManager
-- >
-- >   imm logger database httpClient hooks xmlParser
-- >
-- > xmlParser :: XML.Handle IO
-- > xmlParser = XML.mkHandle defaultXmlParser
-- >
-- > hooks :: Hooks.Handle IO
-- > hooks = Hooks.mkHandle $ SendMailSettings smtpServer formatMail
-- >
-- > formatMail :: FormatMail
-- > formatMail = FormatMail
-- >   (\a b -> (defaultFormatFrom a b) { addressEmail = "user@host" } )
-- >   defaultFormatSubject
-- >   defaultFormatBody
-- >   (\_ _ -> [Address Nothing "user@host"])
-- >
-- > smtpServer :: Feed -> FeedElement -> SMTPServer
-- > smtpServer _ _ = SMTPServer
-- >   (Just $ Authentication PLAIN "user" "password")
-- >   (StartTls "smtp.host" defaultSettingsSMTPSTARTTLS)
imm :: Logger.Handle IO -> Database.Handle IO FeedTable -> HTTP.Handle IO -> Hooks.Handle IO -> XML.Handle IO -> IO ()
imm logger database httpClient hooks xmlParser = void $ do
  options <- parseOptions
  Dyre.wrap (optionDyreMode options) realMain (optionCommand options, optionLogLevel options, optionColorizeLogs options, logger, database, httpClient, hooks, xmlParser)

realMain :: (Command, LogLevel, Bool, Logger.Handle IO, Database.Handle IO FeedTable, HTTP.Handle IO, Hooks.Handle IO, XML.Handle IO) -> IO ()
realMain (command, logLevel, enableColors, logger, database, httpClient, hooks, xmlParser) = void $ do
  setColorizeLogs logger enableColors
  setLogLevel logger logLevel
  log logger Debug . ("Dynamic reconfiguration settings:" <++>) . indent 2 =<< Dyre.describePaths
  log logger Debug $ "Executing: " <> pretty command
  log logger Debug . ("Using database:" <++>) . indent 2 =<< _describeDatabase database

  handleAny (log logger Error . pretty . displayException) $ case command of
    Check t        -> Core.check logger database httpClient xmlParser =<< resolveTarget database ByPassConfirmation t
    Help           -> liftBase $ putStrLn helpString
    Import         -> Core.importOPML logger database stdin
    Read t         -> mapM_ (Database.markAsRead logger database) =<< resolveTarget database AskConfirmation t
    Run t          -> Core.run logger database httpClient hooks xmlParser =<< resolveTarget database ByPassConfirmation t
    Show t         -> Core.showFeed logger database =<< resolveTarget database ByPassConfirmation t
    ShowVersion    -> Core.printVersions
    Subscribe u c  -> Core.subscribe logger database u c
    Unread t       -> mapM_ (Database.markAsUnread logger database) =<< resolveTarget database AskConfirmation t
    Unsubscribe t  -> Database.deleteList logger database =<< resolveTarget database AskConfirmation t
    _              -> return ()

  Database.commit logger database
  flushLogs logger


-- * Util

data SafeGuard = AskConfirmation | ByPassConfirmation
  deriving(Eq, Read, Show)

data InterruptedException = InterruptedException deriving(Eq, Read, Show)
instance Exception InterruptedException where
  displayException _ = "Process interrupted"

promptConfirm :: Text -> IO ()
promptConfirm s = do
  putStr $ s <> " Confirm [Y/n] "
  hFlush stdout
  x <- getLine
  unless (null x || x == ("Y" :: Text)) $ throwM InterruptedException


resolveTarget :: MonadBase IO m => MonadThrow m => Database.Handle m FeedTable -> SafeGuard -> Maybe Core.FeedRef -> m [FeedID]
resolveTarget database s Nothing = do
  result <- keys <$> Database.fetchAll database
  when (s == AskConfirmation) $ liftBase $ promptConfirm $ "This will affect " <> show (length result) <> " feeds."
  return result
resolveTarget database _ (Just (ByUID i)) = do
  result <- fst . (!! (i-1)) . mapToList <$> Database.fetchAll database
  -- log logger Info $ "Target(s): " <> show (pretty result)
  return $ singleton result
resolveTarget _ _ (Just (ByURI uri)) = return [FeedID uri]