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 (Show, Read)

instance Binary UserStatus where
    put (Present sp ch)          = putWord8 0 >> put sp >> put ch
    put (NotPresent ct sw ch)    = putWord8 1 >> put ct >> put sw >> put ch
    put (WasPresent ct sw sp ch) = putWord8 2 >> put ct >> put sw >> put sp >> put ch
    put (NewNick n)              = putWord8 3 >> put n

    get = getWord8 >>= \h -> case h of
        0 -> Present    <$> get <*> get
        1 -> NotPresent <$> get <*> get <*> get
        2 -> WasPresent <$> get <*> get <*> get <*> get
        3 -> NewNick    <$> get
        _ -> error "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 _ c (Present ct cs) = Present ct $ nub (c ++ 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 now) cs (WasPresent lastSeen _ (Just (lastSpoke, missed)) channels)
    | head channels `elem` cs
    ---                 newMissed
    --- |---------------------------------------|
    --- |-------------------|                   |
    ---        missed    lastSeen              now
    = let newMissed = addToClockTime missed now `diffClockTimes` lastSeen
       in newMissed `seq` Present (Just (lastSpoke, newMissed)) cs
-- Otherwise, we create a new record of the user.
updateJ _ cs _ = Present Nothing 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 now _ (NotPresent ct missed c)
    = NotPresent ct (stopWatch now missed) c
-- The user might be gone, thus it's meaningless when we last heard him speak.
updateNP now chan (WasPresent lastSeen missed _ cs)
    | head cs == chan = WasPresent lastSeen (stopWatch now missed) Nothing cs
updateNP _ _ status = status