module Infinity.Core (
 -- * Types
 Bot(..), Server(..),
 -- * Functions on 'Bot'
 newbot,             -- :: Bot
 joinchan, partchan, -- :: Channel -> Server -> Server
 joinserv, partserv, -- :: Server  -> Bot    -> Bot
 joinservs,          -- :: [(Server,Handle)] -> Bot -> Bot
 updateserv,         -- :: Server  -> Bot    -> Bot
 newadmin, deladmin, -- :: Nick    -> Server -> Server
 isadmin,            -- :: Nick    -> Server -> Bool
 servexists,         -- :: Server  -> Bot    -> Bool
 servnum             -- :: Bot     -> Int
) where
import Infinity.Util
import System.IO
import Data.Map (Map)
import Data.Set (Set)
import qualified Data.Set as S
import qualified Data.Map as M

-- | Describes a single IRC server
data Server = Server {
  address        :: String,     -- ^ what server to connect to
  port           :: Int,        -- ^ what port
  channels       :: Set String, -- ^ what channels to enter
  nickname       :: String,     -- ^ bot nick
  password       :: String,     -- ^ bot password, can be empty
  realname       :: String,     -- ^ bot's real name
  administrators :: Set String  -- ^ bot admins
} deriving (Eq,Show,Ord)

-- | This corresponds to the bot's
-- general state overall.
newtype Bot = Bot {
  servs   :: Map Server Handle   -- ^ Connected servers
} deriving (Eq,Show)

-- | Returns a new, empty bot
newbot :: Bot
newbot = Bot (M.empty)

-- | Joins a channel
joinchan :: Channel -> Server -> Server
joinchan chan serv = if chan `S.member` x then serv else newserv (S.insert chan x)
    where x         = channels serv
          newserv y = serv{channels=y}

-- | Joins multiple channels
joinchans :: [Channel] -> Server -> Server
joinchans chans serv = foldr joinchan serv chans

-- | Parts a channel
partchan :: Channel -> Server -> Server
partchan chan serv = if chan `S.notMember` x then serv else newserv (S.delete chan x)
    where x         = channels serv
          newserv y = serv{channels=y}

-- | Checks to see if a server is already connected
-- We have this because 'joinserv' is pure, and it needs
-- the handle to insert into the map. So first we check
-- to see if it's already a member or not, before running
-- joinserv.
servexists :: Bot -> Server -> Bool
servexists (Bot s) serv = serv `M.member` s

-- | Adds a new server
joinserv :: Handle -> Server -> Bot -> Bot
joinserv h serv x@(Bot s) = 
    if serv `M.member` s then x 
     else (Bot $ M.insert serv h s)

-- | Adds multiple new servers
joinservs :: [(Server,Handle)] -> Bot -> Bot
joinservs x b = foldr (\(s,h) b' -> joinserv h s b') b x

-- | Parts a server
partserv :: Server -> Bot -> Bot
partserv serv x@(Bot s) = 
    if serv `M.notMember` s then x
     else (Bot (M.delete serv s))

-- | Updates a server inside the bot, the server
-- must exist
updateserv :: Server -> Bot -> Bot
updateserv serv x@(Bot s) =
     let h = snd $ (M.toList s) !! (M.findIndex serv s) in
     joinserv h serv (partserv serv x)

-- | Adds a new bot administrator
newadmin :: Nick -> Server -> Server
newadmin n serv = if n `S.member` x then serv else newserv (S.insert n x)
    where x         = administrators serv
          newserv y = serv{administrators=y}

-- | Removes a bot administrator
deladmin :: Nick -> Server -> Server
deladmin n serv = if n `S.notMember` x then serv else newserv (S.delete n x)
    where x         = administrators serv
          newserv y = serv{administrators=y}

-- | Checks if a user is an administrator
isadmin :: Nick -> Server -> Bool
isadmin n serv = n `S.member` x
    where x = administrators serv

-- | Number of currently connected servers
servnum :: Bot -> Int
servnum (Bot x) = M.size x
