package com.esri.geoevent.solutions.transport.irc.jerklib;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.esri.geoevent.solutions.transport.irc.jerklib.events.JoinCompleteEvent;
import com.esri.geoevent.solutions.transport.irc.jerklib.events.TopicEvent;
import com.esri.geoevent.solutions.transport.irc.jerklib.events.modes.ModeAdjustment;
import com.esri.geoevent.solutions.transport.irc.jerklib.events.modes.ModeAdjustment.Action;
/**
* A Class to represent a <b>joined</b> IRC channel. This class has methods to
* interact with IRC Channels like say() , part() , getChannelModes() etc.
*
* You will never need to create an instance of this class manually. Instead it
* will be created for you and stored in the Session when you successfully join
* a channel.
*
* @see Session
* @see Session#getChannel(String)
* @see Session#getChannels()
* @see JoinCompleteEvent
*
* @author mohadib
*
*/
public class Channel
{
/* channel name */
private String name;
private Session session;
private Map<String, List<ModeAdjustment>> userMap;
private List<ModeAdjustment> channelModes = new ArrayList<ModeAdjustment>();
private TopicEvent topicEvent;
/**
* This should only be used internally and for testing
*
* @param name - Name of Channel
* @param session - Session Channel belongs to
*/
public Channel(String name, Session session)
{
/* create a map that will match exact and key to lowercase */
userMap = new HashMap<String, List<ModeAdjustment>>()
{
public List<ModeAdjustment> get(Object key)
{
List<ModeAdjustment> rList = super.get(key);
if(key != null && rList == null)
{
rList = super.get(key.toString().toLowerCase());
}
return rList;
}
public List<ModeAdjustment> remove(Object key)
{
List<ModeAdjustment> rList = super.remove(key);
if(key != null && rList == null)
{
rList = super.remove(key.toString().toLowerCase());
}
return rList;
}
public boolean containsKey(Object key)
{
boolean b = super.containsKey(key);
if(!b) b = super.containsKey(key.toString().toLowerCase());
return b;
}
};
this.name = name;
this.session = session;
}
/**
* Updates Channel's modes.
*
* Only tracks channel modes that apply to users in the channel if the mode is
* in the nick prefix map received in numeric 005. If no numeric is passed
* o,v,h are used by default.
*
* So basically modes that do not change the apperance of a nick with a prefix
* are not tracked if the mode applies to a user. Example: q and b are not
* tracked.
*
* If the mode does not apply to a user in the channel , the mode will always
* be tracked. Example: i is tracked
*
* @param modes -
* list of ModeAdjustments
*/
void updateModes(List<ModeAdjustment> modes)
{
ServerInformation info = session.getServerInformation();
List<String> nickModes = new ArrayList<String>(info.getNickPrefixMap().values());
for (ModeAdjustment mode : modes)
{
if (nickModes.contains(String.valueOf(mode.getMode())) && userMap.containsKey(mode.getArgument()))
{
updateMode(mode, userMap.get(mode.getArgument()));
}
/* filter out channel modes that apply to users that are not in prefix map */
/* like +b - this behviour might not be desired , time will tell */
else if (mode.getMode() != 'q' && mode.getMode() != 'b')
{
updateMode(mode, channelModes);
}
}
}
/**
* If Action is MINUS and the same mode exists with a PLUS Action then just
* remove the PLUS mode ModeAdjustment from the collection.
*
* If Action is MINUS and the same mode with PLUS does not exist then add the
* MINUS mode to the ModeAdjustment collection
*
* if Action is PLUS and the same mode exists with a MINUS Action then remove
* MINUS mode and add PLUS mode
*
* If Action is PLUS and the same mode with MINUS does not exist then just add
* PLUS mode to collection
*
* @param mode
*/
private void updateMode(ModeAdjustment mode, List<ModeAdjustment> modes)
{
int index = indexOfMode(mode.getMode(), modes);
if (mode.getAction() == Action.MINUS)
{
if (index != -1)
{
ModeAdjustment ma = modes.remove(index);
if (ma.getAction() == Action.MINUS) modes.add(ma);
}
else
{
modes.add(mode);
}
}
else
{
if (index != -1) modes.remove(index);
modes.add(mode);
}
}
/**
* Finds index of a mode in a List of ModeAdjustments
*
* @param mode
* mode to find
* @param modes
* list to search
* @return index or -1 if not found
*/
private int indexOfMode(char mode, List<ModeAdjustment> modes)
{
for (int i = 0; i < modes.size(); i++)
{
ModeAdjustment ma = modes.get(i);
if (ma.getMode() == mode) return i;
}
return -1;
}
/**
* Get a list of user's channel modes
* Returns an empty list if the nick does not exist.
*
* @param nick
* @return list of ModeAdjustments for user
*/
public List<ModeAdjustment> getUsersModes(String nick)
{
if (userMap.containsKey(nick))
{
return new ArrayList<ModeAdjustment>(userMap.get(nick));
}
else
{
return new ArrayList<ModeAdjustment>();
}
}
/**
* Gets a list of user in channel with a given mode set.
* A user will only match if they have the exact mode set.
* Non existance of +(mode) does not imply that the user is -(mode)
*
* So searching for -v would almost always return an empty list.
*
* @param action
* @param mode
* @return List of nicks with mode/action set
*/
public List<String> getNicksForMode(Action action , char mode)
{
List<String> nicks = new ArrayList<String>();
for (String nick : getNicks())
{
List<ModeAdjustment> modes = userMap.get(nick);
for (ModeAdjustment ma : modes)
{
if (ma.getMode() == mode && ma.getAction() == action) nicks.add(nick);
}
}
return nicks;
}
/**
* Returns a list of modes that apply to the channel but dont apply
* to users in the channel. I.E. +o is not returned as that applies
* to users in the channel not just the channel.
*
* @return List of ModeAdjustments for the Channel
*/
public List<ModeAdjustment> getChannelModes()
{
return new ArrayList<ModeAdjustment>(channelModes);
}
/**
* Sets a mode in the Channel is you have the permissions to do so.
*
* example: +vv00 foo bar baz bob
* example: -v+i foo
*
* @param mode to set.
*/
public void mode(String mode)
{
session.mode(name, mode);
}
/**
* Gets the topic for the channel or an empty string is the topic is not set.
*
* @return topic for channel
*/
public String getTopic()
{
return topicEvent != null ? topicEvent.getTopic() : "";
}
/**
* Gets the nick of who set the topic or an empty string if the topic is not set.
*
* @return nick of topic setter
*/
public String getTopicSetter()
{
return topicEvent != null ? topicEvent.getSetBy() : "";
}
/**
* Returns the Date the topic was set or null if the topic is unset.
*
* @return date topic was set or null if not set
*/
public Date getTopicSetTime()
{
return topicEvent == null? null:topicEvent.getSetWhen();
}
/**
* Sets the topic of the Channel is you have the permissions to do so.
*
* @param topic to use.
*/
public void setTopic(String topic)
{
write(new WriteRequest("TOPIC " + name + " :" + topic, session));
}
/**
* This method should only be used internally
*
* @param topicEvent
*/
public void setTopicEvent(TopicEvent topicEvent)
{
this.topicEvent = topicEvent;
}
/**
* Gets the Channel name.
*
* @return name of Channel
*/
public String getName()
{
return name;
}
/**
* Speak in the Channel.
*
* @param message - what to say
*/
public void say(String message)
{
session.sayChannel(this, message);
}
/**
* Send a notice in the Channel
*
* @param message - notice messgae
*/
public void notice(String message)
{
session.notice(getName(), message);
}
/**
* This method is for internal use only
*
* @param nick to add
*/
public void addNick(String nick)
{
if (!userMap.containsKey(nick))
{
ServerInformation info = session.getServerInformation();
Map<String, String> nickPrefixMap = info.getNickPrefixMap();
List<ModeAdjustment> modes = new ArrayList<ModeAdjustment>();
for (String prefix : nickPrefixMap.keySet())
{
if (nick.startsWith(prefix))
{
modes.add(new ModeAdjustment(Action.PLUS, nickPrefixMap.get(prefix).charAt(0), ""));
}
}
// TODO can a nick come in as voiced and oped? +@dib ?
// if so substring nick with modes.size();
if (!modes.isEmpty())
{
nick = nick.substring(1);
}
userMap.put(nick, modes);
}
}
/**
* removes a nick from the Channel nick list
*
* @param nick
* @return true if nick was removed else false
*/
boolean removeNick(String nick)
{
return userMap.remove(nick) != null;
}
/**
* Called to update nick list when nick change happens
*
* @param oldNick
* @param newNick
*/
void nickChanged(String oldNick, String newNick)
{
List<ModeAdjustment> modes = userMap.remove(oldNick);
userMap.put(newNick, modes);
}
/**
* Gets a list of nicks for Channel.
* The list returned has a case insenstive
* indexOf() and contains()
*
* @return List of nicks
*/
public List<String> getNicks()
{
return new ArrayList<String>(userMap.keySet())
{
public int indexOf(Object o)
{
if (o != null)
{
for (int i = 0; i < size(); i++)
{
if (get(i).equalsIgnoreCase(o.toString())) { return i; }
}
}
return -1;
}
};
}
/**
* Part the channel
*
* @param partMsg
*/
public void part(String partMsg)
{
if(partMsg == null || partMsg.length() == 0) partMsg = "Leaving";
write(new WriteRequest("PART " + getName() + " :" + partMsg, session));
}
/**
* Send an action
*
* @param text action text
*/
public void action(String text)
{
write(new WriteRequest("\001ACTION " + text + "\001", this, session));
}
/**
*Send a names query to the server
*/
public void names()
{
write(new WriteRequest("NAMES " + getName(), this, session));
}
/**
* Devoice a user
* @param userName
*/
public void deVoice(String userName)
{
write(new WriteRequest("MODE " + getName() + " -v " + userName, session));
}
/**
* Voice a user
* @param userName
*/
public void voice(String userName)
{
write(new WriteRequest("MODE " + getName() + " +v " + userName, session));
}
/**
* Op a user
* @param userName
*/
public void op(String userName)
{
write(new WriteRequest("MODE " + getName() + " +o " + userName, session));
}
/**
* DeOp a user
* @param userName
*/
public void deop(String userName)
{
write(new WriteRequest("MODE " + getName() + " -o " + userName, session));
}
/**
* Kick a user
* @param userName
* @param reason
*/
public void kick(String userName, String reason)
{
if(reason == null || reason.length() == 0) reason = session.getNick();
write(new WriteRequest("KICK " + getName() + " " + userName + " :" + reason, session));
}
/**
* Helper method for writing
* @param req
*/
private void write(WriteRequest req)
{
session.getConnection().addWriteRequest(req);
}
/**
* Return the Session this Channel belongs to
*/
public Session getSession()
{
return session;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object o)
{
if (this == o) { return true; }
if (!(o instanceof Channel)) { return false; }
Channel channel = (Channel) o;
if (!session.getConnectedHostName().equals(channel.getSession().getConnectedHostName())) { return false; }
if (!name.equals(channel.getName())) { return false; }
return true;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
public int hashCode()
{
int result;
result = (name != null ? name.hashCode() : 0);
result = 31 * result + session.getConnectedHostName().hashCode();
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString()
{
return "[Channel: name=" + name + "]";
}
}