module Lambdabot.Plugin.Social.Seen.UserStatus where

import Control.Applicative
import Data.Binary
import qualified Data.ByteString as BS
import Data.List
import Lambdabot.Compat.AltTime
import Lambdabot.Compat.PackedNick
import Lambdabot.Plugin.Social.Seen.StopWatch

-- | The type of channels
type Channel = BS.ByteString

-- | We last heard the user speak at ClockTime; since then we have missed
--   TimeDiff of him because we were absent.
type LastSpoke = Maybe (ClockTime, TimeDiff)

-- | 'UserStatus' keeps track of the status of a given Nick name.
data UserStatus
    = Present    !LastSpoke [Channel]
        -- ^ Records when the nick last spoke and that the nick is currently
        --   in [Channel].
    | NotPresent !ClockTime !StopWatch [Channel]
        -- ^ The nick is not present and was last seen at ClockTime in Channel.
        --   The second argument records how much we've missed.
    | WasPresent !ClockTime !StopWatch !LastSpoke [Channel]
        -- ^ The bot parted a channel where the user was. The Clocktime
        --   records the time and Channel the channel this happened in.
        --   We also save the reliablility of our information and the
        --   time we last heard the user speak.
    | NewNick !PackedNick
        -- ^ The user changed nick to something new.
    deriving (Int -> UserStatus -> ShowS
[UserStatus] -> ShowS
UserStatus -> String
(Int -> UserStatus -> ShowS)
-> (UserStatus -> String)
-> ([UserStatus] -> ShowS)
-> Show UserStatus
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [UserStatus] -> ShowS
$cshowList :: [UserStatus] -> ShowS
show :: UserStatus -> String
$cshow :: UserStatus -> String
showsPrec :: Int -> UserStatus -> ShowS
$cshowsPrec :: Int -> UserStatus -> ShowS
Show, ReadPrec [UserStatus]
ReadPrec UserStatus
Int -> ReadS UserStatus
ReadS [UserStatus]
(Int -> ReadS UserStatus)
-> ReadS [UserStatus]
-> ReadPrec UserStatus
-> ReadPrec [UserStatus]
-> Read UserStatus
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [UserStatus]
$creadListPrec :: ReadPrec [UserStatus]
readPrec :: ReadPrec UserStatus
$creadPrec :: ReadPrec UserStatus
readList :: ReadS [UserStatus]
$creadList :: ReadS [UserStatus]
readsPrec :: Int -> ReadS UserStatus
$creadsPrec :: Int -> ReadS UserStatus
Read)

instance Binary UserStatus where
    put :: UserStatus -> Put
put (Present LastSpoke
sp [Channel]
ch)          = Word8 -> Put
putWord8 Word8
0 Put -> Put -> Put
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> LastSpoke -> Put
forall t. Binary t => t -> Put
put LastSpoke
sp Put -> Put -> Put
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> [Channel] -> Put
forall t. Binary t => t -> Put
put [Channel]
ch
    put (NotPresent ClockTime
ct StopWatch
sw [Channel]
ch)    = Word8 -> Put
putWord8 Word8
1 Put -> Put -> Put
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> ClockTime -> Put
forall t. Binary t => t -> Put
put ClockTime
ct Put -> Put -> Put
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> StopWatch -> Put
forall t. Binary t => t -> Put
put StopWatch
sw Put -> Put -> Put
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> [Channel] -> Put
forall t. Binary t => t -> Put
put [Channel]
ch
    put (WasPresent ClockTime
ct StopWatch
sw LastSpoke
sp [Channel]
ch) = Word8 -> Put
putWord8 Word8
2 Put -> Put -> Put
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> ClockTime -> Put
forall t. Binary t => t -> Put
put ClockTime
ct Put -> Put -> Put
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> StopWatch -> Put
forall t. Binary t => t -> Put
put StopWatch
sw Put -> Put -> Put
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> LastSpoke -> Put
forall t. Binary t => t -> Put
put LastSpoke
sp Put -> Put -> Put
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> [Channel] -> Put
forall t. Binary t => t -> Put
put [Channel]
ch
    put (NewNick Channel
n)              = Word8 -> Put
putWord8 Word8
3 Put -> Put -> Put
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> Channel -> Put
forall t. Binary t => t -> Put
put Channel
n

    get :: Get UserStatus
get = Get Word8
getWord8 Get Word8 -> (Word8 -> Get UserStatus) -> Get UserStatus
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \Word8
h -> case Word8
h of
        Word8
0 -> LastSpoke -> [Channel] -> UserStatus
Present    (LastSpoke -> [Channel] -> UserStatus)
-> Get LastSpoke -> Get ([Channel] -> UserStatus)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get LastSpoke
forall t. Binary t => Get t
get Get ([Channel] -> UserStatus) -> Get [Channel] -> Get UserStatus
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Get [Channel]
forall t. Binary t => Get t
get
        Word8
1 -> ClockTime -> StopWatch -> [Channel] -> UserStatus
NotPresent (ClockTime -> StopWatch -> [Channel] -> UserStatus)
-> Get ClockTime -> Get (StopWatch -> [Channel] -> UserStatus)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get ClockTime
forall t. Binary t => Get t
get Get (StopWatch -> [Channel] -> UserStatus)
-> Get StopWatch -> Get ([Channel] -> UserStatus)
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Get StopWatch
forall t. Binary t => Get t
get Get ([Channel] -> UserStatus) -> Get [Channel] -> Get UserStatus
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Get [Channel]
forall t. Binary t => Get t
get
        Word8
