content: 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
  