{- This file is part of irc-fun-client.
 -
 - Written in 2015 by fr33domlover <fr33domlover@rel4tion.org>.
 -
 - ♡ Copying is an act of love. Please copy, reuse and share.
 -
 - The author(s) have dedicated all copyright and related and neighboring
 - rights to this software to the public domain worldwide. This software is
 - distributed without any warranty.
 -
 - You should have received a copy of the CC0 Public Domain Dedication along
 - with this software. If not, see
 - <http://creativecommons.org/publicdomain/zero/1.0/>.
 -}

-- | Using the tools provided here, you can track which users are members of
-- which channels. This functionality has a veriety of uses, e.g. displaying
-- user lists in client UI and logging/displaying quit messages in the correct
-- channel logs/buffers.
module Network.IRC.Fun.Client.NickTracker
    ( ChannelTracker (..)
    , NetworkTracker (..)
    , isMemberOf
    , isInChannel
    , presence
    , newChannel
    , newNetwork
    , addMember
    , addToChannel
    , addChannel
    , removeMember
    , removeFromChannel
    , removeFromNetwork
    , removeChannel
    , removeChannels
    )
where

import qualified Data.HashMap.Lazy as M
import qualified Data.HashSet as S
import Network.IRC.Fun.Messages.TypeAliases

newtype ChannelTracker = ChannelTracker (S.HashSet NickName)

newtype NetworkTracker = NetworkTracker (M.HashMap ChannelName ChannelTracker)

-- | Check whether a nickname is present in a channel.
isMemberOf :: NickName -> ChannelTracker -> Bool
nick `isMemberOf` (ChannelTracker nicks) = nick `S.member` nicks

-- | Check whether a nickname is present in a channel.
isInChannel :: NickName -> ChannelName -> NetworkTracker -> Bool
isInChannel nick chan (NetworkTracker cts) =
    case M.lookup chan cts of
        Nothing -> False
        Just ct -> nick `isMemberOf` ct

applySnd :: (b -> c) -> (a, b) -> (a, c)
applySnd f (x, y) = (x, f y)

-- | Check in which channels a nickname is present.
presence :: NickName -> NetworkTracker -> [ChannelName]
presence nick (NetworkTracker cts) =
    [ chan | (chan, True) <- map (applySnd (nick `isMemberOf`)) $ M.toList cts]

-- | Record a channel with the given present nicknames.
newChannel :: [NickName] -> ChannelTracker
newChannel nicks = ChannelTracker $ S.fromList nicks

-- | Create new tracker.
newNetwork :: NetworkTracker
newNetwork = NetworkTracker $ M.empty

-- | Record a nickname being present in a channel.
addMember :: NickName -> ChannelTracker -> ChannelTracker
addMember nick (ChannelTracker nicks) = ChannelTracker $ S.insert nick nicks

-- | Record a nickname being present in a channel.
addToChannel :: ChannelName -> NickName -> NetworkTracker -> NetworkTracker
addToChannel chan nick (NetworkTracker cts) = NetworkTracker $ f cts
    where
    f chans =
        case M.lookup chan chans of
            Nothing ->
                let ct = ChannelTracker $ S.singleton nick
                in  M.insert chan ct chans
            Just (ChannelTracker nicks) ->
                let ct = ChannelTracker $ S.insert nick nicks
                in  M.insert chan ct chans

-- | Record a channel with the given present nicknames.
addChannel :: ChannelName -> [NickName] -> NetworkTracker -> NetworkTracker
addChannel chan nicks (NetworkTracker cts) = NetworkTracker $ f cts
    where
    f = M.insert chan $ newChannel nicks

-- | Record a channel not having a given nickname anymore.
removeMember :: NickName -> ChannelTracker -> ChannelTracker
removeMember nick (ChannelTracker nicks) = ChannelTracker $ S.delete nick nicks

-- | Record a channel not having a given nickname anymore.
removeFromChannel :: ChannelName
                  -> NickName
                  -> NetworkTracker
                  -> NetworkTracker
removeFromChannel chan nick (NetworkTracker cts) = NetworkTracker $ f cts
    where
    f chans = M.adjust (removeMember nick) chan chans

-- | Record a nickname not being present in any channel anymore.
removeFromNetwork :: NickName -> NetworkTracker -> NetworkTracker
removeFromNetwork nick (NetworkTracker cts) = NetworkTracker $ f cts
    where
    f = M.map (removeMember nick)

-- | Remove a channel from the tracker.
removeChannel :: ChannelName -> NetworkTracker -> NetworkTracker
removeChannel chan (NetworkTracker cts) = NetworkTracker $ f cts
    where
    f = M.delete chan

-- | Remove channels from the tracker.
removeChannels :: [ChannelName] -> NetworkTracker -> NetworkTracker
removeChannels chans (NetworkTracker cts) = NetworkTracker $ f cts
    where
    f ts = ts `M.difference` M.fromList (zip chans (repeat ()))