context: /*
   * $Id$
   * $URL$
   * $Rev$
   * $Author$
   * $Date$
   *
   * SmartIrc4net - the IRC library for .NET/C# <http://smartirc4net.sf.net>
   *
   * Copyright (c) 2003-2010, 2012-2014 Mirco Bauer <meebey@meebey.net>
   * Copyright (c) 2008-2009 Thomas Bruderer <apophis@apophis.ch>
   *
   * Full LGPL License: <http://www.gnu.org/licenses/lgpl.txt>
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */
  
  using System;
  using System.Linq;
  using System.Text;
  using System.Text.RegularExpressions;
  using System.Threading;
  using System.Collections;
  using System.Collections.Generic;
  using System.Collections.Specialized;
  
  namespace Meebey.SmartIrc4net
  {
      /// <summary>
      /// This layer is an event driven high-level API with all features you could need for IRC programming.
      /// </summary>
      /// <threadsafety static="true" instance="true" />
      public class IrcClient : IrcCommands
      {
          private string           _Nickname                = string.Empty;
          private string[]         _NicknameList;
          private int              _CurrentNickname;
          private string           _Realname                = string.Empty;
          private string           _Usermode                = string.Empty;
          private int              _IUsermode;
          private string           _Username                = string.Empty;
          private string           _Password                = string.Empty;
          private bool             _IsAway;
          private string           _CtcpVersion;
          private bool             _ActiveChannelSyncing;
          private bool             _PassiveChannelSyncing;
          private bool             _AutoJoinOnInvite;
          private bool             _AutoRejoin;
          private Dictionary<string, string> _AutoRejoinChannels = new Dictionary<string, string>();
          private bool             _AutoRejoinOnKick;
          private bool             _AutoRelogin;
          private bool             _AutoNickHandling        = true;
          private bool             _SupportNonRfc;
          private bool             _SupportNonRfcLocked;
          private List<string> _Motd                    = new List<string>();
          private bool             _MotdReceived;
          private Array            _ReplyCodes              = Enum.GetValues(typeof(ReplyCode));
          private List<string> _JoinedChannels          = new List<string>();
          private Hashtable        _Channels                = Hashtable.Synchronized(CollectionsUtil.CreateCaseInsensitiveHashtable());
          private Hashtable        _IrcUsers                = Hashtable.Synchronized(CollectionsUtil.CreateCaseInsensitiveHashtable());
          private List<ChannelInfo> _ChannelList;
          private Object            _ChannelListSyncRoot = new Object();
          private AutoResetEvent    _ChannelListReceivedEvent;
          private List<WhoInfo>    _WhoList;
          private Object           _WhoListSyncRoot = new Object();
          private AutoResetEvent   _WhoListReceivedEvent;
          private List<BanInfo>    _BanList;
          private Object           _BanListSyncRoot = new Object();
          private AutoResetEvent   _BanListReceivedEvent;
          private List<BanInfo>    _BanExceptList;
          private Object           _BanExceptListSyncRoot = new Object();
          private AutoResetEvent   _BanExceptListReceivedEvent;
          private List<BanInfo>    _InviteExceptList;
          private Object           _InviteExceptListSyncRoot = new Object();
          private AutoResetEvent   _InviteExceptListReceivedEvent;
          private ServerProperties _ServerProperties = new ServerProperties();
          private static Regex     _ReplyCodeRegex          = new Regex("^:?[^ ]+? ([0-9]{3}) .+$", RegexOptions.Compiled);
          private static Regex     _PingRegex               = new Regex("^PING :.*", RegexOptions.Compiled);
          private static Regex     _ErrorRegex              = new Regex("^ERROR :.*", RegexOptions.Compiled);
          private static Regex     _ActionRegex             = new Regex("^:?.*? PRIVMSG (.).* :"+"\x1"+"ACTION .*"+"\x1"+"$", RegexOptions.Compiled);
          private static Regex     _CtcpRequestRegex        = new Regex("^:?.*? PRIVMSG .* :"+"\x1"+".*"+"\x1"+"$", RegexOptions.Compiled);
          private static Regex     _MessageRegex            = new Regex("^:?.*? PRIVMSG (.).* :.*$", RegexOptions.Compiled);
          private static Regex     _CtcpReplyRegex          = new Regex("^:?.*? NOTICE .* :"+"\x1"+".*"+"\x1"+"$", RegexOptions.Compiled);
          private static Regex     _NoticeRegex             = new Regex("^:?.*? NOTICE (.).* :.*$", RegexOptions.Compiled);
          private static Regex     _InviteRegex             = new Regex("^:?.*? INVITE .* .*$", RegexOptions.Compiled);
          private static Regex     _JoinRegex               = new Regex("^:?.*? JOIN .*$", RegexOptions.Compiled);
          private static Regex     _TopicRegex              = new Regex("^:?.*? TOPIC .* :.*$", RegexOptions.Compiled);
          private static Regex     _NickRegex               = new Regex("^:?.*? NICK .*$", RegexOptions.Compiled);
          private static Regex     _KickRegex               = new Regex("^:?.*? KICK .* .*$", RegexOptions.Compiled);
          private static Regex     _PartRegex               = new Regex("^:?.*? PART .*$", RegexOptions.Compiled);
          private static Regex     _ModeRegex               = new Regex("^:?.*? MODE (.*) .*$", RegexOptions.Compiled);
          private static Regex     _QuitRegex               = new Regex("^:?.*? QUIT :.*$", RegexOptions.Compiled);
          private static Regex     _BounceMessageRegex      = new Regex("^Try server (.+), port ([0-9]+)$", RegexOptions.Compiled);
  
          ChannelModeMap ChannelModeMap { get; set; }
  
          public event EventHandler               OnRegistered;
          public event PingEventHandler           OnPing;
          public event PongEventHandler           OnPong;
          public event IrcEventHandler            OnRawMessage;
          public event ErrorEventHandler          OnError;
          public event IrcEventHandler            OnErrorMessage;
          public event JoinEventHandler           OnJoin;
          public event NamesEventHandler          OnNames;
          public event ListEventHandler           OnList;
          public event PartEventHandler           OnPart;
          public event QuitEventHandler           OnQuit;
          public event KickEventHandler           OnKick;
          public event AwayEventHandler           OnAway;
          public event IrcEventHandler            OnUnAway;
          public event IrcEventHandler            OnNowAway;
          public event InviteEventHandler         OnInvite;
          public event BanEventHandler            OnBan;
          public event UnbanEventHandler          OnUnban;
          public event BanEventHandler            OnBanException;
          public event UnbanEventHandler          OnUnBanException;
          public event BanEventHandler            OnInviteException;
          public event UnbanEventHandler          OnUnInviteException;
          public event OwnerEventHandler          OnOwner;
          public event DeownerEventHandler        OnDeowner;
          public event ChannelAdminEventHandler   OnChannelAdmin;
          public event DeChannelAdminEventHandler OnDeChannelAdmin;
          public event OpEventHandler             OnOp;
          public event DeopEventHandler           OnDeop;
          public event HalfopEventHandler         OnHalfop;
          public event DehalfopEventHandler       OnDehalfop;
          public event VoiceEventHandler          OnVoice;
          public event DevoiceEventHandler        OnDevoice;
          public event WhoEventHandler            OnWho;
          public event MotdEventHandler           OnMotd;
          public event TopicEventHandler          OnTopic;
          public event TopicChangeEventHandler    OnTopicChange;
          public event NickChangeEventHandler     OnNickChange;
          public event IrcEventHandler            OnModeChange;
          public event IrcEventHandler            OnUserModeChange;
          public event EventHandler<ChannelModeChangeEventArgs> OnChannelModeChange;
          public event IrcEventHandler            OnChannelMessage;
          public event ActionEventHandler         OnChannelAction;
          public event IrcEventHandler            OnChannelNotice;
          public event IrcEventHandler            OnChannelActiveSynced;
          public event IrcEventHandler            OnChannelPassiveSynced;
          public event IrcEventHandler            OnQueryMessage;
          public event ActionEventHandler         OnQueryAction;
          public event IrcEventHandler            OnQueryNotice;
          public event CtcpEventHandler           OnCtcpRequest;
          public event CtcpEventHandler           OnCtcpReply;
          public event BounceEventHandler         OnBounce;
  
          /// <summary>
          /// Enables/disables the active channel sync feature.
          /// Default: false
          /// </summary>
          public bool ActiveChannelSyncing {
              get {
                  return _ActiveChannelSyncing;
              }
              set {
  #if LOG4NET
                  if (value) {
                      Logger.ChannelSyncing.Info("Active channel syncing enabled");
                  } else {
                      Logger.ChannelSyncing.Info("Active channel syncing disabled");
                  }
  #endif
                  _ActiveChannelSyncing = value;
              }
          }
  
          /// <summary>
          /// Enables/disables the passive channel sync feature. Not implemented yet!
          /// </summary>
          public bool PassiveChannelSyncing {
              get {
                  return _PassiveChannelSyncing;
              }
              /*
              set {
  #if LOG4NET
                  if (value) {
                      Logger.ChannelSyncing.Info("Passive channel syncing enabled");
                  } else {
                      Logger.ChannelSyncing.Info("Passive channel syncing disabled");
                  }
  #endif
                  _PassiveChannelSyncing = value;
              }
              */
          }
          
          /// <summary>
          /// Sets the ctcp version that should be replied on ctcp version request.
          /// </summary>
          public string CtcpVersion {
              get {
                  return _CtcpVersion;
              }
              set {
                  _CtcpVersion = value;
              }
          }
  
          /// <summary>
          /// Enables/disables auto joining of channels when invited.
          /// Default: false
          /// </summary>
          public bool AutoJoinOnInvite {
              get {
                  return _AutoJoinOnInvite;
              }
              set {
  #if LOG4NET
                  if (value) {
                      Logger.ChannelSyncing.Info("AutoJoinOnInvite enabled");
                  } else {
                      Logger.ChannelSyncing.Info("AutoJoinOnInvite disabled");
                  }
  #endif
                  _AutoJoinOnInvite = value;
              }
          }
  
          /// <summary>
          /// Enables/disables automatic rejoining of channels when a connection to the server is lost.
          /// Default: false
          /// </summary>
          public bool AutoRejoin {
              get {
                  return _AutoRejoin;
              }
              set {
  #if LOG4NET
                  if (value) {
                      Logger.ChannelSyncing.Info("AutoRejoin enabled");
                  } else {
                      Logger.ChannelSyncing.Info("AutoRejoin disabled");
                  }
  #endif
                  _AutoRejoin = value;
              }
          }
          
          /// <summary>
          /// Enables/disables auto rejoining of channels when kicked.
          /// Default: false
          /// </summary>
          public bool AutoRejoinOnKick {
              get {
                  return _AutoRejoinOnKick;
              }
              set {
  #if LOG4NET
                  if (value) {
                      Logger.ChannelSyncing.Info("AutoRejoinOnKick enabled");
                  } else {
                      Logger.ChannelSyncing.Info("AutoRejoinOnKick disabled");
                  }
  #endif
                  _AutoRejoinOnKick = value;
              }
          }
  
          /// <summary>
          /// Enables/disables auto relogin to the server after a reconnect.
          /// Default: false
          /// </summary>
          public bool AutoRelogin {
              get {
                  return _AutoRelogin;
              }
              set {
  #if LOG4NET
                  if (value) {
                      Logger.ChannelSyncing.Info("AutoRelogin enabled");
                  } else {
                      Logger.ChannelSyncing.Info("AutoRelogin disabled");
                  }
  #endif
                  _AutoRelogin = value;
              }
          }
  
          /// <summary>
          /// Enables/disables auto nick handling on nick collisions
          /// Default: true
          /// </summary>
          public bool AutoNickHandling {
              get {
                  return _AutoNickHandling;
              }
              set {
  #if LOG4NET
                  if (value) {
                      Logger.ChannelSyncing.Info("AutoNickHandling enabled");
                  } else {
                      Logger.ChannelSyncing.Info("AutoNickHandling disabled");
                  }
  #endif
                  _AutoNickHandling = value;
              }
          }
          
          /// <summary>
          /// Enables/disables support for non rfc features.
          /// Default: false
          /// </summary>
          public bool SupportNonRfc {
              get {
                  return _SupportNonRfc;
              }
              set {
                  if (_SupportNonRfcLocked) {
                      return;
                  }
  #if LOG4NET
                  
                  if (value) {
                      Logger.ChannelSyncing.Info("SupportNonRfc enabled");
                  } else {
                      Logger.ChannelSyncing.Info("SupportNonRfc disabled");
                  }
  #endif
                  _SupportNonRfc = value;
              }
          }
  
          /// <summary>
          /// Gets the nickname of us.
          /// </summary>
          public string Nickname {
              get {
                  return _Nickname;
              }
          }
          
          /// <summary>
          /// Gets the list of nicknames of us.
          /// </summary>
          public string[] NicknameList {
              get {
                  return _NicknameList;
              }
          }
        
          /// <summary>
          /// Gets the supposed real name of us.
          /// </summary>
          public string Realname {
              get {
                  return _Realname;
              }
          }
  
          /// <summary>
          /// Gets the username for the server.
          /// </summary>
          /// <remarks>
          /// System username is set by default 
          /// </remarks>
          public string Username {
              get {
                  return _Username;
              }
          }
  
          /// <summary>
          /// Gets the alphanumeric mode mask of us.
          /// </summary>
          public string Usermode {
              get {
                  return _Usermode;
              }
          }
  
          /// <summary>
          /// Gets the numeric mode mask of us.
          /// </summary>
          public int IUsermode {
              get {
                  return _IUsermode;
              }
          }
  
          /// <summary>
          /// Returns if we are away on this connection
          /// </summary>
          public bool IsAway {
              get {
                  return _IsAway;
              }
          }
          
          /// <summary>
          /// Gets the password for the server.
          /// </summary>
          public string Password {
              get {
                  return _Password;
              }
          }
  
          /// <summary>
          /// Gets the list of channels we are joined.
          /// </summary>
          public List<string> JoinedChannels {
              get {
                  return _JoinedChannels;
              }
          }
          
          /// <summary>
          /// Gets the server message of the day.
          /// </summary>
          public List<string> Motd {
              get {
                  return _Motd;
              }
          }
  
          public object BanListSyncRoot {
              get {
                  return _BanListSyncRoot;
              }
          }
  
          /// <summary>
          /// Gets the special functionality supported by this server.
          /// </summary>
          public ServerProperties ServerProperties {
              get {
                  return _ServerProperties;
              }
          }
          
          /// <summary>
          /// This class manages the connection server and provides access to all the objects needed to send and receive messages.
          /// </summary>
          public IrcClient()
          {
  #if LOG4NET
              Logger.Main.Debug("IrcClient created");
  #endif
              OnReadLine        += new ReadLineEventHandler(_Worker);
              OnDisconnected    += new EventHandler(_OnDisconnected);
              OnConnectionError += new EventHandler(_OnConnectionError);
  
              ChannelModeMap = new ChannelModeMap();
          }
  
  #if LOG4NET
          ~IrcClient()
          {
              Logger.Main.Debug("IrcClient destroyed");
          }
  #endif
  
          /// <summary>
          /// Connection parameters required to establish an server connection.
          /// </summary>
          /// <param name="addresslist">The list of server hostnames.</param>
          /// <param name="port">The TCP port the server listens on.</param>
          public new void Connect(string address, int port)
          {
              _SupportNonRfcLocked = true;
              ChannelModeMap = new ChannelModeMap();
              base.Connect(address, port);
          }
          
          /// <overloads>
          /// Reconnects to the current server.
          /// </overloads>
          /// <param name="login">If the login data should be sent, after successful connect.</param>
          /// <param name="channels">If the channels should be rejoined, after successful connect.</param>
          public void Reconnect(bool login, bool channels)
          {
              if (channels) {
                  _StoreChannelsToRejoin();
              }
              base.Reconnect();
              if (login) {
                  //reset the nick to the original nicklist
                  _CurrentNickname = 0;
                  // FIXME: honor _Nickname (last used nickname)
                  Login(_NicknameList, Realname, IUsermode, Username, Password);
              }
              if (channels) {
                  _RejoinChannels();
              }
          }
  
          /// <param name="login">If the login data should be sent, after successful connect.</param>
          public void Reconnect(bool login)
          {
              Reconnect(login, AutoRejoin);
          }
  
          /// <summary>
          /// Login parameters required identify with server connection
          /// </summary>
          /// <remark>Login is used at the beginning of connection to specify the username, hostname and realname of a new user.</remark>
          /// <param name="nicklist">The users list of 'nick' names which may NOT contain spaces</param>
          /// <param name="realname">The users 'real' name which may contain space characters</param>
          /// <param name="usermode">A numeric mode parameter.  
          ///   <remark>
          ///     Set to 0 to recieve wallops and be invisible. 
          ///     Set to 4 to be invisible and not receive wallops.
          ///   </remark>
          /// </param>
          /// <param name="username">The user's machine logon name</param>        
          /// <param name="password">The optional password can and MUST be set before any attempt to register
          ///  the connection is made.</param>        
          public void Login(string[] nicklist, string realname, int usermode, string username, string password)
          {
  #if LOG4NET
              Logger.Connection.Info("logging in");
  #endif
              _NicknameList = (string[])nicklist.Clone();
              // here we set the nickname which we will try first
              _Nickname = _NicknameList[0].Replace(" ", "");
              _Realname = realname;
              _IUsermode = usermode;
  
              if (username != null && username.Length > 0) {
                  _Username = username.Replace(" ", "");
              } else {
                  _Username = $"SmartUser{new Random().Next(1, 99999)}";
              }
  
              if (password != null && password.Length > 0) {
                  _Password = password;
                  RfcPass(Password, Priority.Critical);
              }
  
              RfcNick(Nickname, Priority.Critical);
              RfcUser(Username, IUsermode, Realname, Priority.Critical);
          }
  
          /// <summary>
          /// Login parameters required identify with server connection
          /// </summary>
          /// <remark>Login is used at the beginning of connection to specify the username, hostname and realname of a new user.</remark>
          /// <param name="nicklist">The users list of 'nick' names which may NOT contain spaces</param>
          /// <param name="realname">The users 'real' name which may contain space characters</param>
          /// <param name="usermode">A numeric mode parameter.  
          /// Set to 0 to recieve wallops and be invisible. 
          /// Set to 4 to be invisible and not receive wallops.</param>        
          /// <param name="username">The user's machine logon name</param>        
          public void Login(string[] nicklist, string realname, int usermode, string username)
          {
              Login(nicklist, realname, usermode, username, "");
          }
          
          /// <summary>
          /// Login parameters required identify with server connection
          /// </summary>
          /// <remark>Login is used at the beginning of connection to specify the username, hostname and realname of a new user.</remark>
          /// <param name="nicklist">The users list of 'nick' names which may NOT contain spaces</param>
          /// <param name="realname">The users 'real' name which may contain space characters</param>
          /// <param name="usermode">A numeric mode parameter.  
          /// Set to 0 to recieve wallops and be invisible. 
          /// Set to 4 to be invisible and not receive wallops.</param>        
          public void Login(string[] nicklist, string realname, int usermode)
          {
              Login(nicklist, realname, usermode, "", "");
          }
          
          /// <summary>
          /// Login parameters required identify with server connection
          /// </summary>
          /// <remark>Login is used at the beginning of connection to specify the username, hostname and realname of a new user.</remark>
          /// <param name="nicklist">The users list of 'nick' names which may NOT contain spaces</param>
          /// <param name="realname">The users 'real' name which may contain space characters</param> 
          public void Login(string[] nicklist, string realname)
          {
              Login(nicklist, realname, 0, "", "");
          }
          
          /// <summary>
          /// Login parameters required identify with server connection
          /// </summary>
          /// <remark>Login is used at the beginning of connection to specify the username, hostname and realname of a new user.</remark>
          /// <param name="nick">The users 'nick' name which may NOT contain spaces</param>
          /// <param name="realname">The users 'real' name which may contain space characters</param>
          /// <param name="usermode">A numeric mode parameter.  
          /// Set to 0 to recieve wallops and be invisible. 
          /// Set to 4 to be invisible and not receive wallops.</param>        
          /// <param name="username">The user's machine logon name</param>        
          /// <param name="password">The optional password can and MUST be set before any attempt to register
          ///  the connection is made.</param>   
          public void Login(string nick, string realname, int usermode, string username, string password)
          {
              Login(new string[] {nick, nick+"_", nick+"__"}, realname, usermode, username, password);
          }
  
          /// <summary>
          /// Login parameters required identify with server connection
          /// </summary>
          /// <remark>Login is used at the beginning of connection to specify the username, hostname and realname of a new user.</remark>
          /// <param name="nick">The users 'nick' name which may NOT contain spaces</param>
          /// <param name="realname">The users 'real' name which may contain space characters</param>
          /// <param name="usermode">A numeric mode parameter.  
          /// Set to 0 to recieve wallops and be invisible. 
          /// Set to 4 to be invisible and not receive wallops.</param>        
          /// <param name="username">The user's machine logon name</param>        
          public void Login(string nick, string realname, int usermode, string username)
          {
              Login(new string[] {nick, nick+"_", nick+"__"}, realname, usermode, username, "");
          }
  
          /// <summary>
          /// Login parameters required identify with server connection
          /// </summary>
          /// <remark>Login is used at the beginning of connection to specify the username, hostname and realname of a new user.</remark>
          /// <param name="nick">The users 'nick' name which may NOT contain spaces</param>
          /// <param name="realname">The users 'real' name which may contain space characters</param>
          /// <param name="usermode">A numeric mode parameter.  
          /// Set to 0 to recieve wallops and be invisible. 
          /// Set to 4 to be invisible and not receive wallops.</param>        
          public void Login(string nick, string realname, int usermode)
          {
              Login(new string[] {nick, nick+"_", nick+"__"}, realname, usermode, "", "");
          }
  
          /// <summary>
          /// Login parameters required identify with server connection
          /// </summary>
          /// <remark>Login is used at the beginning of connection to specify the username, hostname and realname of a new user.</remark>
          /// <param name="nick">The users 'nick' name which may NOT contain spaces</param>
          /// <param name="realname">The users 'real' name which may contain space characters</param>
          public void Login(string nick, string realname)
          {
              Login(new string[] {nick, nick+"_", nick+"__"}, realname, 0, "", "");
          }
          
          /// <summary>
          /// Determine if a specifier nickname is you
          /// </summary>
          /// <param name="nickname">The users 'nick' name which may NOT contain spaces</param>
          /// <returns>True if nickname belongs to you</returns>
          public bool IsMe(string nickname)
          {
              return String.Compare(Nickname, nickname, StringComparison.OrdinalIgnoreCase) == 0;
          }
  
          /// <summary>
          /// Determines if your nickname can be found in a specified channel
          /// </summary>
          /// <param name="channelname">The name of the channel you wish to query</param>
          /// <returns>True if you are found in channel</returns>
          public bool IsJoined(string channelname)
          {
              return IsJoined(channelname, Nickname);
          }
  
          /// <summary>
          /// Determine if a specified nickname can be found in a specified channel
          /// </summary>
          /// <param name="channelname">The name of the channel you wish to query</param>
          /// <param name="nickname">The users 'nick' name which may NOT contain spaces</param>
          /// <returns>True if nickname is found in channel</returns>
          public bool IsJoined(string channelname, string nickname)
          {
              if (channelname == null) {
                  throw new System.ArgumentNullException("channelname");
              }
  
              if (nickname == null) {
                  throw new System.ArgumentNullException("nickname");
              }
              
              Channel channel = GetChannel(channelname);
              if (channel != null &&
                  channel.UnsafeUsers != null &&
                  channel.UnsafeUsers.ContainsKey(nickname)) {
                  return true;
              }
              
              return false;
          }
  
          /// <summary>
          /// Returns user information
          /// </summary>
          /// <param name="nickname">The users 'nick' name which may NOT contain spaces</param>
          /// <returns>IrcUser object of requested nickname</returns>
          public IrcUser GetIrcUser(string nickname)
          {
              if (nickname == null) {
                  throw new System.ArgumentNullException("nickname");
              }
  
              return (IrcUser)_IrcUsers[nickname];
          }
  
          /// <summary>
          /// Returns extended user information including channel information
          /// </summary>
          /// <param name="channelname">The name of the channel you wish to query</param>
          /// <param name="nickname">The users 'nick' name which may NOT contain spaces</param>
          /// <returns>ChannelUser object of requested channelname/nickname</returns>
          public ChannelUser GetChannelUser(string channelname, string nickname)
          {
              if (channelname == null) {
                  throw new System.ArgumentNullException("channel");
              }
  
              if (nickname == null) {
                  throw new System.ArgumentNullException("nickname");
              }
              
              Channel channel = GetChannel(channelname);
              if (channel != null) {
                  return (ChannelUser)channel.UnsafeUsers[nickname];
              } else {
                  return null;
              } 
          }
  
          /// <summary>
          /// 
          /// </summary>
          /// <param name="channelname">The name of the channel you wish to query</param>
          /// <returns>Channel object of requested channel</returns>
          public Channel GetChannel(string channelname)
          {
              if (channelname == null) {
                  throw new System.ArgumentNullException("channelname");
              }
              
              return (Channel)_Channels[channelname];
          }
  
          /// <summary>
          /// Gets a list of all joined channels on server
          /// </summary>
          /// <returns>String array of all joined channel names</returns>
          public string[] GetChannels()
          {
              string[] channels = new string[_Channels.Values.Count];
              int i = 0;
              foreach (Channel channel in _Channels.Values) {
                  channels[i++] = channel.Name;
              }
  
              return channels;
          }
          
          /// <summary>
          /// Fetches a fresh list of all available channels that match the passed mask
          /// </summary>
          /// <returns>List of ListInfo</returns>
          public IList<ChannelInfo> GetChannelList(string mask)
          {
              List<ChannelInfo> list = new List<ChannelInfo>();
              lock (_ChannelListSyncRoot) {
                  _ChannelList = list;
                  _ChannelListReceivedEvent = new AutoResetEvent(false);
                  
                  // request list
                  RfcList(mask);
                  // wait till we have the complete list
                  _ChannelListReceivedEvent.WaitOne();
                  
                  _ChannelListReceivedEvent = null;
                  _ChannelList = null;
              }
              
              return list;
          }
          
          /// <summary>
          /// Fetches a fresh list of users that matches the passed mask
          /// </summary>
          /// <returns>List of ListInfo</returns>
          public IList<WhoInfo> GetWhoList(string mask)
          {
              List<WhoInfo> list = new List<WhoInfo>();
              lock (_WhoListSyncRoot) {
                  _WhoList = list;
                  _WhoListReceivedEvent = new AutoResetEvent(false);
                  
                  // request list
                  RfcWho(mask);
                  // wait till we have the complete list
                  _WhoListReceivedEvent.WaitOne();
                  
                  _WhoListReceivedEvent = null;
                  _WhoList = null;
              }
              
              return list;
          }
          
          /// <summary>
          /// Fetches a fresh ban list of the specified channel
          /// </summary>
          /// <returns>List of ListInfo</returns>
          public IList<BanInfo> GetBanList(string channel)
          {
              List<BanInfo> list = new List<BanInfo>();
              lock (_BanListSyncRoot) {
                  _BanList = list;
                  _BanListReceivedEvent = new AutoResetEvent(false);
                  
                  // request list
                  Ban(channel);
                  // wait till we have the complete list
                  _BanListReceivedEvent.WaitOne();
                  
                  _BanListReceivedEvent = null;
                  _BanList = null;
              }
              
              return list;
          }
  
          /// <summary>
          /// Fetches a fresh ban-exceptions list from the specified channel.
          /// </summary>
          public IList<BanInfo> GetBanExceptionList(string channel)
          {
              List<BanInfo> list = new List<BanInfo>();
              if (!_ServerProperties.BanExceptionCharacter.HasValue) {
                  return list;
              }
              lock (_BanExceptListSyncRoot) {
                  _BanExceptList = list;
                  _BanExceptListReceivedEvent = new AutoResetEvent(false);
  
                  BanException(channel);
                  _BanExceptListReceivedEvent.WaitOne();
  
                  _BanExceptListReceivedEvent = null;
                  _BanExceptList = null;
              }
  
              return list;
          }
  
          /// <summary>
          /// Fetches a fresh invite-exceptions list from the specified channel.
          /// </summary>
          public IList<BanInfo> GetInviteExceptionList(string channel)
          {
              List<BanInfo> list = new List<BanInfo>();
              if (!_ServerProperties.InviteExceptionCharacter.HasValue) {
                  return list;
              }
              lock (_InviteExceptListSyncRoot) {
                  _InviteExceptList = list;
                  _InviteExceptListReceivedEvent = new AutoResetEvent(false);
  
                  InviteException(channel);
                  _InviteExceptListReceivedEvent.WaitOne();
  
                  _InviteExceptListReceivedEvent = null;
                  _InviteExceptList = null;
              }
  
              return list;
          }
          
          public IrcMessageData MessageParser(string rawline)
          {
              if (rawline == null) {
                  throw new ArgumentNullException("rawline");
              }
  
              string         line;
              string[]       linear;
              string         messagecode;
              string         from;
              string         nick = null;
              string         ident = null;
              string         host = null;
              string         channel = null;
              string         message = null;
              string         rawTags = null;
              Dictionary<string, string> tags = new Dictionary<string, string>();
              ReceiveType    type;
              ReplyCode      replycode;
              int            exclamationpos;
              int            atpos;
              int            colonpos;
  
              if (rawline.Length == 0) {
                  throw new ArgumentException("Value must not be empty.", "rawline");
              }
  
              // IRCv3.2 message tags: http://ircv3.net/specs/core/message-tags-3.2.html
              if (rawline[0] == '@') {
                  int spcidx = rawline.IndexOf(' ');
                  rawTags = rawline.Substring(1, spcidx - 1);
                  // strip tags from further parsing for backwards compatibility
                  line = rawline.Substring(spcidx + 1);
  
                  string[] sTags = rawTags.Split(new char[] { ';' });
                  foreach (string s in sTags) {
                      int eqidx = s.IndexOf("=");
  
                      if (eqidx != -1) {
                          tags.Add(s.Substring(0, eqidx), _UnescapeTagValue(s.Substring(eqidx + 1)));
                      } else {
                          tags.Add(s, null);
                      }
                  }
              } else {
                  line = rawline;
              }
  
              if (line[0] == ':') {
                  line = line.Substring(1);
              }
              linear = line.Split(new char[] {' '});
  
              // conform to RFC 2812
              from = linear[0];
              messagecode = linear[1];
              exclamationpos = from.IndexOf("!", StringComparison.Ordinal);
              atpos = from.IndexOf("@", StringComparison.Ordinal);
              colonpos = line.IndexOf(" :", StringComparison.Ordinal);
              if (colonpos != -1) {
                  // we want the exact position of ":" not beginning from the space
                  colonpos += 1;
              }
              if (exclamationpos != -1) {
                  nick = from.Substring(0, exclamationpos);
              } else {
                  if (atpos == -1) {
                      // no ident and no host, should be nick then
                      if (!from.Contains(".")) {
                          // HACK: from seems to be a nick instead of servername
                          nick = from;
                      }
                  } else {
                      nick = from.Substring(0, atpos);
                  }
              }
              if ((atpos != -1) &&
                  (exclamationpos != -1)) {
                  ident = from.Substring(exclamationpos+1, (atpos - exclamationpos)-1);
              }
              if (atpos != -1) {
                  host = from.Substring(atpos+1);
              }
  
              int tmp;
              if (int.TryParse(messagecode, out tmp)) {
                  replycode = (ReplyCode) tmp;
              } else {
                  replycode = ReplyCode.Null;
              }
  
              type = _GetMessageType(line);
              if (colonpos != -1) {
                  message = line.Substring(colonpos + 1);
              }
  
              switch (type) {
                  case ReceiveType.Join:
                  case ReceiveType.Kick:
                  case ReceiveType.Part:
                  case ReceiveType.TopicChange:
                  case ReceiveType.ChannelModeChange:
                  case ReceiveType.ChannelMessage:
                  case ReceiveType.ChannelAction:
                  case ReceiveType.ChannelNotice:
                      channel = linear[2];
                  break;
                  case ReceiveType.Who:
                  case ReceiveType.Topic:
                  case ReceiveType.Invite:
                  case ReceiveType.BanList:
                  case ReceiveType.ChannelMode:
                      channel = linear[3];
                  break;
                  case ReceiveType.Name:
                      channel = linear[4];
                  break;
              }
              
              switch (replycode) {
                  case ReplyCode.List:
                  case ReplyCode.ListEnd:
                  case ReplyCode.ErrorNoChannelModes:
                  case ReplyCode.InviteList:
                  case ReplyCode.ExceptionList:
                      channel = linear[3];
                      break;
              }
  
              if ((channel != null) &&
                  (channel[0] == ':')) {
                      channel = channel.Substring(1);
              }
  
              IrcMessageData data;
              data = new IrcMessageData(this, from, nick, ident, host, channel, message, rawline, type, replycode, tags);
  #if LOG4NET
              Logger.MessageParser.Debug("IrcMessageData "+
                                         "nick: '"+data.Nick+"' "+
                                         "ident: '"+data.Ident+"' "+
                                         "host: '"+data.Host+"' "+
                                         "type: '"+data.Type.ToString()+"' "+
                                         "from: '"+data.From+"' "+
                                         "channel: '"+data.Channel+"' "+
                                         "message: '"+data.Message+"' "+
                                         "tags: '"+rawTags+"' "
                                         );
  #endif
              return data;
          }
  
  	// ISUPPORT-honoring versions of some IrcCommands methods
  
          public override void BanException(string channel)
          {
              var bexchar = _ServerProperties.BanExceptionCharacter;
              if (bexchar.HasValue) {
                  ListChannelMasks("+" + bexchar.Value, channel);
              } else {
                  base.BanException(channel);
              }
          }
  
          public override void BanException(string channel, string hostmask, Priority priority)
          {
              var bexchar = _ServerProperties.BanExceptionCharacter;
              if (bexchar.HasValue) {
                  ModifyChannelMasks("+" + bexchar.Value, channel, hostmask, priority);
              } else {
                  base.BanException(channel, hostmask, priority);
              }
          }
  
          public override void BanException(string channel, string hostmask)
          {
              var bexchar = _ServerProperties.BanExceptionCharacter;
              if (bexchar.HasValue) {
                  ModifyChannelMasks("+" + bexchar.Value, channel, hostmask);
              } else {
                  base.BanException(channel, hostmask);
              }
          }
  
          public override void BanException(string channel, string[] hostmasks)
          {
              var bexchar = _ServerProperties.BanExceptionCharacter;
              if (bexchar.HasValue) {
                  ModifyChannelMasks("+" + bexchar.Value, channel, hostmasks);
              } else {
                  base.BanException(channel, hostmasks);
              }
          }
  
          public override void UnBanException(string channel, string hostmask, Priority priority)
          {
              var bexchar = _ServerProperties.BanExceptionCharacter;
              if (bexchar.HasValue) {
                  ModifyChannelMasks("-" + bexchar.Value, channel, hostmask, priority);
              } else {
                  base.UnBanException(channel, hostmask, priority);
              }
          }
  
          public override void UnBanException(string channel, string hostmask)
          {
              var bexchar = _ServerProperties.BanExceptionCharacter;
              if (bexchar.HasValue) {
                  ModifyChannelMasks("-" + bexchar.Value, channel, hostmask);
              } else {
                  base.UnBanException(channel, hostmask);
              }
          }
  
          public override void UnBanException(string channel, string[] hostmasks)
          {
              var bexchar = _ServerProperties.BanExceptionCharacter;
              if (bexchar.HasValue) {
                  ModifyChannelMasks("-" + bexchar.Value, channel, hostmasks);
              } else {
                  base.UnBanException(channel, hostmasks);
              }
          }
  
          public override void InviteException(string channel)
          {
              var iexchar = _ServerProperties.InviteExceptionCharacter;
              if (iexchar.HasValue) {
                  ListChannelMasks("+" + iexchar.Value, channel);
              } else {
                  base.InviteException(channel);
              }
          }
  
          public override void InviteException(string channel, string hostmask, Priority priority)
          {
              var iexchar = _ServerProperties.InviteExceptionCharacter;
              if (iexchar.HasValue) {
                  ModifyChannelMasks("+" + iexchar.Value, channel, hostmask, priority);
              } else {
                  base.InviteException(channel, hostmask, priority);
              }
          }
  
          public override void InviteException(string channel, string hostmask)
          {
              var iexchar = _ServerProperties.InviteExceptionCharacter;
              if (iexchar.HasValue) {
                  ModifyChannelMasks("+" + iexchar.Value, channel, hostmask);
              } else {
                  base.InviteException(channel, hostmask);
              }
          }
  
          public override void InviteException(string channel, string[] hostmasks)
          {
              var iexchar = _ServerProperties.InviteExceptionCharacter;
              if (iexchar.HasValue) {
                  ModifyChannelMasks("+" + iexchar.Value, channel, hostmasks);
              } else {
                  base.InviteException(channel, hostmasks);
              }
          }
  
          public override void UnInviteException(string channel, string hostmask, Priority priority)
          {
              var iexchar = _ServerProperties.InviteExceptionCharacter;
              if (iexchar.HasValue) {
                  ModifyChannelMasks("-" + iexchar.Value, channel, hostmask, priority);
              } else {
                  base.UnInviteException(channel, hostmask, priority);
              }
          }
  
          public override void UnInviteException(string channel, string hostmask)
          {
              var iexchar = _ServerProperties.InviteExceptionCharacter;
              if (iexchar.HasValue) {
                  ModifyChannelMasks("-" + iexchar.Value, channel, hostmask);
              } else {
                  base.UnInviteException(channel, hostmask);
              }
          }
  
          public override void UnInviteException(string channel, string[] hostmasks)
          {
              var iexchar = _ServerProperties.InviteExceptionCharacter;
              if (iexchar.HasValue) {
                  ModifyChannelMasks("-" + iexchar.Value, channel, hostmasks);
              } else {
                  base.UnInviteException(channel, hostmasks);
              }
          }
          
          protected virtual IrcUser CreateIrcUser(string nickname)
          {
               return new IrcUser(nickname, this);
          }
          
          protected virtual Channel CreateChannel(string name)
          {
              if (_SupportNonRfc) {
                  return new NonRfcChannel(name);
              } else {
                  return new Channel(name);
              }
          }
          
          protected virtual ChannelUser CreateChannelUser(string channel, IrcUser ircUser)
          {
              if (_SupportNonRfc) {
                  return new NonRfcChannelUser(channel, ircUser);
              } else {
  
gt:                 return new ChannelUser(channel, ircUser);