2 -> ClockTime -> StopWatch -> LastSpoke -> [Channel] -> UserStatus
WasPresent (ClockTime -> StopWatch -> LastSpoke -> [Channel] -> UserStatus)
-> Get ClockTime
-> Get (StopWatch -> LastSpoke -> [Channel] -> UserStatus)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get ClockTime
forall t. Binary t => Get t
get Get (StopWatch -> LastSpoke -> [Channel] -> UserStatus)
-> Get StopWatch -> Get (LastSpoke -> [Channel] -> UserStatus)
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Get StopWatch
forall t. Binary t => Get t
get Get (LastSpoke -> [Channel] -> UserStatus)
-> Get LastSpoke -> Get ([Channel] -> UserStatus)
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Get LastSpoke
forall t. Binary t => Get t
get Get ([Channel] -> UserStatus) -> Get [Channel] -> Get UserStatus
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Get [Channel]
forall t. Binary t => Get t
get
        Word8
3 -> Channel -> UserStatus
NewNick    (Channel -> UserStatus) -> Get Channel -> Get UserStatus
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Get Channel
forall t. Binary t => Get t
get
        Word8
_ -> String -> Get UserStatus
forall a. HasCallStack => String -> a
error String
"Seen.UserStatus.get"

-- | Update the user status when a user joins a channel.
updateJ :: Maybe ClockTime -- ^ If the bot joined the channel, the time that
                           --   happened, i.e. now.
    -> [Channel]           -- ^ The channels the user joined.
    -> UserStatus          -- ^ The old status
    -> UserStatus          -- ^ The new status
-- The user was present before, so he's present now.
updateJ :: Maybe ClockTime -> [Channel] -> UserStatus -> UserStatus
updateJ Maybe ClockTime
_ [Channel]
c (Present LastSpoke
ct [Channel]
cs) = LastSpoke -> [Channel] -> UserStatus
Present LastSpoke
ct ([Channel] -> UserStatus) -> [Channel] -> UserStatus
forall a b. (a -> b) -> a -> b
$ [Channel] -> [Channel]
forall a. Eq a => [a] -> [a]
nub ([Channel]
c [Channel] -> [Channel] -> [Channel]
forall a. [a] -> [a] -> [a]
++ [Channel]
cs)
-- The user was present when we left that channel and now we've come back.
-- We need to update the time we've missed.
updateJ (Just ClockTime
now) [Channel]
cs (WasPresent ClockTime
lastSeen StopWatch
_ (Just (ClockTime
lastSpoke, TimeDiff
missed)) [Channel]
channels)
    | [Channel] -> Channel
forall a. [a] -> a
head [Channel]
channels Channel -> [Channel] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Channel]
cs
    ---                 newMissed
    --- |---------------------------------------|
    --- |-------------------|                   |
    ---        missed    lastSeen              now
    = let newMissed :: TimeDiff
newMissed = TimeDiff -> ClockTime -> ClockTime
addToClockTime TimeDiff
missed ClockTime
now ClockTime -> ClockTime -> TimeDiff
`diffClockTimes` ClockTime
lastSeen
       in TimeDiff
newMissed TimeDiff -> UserStatus -> UserStatus
`seq` LastSpoke -> [Channel] -> UserStatus
Present ((ClockTime, TimeDiff) -> LastSpoke
forall a. a -> Maybe a
Just (ClockTime
lastSpoke, TimeDiff
newMissed)) [Channel]
cs
-- Otherwise, we create a new record of the user.
updateJ Maybe ClockTime
_ [Channel]
cs UserStatus
_ = LastSpoke -> [Channel] -> UserStatus
Present LastSpoke
forall a. Maybe a
Nothing [Channel]
cs

-- | Update a user who is not present. We just convert absolute missing time
--   into relative time (i.e. start the "watch").
updateNP :: ClockTime -> Channel -> UserStatus -> UserStatus
updateNP :: ClockTime -> Channel -> UserStatus -> UserStatus
updateNP ClockTime
now Channel
_ (NotPresent ClockTime
ct StopWatch
missed [Channel]
c)
    = ClockTime -> StopWatch -> [Channel] -> UserStatus
NotPresent ClockTime
ct (ClockTime -> StopWatch -> StopWatch
stopWatch ClockTime
now StopWatch
missed) [Channel]
c
-- The user might be gone, thus it's meaningless when we last heard him speak.
updateNP ClockTime
now Channel
chan (WasPresent ClockTime
lastSeen StopWatch
missed LastSpoke
_ [Channel]
cs)
    | [Channel] -> Channel
forall a. [a] -> a
head [Channel]
cs Channel -> Channel -> Bool
forall a. Eq a => a -> a -> Bool
== Channel
chan = ClockTime -> StopWatch -> LastSpoke -> [Channel] -> UserStatus
WasPresent ClockTime
lastSeen (ClockTime -> StopWatch -> StopWatch
stopWatch ClockTime
now StopWatch
missed) LastSpoke
forall a. Maybe a
Nothing [Channel]
cs
updateNP ClockTime
_ Channel
_ UserStatus
status = UserStatus
status