{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneDeriving #-}
module Status(editStatusFile, setStatusFromFile, updateStatus) where

import           Control.Monad          ( when )
import           Control.Exception.Safe ( onException )
import qualified Data.Text as T
import           Text.Read              ( readMaybe )

import           Discord.Types
import           Discord                ( sendCommand
                                        , DiscordHandler
                                        )
import           UnliftIO               ( liftIO )
import           CSV                    ( readCSV
                                        , writeCSV
                                        )
import           Command

-- | These datatypes in discord-haskell do not derive Read, but it's kinda
-- necessary to do @readMaybe@, so here we go:
deriving instance Read UpdateStatusType
deriving instance Read ActivityType

-- | 'updateStatus' updates the status through the Discord gateway.
-- Therefore, it requires DiscordHandler and is not polymorphic.
updateStatus :: UpdateStatusType -> ActivityType -> T.Text -> DiscordHandler ()
updateStatus :: UpdateStatusType -> ActivityType -> Text -> DiscordHandler ()
updateStatus UpdateStatusType
newStatus ActivityType
newType Text
newName = GatewaySendable -> DiscordHandler ()
sendCommand (GatewaySendable -> DiscordHandler ())
-> GatewaySendable -> DiscordHandler ()
forall a b. (a -> b) -> a -> b
$
    UpdateStatusOpts -> GatewaySendable
UpdateStatus (UpdateStatusOpts -> GatewaySendable)
-> UpdateStatusOpts -> GatewaySendable
forall a b. (a -> b) -> a -> b
$ UpdateStatusOpts :: Maybe UTCTime
-> Maybe Activity -> UpdateStatusType -> Bool -> UpdateStatusOpts
UpdateStatusOpts
        { updateStatusOptsSince :: Maybe UTCTime
updateStatusOptsSince = Maybe UTCTime
forall a. Maybe a
Nothing
        , updateStatusOptsGame :: Maybe Activity
updateStatusOptsGame = Activity -> Maybe Activity
forall a. a -> Maybe a
Just (Activity -> Maybe Activity) -> Activity -> Maybe Activity
forall a b. (a -> b) -> a -> b
$ Activity :: Text -> ActivityType -> Maybe Text -> Activity
Activity
            { activityName :: Text
activityName = Text
newName
            , activityType :: ActivityType
activityType = ActivityType
newType
            , activityUrl :: Maybe Text
activityUrl = Maybe Text
forall a. Maybe a
Nothing
            }
        , updateStatusOptsNewStatus :: UpdateStatusType
updateStatusOptsNewStatus = UpdateStatusType
newStatus
        , updateStatusOptsAFK :: Bool
updateStatusOptsAFK = Bool
False
        }

-- | @setStatusFromFile@ reads "status.csv" from the Config directory, and
-- reads in the 3 columns as 'UpdateStatusType', 'ActivityType', and 'T.Text'.
-- The values are used to call 'updateStatus'. 
--
-- Incorrect formats (read parse errors) are ignored.
setStatusFromFile :: DiscordHandler ()
setStatusFromFile :: DiscordHandler ()
setStatusFromFile = do
    [Text]
line <- IO [Text] -> ReaderT DiscordHandle IO [Text]
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO IO [Text]
readStatusFile
    Bool -> DiscordHandler () -> DiscordHandler ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Text] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
line Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
3) (DiscordHandler () -> DiscordHandler ())
-> DiscordHandler () -> DiscordHandler ()
forall a b. (a -> b) -> a -> b
$ do
        -- Utilising the Maybe Monad whooo!
        let mbStuff :: Maybe (UpdateStatusType, ActivityType, Text)
mbStuff = do
                UpdateStatusType
statusType <- (String -> Maybe UpdateStatusType
forall a. Read a => String -> Maybe a
readMaybe (String -> Maybe UpdateStatusType)
-> ([Text] -> String) -> [Text] -> Maybe UpdateStatusType
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack (Text -> String) -> ([Text] -> Text) -> [Text] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> Text
forall a. [a] -> a
head) [Text]
line
                ActivityType
activityType <- (String -> Maybe ActivityType
forall a. Read a => String -> Maybe a
readMaybe (String -> Maybe ActivityType)
-> ([Text] -> String) -> [Text] -> Maybe ActivityType
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack (Text -> String) -> ([Text] -> Text) -> [Text] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> Text
forall a. [a] -> a
head ([Text] -> Text) -> ([Text] -> [Text]) -> [Text] -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> [Text]
forall a. [a] -> [a]
tail) [Text]
line
                let name :: Text
name = [Text] -> Text
T.unwords ([Text] -> Text) -> [Text] -> Text
forall a b. (a -> b) -> a -> b
$ ([Text] -> [Text]
forall a. [a] -> [a]
tail ([Text] -> [Text]) -> ([Text] -> [Text]) -> [Text] -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> [Text]
forall a. [a] -> [a]
tail) [Text]
line
                (UpdateStatusType, ActivityType, Text)
-> Maybe (UpdateStatusType, ActivityType, Text)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (UpdateStatusType
statusType, ActivityType
activityType, Text
name)
        case Maybe (UpdateStatusType, ActivityType, Text)
mbStuff of
            Maybe (UpdateStatusType, ActivityType, Text)
Nothing -> IO () -> DiscordHandler ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> DiscordHandler ()) -> IO () -> DiscordHandler ()
forall a b. (a -> b) -> a -> b
$ String -> IO ()
putStrLn String
"Incorrect status format, ignoring."
            Just (UpdateStatusType
s, ActivityType
a, Text
n) -> UpdateStatusType -> ActivityType -> Text -> DiscordHandler ()
updateStatus UpdateStatusType
s ActivityType
a Text
n

-- | @editStatusFile@ puts the status values into "status.csv" by calling
-- 'show' on them and converting it to 'T.Text'.
editStatusFile :: UpdateStatusType -> ActivityType -> T.Text -> IO ()
editStatusFile :: UpdateStatusType -> ActivityType -> Text -> IO ()
editStatusFile UpdateStatusType
newStatus ActivityType
newType Text
newName =
    String -> [[Text]] -> IO ()
writeCSV String
"status.csv" [[String -> Text
T.pack (UpdateStatusType -> String
forall a. Show a => a -> String
show UpdateStatusType
newStatus), String -> Text
T.pack (ActivityType -> String
forall a. Show a => a -> String
show ActivityType
newType), Text
newName]]

-- | @readStatusFile@ is a wrapper around 'readCSV' that returns only the first
-- row, if it exists.
readStatusFile :: IO [T.Text]
readStatusFile :: IO [Text]
readStatusFile = do
    [[Text]]
contents <- String -> IO [[Text]]
readCSV String
"status.csv"
    [Text] -> IO [Text]
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([Text] -> IO [Text]) -> [Text] -> IO [Text]
forall a b. (a -> b) -> a -> b
$ if [[Text]] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [[Text]]
contents
        then []
        else [[Text]] -> [Text]
forall a. [a] -> a
head [[Text]]
contents