<?php
/*
+---------------------------------------------------------------------------
|   PHP-IRC v2.2.1 Service Release
|   ========================================================
|   by Manick
|   (c) 2001-2005 by http://www.phpbots.org/
|   Contact: manick@manekian.com
|   irc: #manekian@irc.rizon.net
|   ========================================
+---------------------------------------------------------------------------
|   > irc module
|   > Module written by Manick
|   > Module Version Number: 2.2.0
+---------------------------------------------------------------------------
|   > This program is free software; you can redistribute it and/or
|   > modify it under the terms of the GNU General Public License
|   > as published by the Free Software Foundation; either version 2
|   > of the License, or (at your option) any later version.
|   >
|   > This program 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 General Public License for more details.
|   >
|   > You should have received a copy of the GNU General Public License
|   > along with this program; if not, write to the Free Software
|   > Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+---------------------------------------------------------------------------
|   Changes
|   =======-------
|   > If you wish to suggest or submit an update/change to the source
|   > code, email me at manick@manekian.com with the change, and I
|   > will look to adding it in as soon as I can.
+---------------------------------------------------------------------------
*/

class irc {

	// Config Vars
	private $clientConfig = array();
	private $configFilename = "";
		// nick, realname, localhost, remotehost, ident, host, port
	private $serverConfig = array();
	private $nick = "";
	private $tempNick = "";
		// only used if our nick is taken initially
	private $clientIP;
	private $clientLongIP;
		// Above address related determined on runtime..
	private $startTime = 0; //when the bot was started

	// Status Vars
	private $status = STATUS_IDLE;
	private $exception;
	private $reportedStatus = STATUS_IDLE;

	//Classes
	private $timerClass = null;
	private $socketClass = null;
	private $dccClass = null;
	private $parserClass = null;

	//Socket Vars
	private $sockInt; //old pre 2.1.2 method
	private $conn; //new 2.1.2 method
	private $connectStartTime = 0;
	private $lagTime = 0;
	private $timeConnected = 0;

	//Queue Vars
	private $textQueueTime = 0;
	private $textQueue = array();
	private $textQueueLength = 0;
	
	/* This variable will be set when new text is sent to be sent to the irc server, its so we don't
	   have to call addQueue() more than once. */
	private $textQueueAdded = false;
	private $modeQueueAdded = false;

	private $modeQueue = array();
	private $modeQueueLength = 0;
	private $modeTimerAdded = false;

	//Stats Var
	private $stats = array();

	//Parsing Vars
	private $lVars = array();
	private $modeArray;
	private $prefixArray;

	//Channel and User Linked Lists
	private $chanData = array();
	private $maintainChannels = array();
/*	private $chanData = null; */

	private $usageList = array();

	//Timers
	private $timeoutTimer = 0;
	  private $lastPing = 0;
	  private $lastPingTime = 0;
	private $nickTimer = 0;
	private $checkChansTimer = 0;

	//Kill
	private $setKill = false;
	
	//My process id
	private $pid;
	
	//Process Queue
	private $procQueue;

	function __construct()
	{

		$this->startTime = time();

		// Initialize the stats array
		$this->stats = array(	'BYTESUP'	=>		0,
								'BYTESDOWN' =>		0,
			);

		$this->pid = getmypid();

		return;

	}

	public function init()
	{
		$this->socketClass->setTcpRange( $this->getClientConf('dccrangestart') );

		/* Add other timers */
		$this->timerClass->addTimer("check_nick_timer", $this, "checkNick", "", NICK_CHECK_TIMEOUT);
		$this->timerClass->addTimer("check_channels_timer", $this, "checkChans", "", CHAN_CHECK_TIMEOUT);
		$this->timerClass->addTimer("check_ping_timeout_timer", $this, "checkPingTimeout", "", PING_TIMEOUT+1);

		/* Timer that makes sure we're connected every 1:15 minutes */
		$this->reconnect();
		//$this->timerClass->addTimer("check_connection", $this, "checkConnection", "", 75, true);
	}

	public function pid()
	{
		return $this->pid;
	}

	public function setConfig($config, $filename = "")
	{
		$this->clientConfig = $config;
		$this->configFilename = $filename;
		$this->nick = $config['nick'];
	}

	public function setProcQueue($class)
	{
		$this->procQueue = $class;
	}

	public function setDccClass($class)
	{
		$this->dccClass = $class;
		$this->dccClass->setIrcClass($this);
	}

	public function setParserClass($class)
	{
		$this->parserClass = $class;
		$this->parserClass->setIrcClass($this);
	}

	public function setSocketClass($class)
	{
		$this->socketClass = $class;
	}

	public function setTimerClass($class)
	{
		$this->timerClass = $class;
	}

	public function setClientConfigVar($var, $value)
	{
		$this->clientConfig[$this->myStrToLower($var)] = $value;
	}

	public function getNick()
	{
		return $this->nick;
	}

	public function getMaintainedChannels()
	{
		return $this->maintainChannels;
	}

	public function getServerConf($var)
	{
		if (isset($this->serverConfig[$this->myStrToUpper($var)]))
		{
			return $this->serverConfig[$this->myStrToUpper($var)];
		}

		return "";
	}


	public function getConfigFilename()
	{
		return $this->configFilename;
	}

	public function getClientConf($var = "")
	{
	
		if ($var != "")
		{
			if (isset($this->clientConfig[$var]))
			{
				return $this->clientConfig[$var];
			}
		}
		else
		{
			return $this->clientConfig;
		}
		
		return "";

	}


	//debug only!
	public function displayUsers()
	{
		$toReturn = "";
		
		foreach($this->chanData AS $chanPtr)
		{

			$toReturn .= $chanPtr->name . "\n";

			foreach($chanPtr->memberList AS $memPtr)
			{
				$toReturn .= $memPtr->realNick . " -- ";
				$toReturn .= $memPtr->status . "\n";
			}

		}

		return trim($toReturn);
	}



	private function addMember($channel, $nick, $ident, $status, $host = "")
	{
		$channel = $this->myStrToLower($channel);
		$realNick = trim($nick);
		$nick = trim($this->myStrToLower($nick));

		$newMember = new memberLink;
		if ($host != "")
		{
			$newMember->host = $host;
		}
		$newMember->nick = $nick;
		$newMember->realNick = $realNick;
		$newMember->ident = $ident;
		$newMember->status = $status;

		if (!isset($this->chanData[$channel]))
		{
			$chanPtr = new channelLink;
			$chanPtr->count = 1;
			$chanPtr->memberList = NULL;
			$chanPtr->banComplete = 1;
			$chanPtr->name = $channel;
			$this->chanData[$channel] = $chanPtr;
		}
		else
		{
			$chanPtr = $this->chanData[$channel];
			
			if (!isset($chanPtr->memberList[$nick]))
			{
				$chanPtr->count++;
			}
		}

		$chanPtr->memberList[$nick] = $newMember;

		return $chanPtr->count;

	}

	public function &getChannelData($channel = "")
	{

		if ($channel == "")
		{
			return $this->chanData;
		}

		$channel = $this->myStrToLower($channel);

		if (isset($this->chanData[$channel]))
		{
			return $this->chanData[$channel];
		}

		return NULL;

	}


	public function setMemberData($nick, $ident, $host)
	{
		$nick = $this->myStrToLower($nick);

		foreach($this->chanData AS $chanPtr)
		{
			if (isset($chanPtr->memberList[$nick]))
			{
				$chanPtr->memberList[$nick]->ident = $ident;
				$chanPtr->memberList[$nick]->host = $host;
			}
		}

	}


	public function getUserData($user, $channel = "")
	{
		if ($user == "")
		{
			return NULL;
		}

		$channel = $this->myStrToLower($channel);
		$user = $this->myStrToLower($user);

		if ($channel == "")
		{
			foreach ($this->chanData AS $chanPtr)
			{
				if (isset($chanPtr->memberList[$user]))
				{
					return $chanPtr->memberList[$user];
				}
			}
			return NULL;
		}
		
		if (isset($this->chanData[$channel]))
		{
			if (isset($this->chanData[$channel]->memberList[$user]))
			{
				return $this->chanData[$channel]->memberList[$user];
			}
			return NULL;
		}
	}






	private function changeMember(	$channel, $oldNick, $newNick, $ident,
									$newStatus, $action, $newHost = "")
	{

		$channel = $this->myStrToLower($channel);
		$ident = trim($ident);
		$oldNick = trim($this->myStrToLower($oldNick));
		$realNick = trim($newNick);
		$newNick = trim($this->myStrToLower($newNick));

		// See if we have a valid usermode

		if ($newStatus != "")
		{
			if (strpos($this->getServerConf('PREFIX'), $newStatus) === false)
			{
				$newStatus = "";
				$action = "";
			}
		}

		//Find our channel, also change user name if no channel name given

		if ($channel == "")
		{
			foreach($this->chanData AS $chanPtr)
			{
				if (isset($chanPtr->memberList[$oldNick]))
				{
					$memPtr = $chanPtr->memberList[$oldNick];

					if ($newHost != "")
					{
						$memPtr->host = $newHost;
					}
					if ($ident != "")
					{
						$memPtr->ident = $ident;
					}
					$memPtr->nick = $newNick;
					$memPtr->realNick = $realNick;
					
					$chanPtr->memberList[$newNick] = $memPtr;
					unset($chanPtr->memberList[$oldNick]);
				}
			}
			return;
		}

		if (isset($this->chanData[$channel]))
		{
			$chanPtr = $this->chanData[$channel];

			if (isset($chanPtr->memberList[$oldNick]))
			{
				$memPtr = $chanPtr->memberList[$oldNick];

				if ($newHost != "")
				{
					$memPtr->host = $newHost;
				}
				if ($ident != "")
				{
					$memPtr->ident = $ident;
				}

				if ($newStatus != "")
				{
					if ($action == "+")
					{
						if (strpos($memPtr->status, $newStatus) === false)
						{
							$memPtr->status .= $newStatus;
						}
					}
					else
					{
						$memPtr->status = str_replace($newStatus, "", $memPtr->status);
					}
				}

			}

		}


	}
	
	private function setChannelData($channel, $item, $val)
	{
		$channel = $this->myStrToLower($channel);
		$item = $this->myStrToLower($item);

		if (isset($this->chanData[$channel]))
		{
			$chanPtr = $this->chanData[$channel];

			switch ($item)
			{
				case "topicby":
					$chanPtr->topicBy = $val;
					break;

				case "topic":
					$chanPtr->topic = $val;
					break;

				case "bancomplete":
					$chanPtr->banComplete = $val;
					break;

				case "created":
					$chanPtr->created = $val;
					break;

				case "modes":
					unset($chanPtr->modes);
					$chanPtr->modes = array();
					$chanPtr->modes['MODE_STRING'] = substr($val, 1);
					break;
			}

		}

	}


	private function clearBanList($channel)
	{
		$channel = $this->myStrToLower($channel);
		
		if (isset($this->chanData[$channel]))
		{
			unset($this->chanData[$channel]->banList);
			$this->chanData[$channel]->banList = array();
		}
	}

	private function changeChannel($channel, $action, $newStatus, $extraStatus = "")
	{

		$channel = $this->myStrToLower($channel);
		$newStatus = trim($newStatus); // i.e, l, b
		
		if (!is_array($extraStatus))
		{
			$extraStatus = trim($extraStatus); // i.e, 50, *!*@*
		}



		//ex. CHANMODES=eIb,k,l,cimnpstMNORZ

		if ($newStatus == "" || $action == "")
		{
			return;
		}

		if (!isset($this->modeArray[$newStatus]))
		{
			return;
		}

		$type = $this->modeArray[$newStatus];

		if ($type != BY_NONE && $extraStatus == "")
		{
			return;
		}

		if (strpos($this->getServerConf('CHANMODES'), $newStatus) === false)
		{
			return;
		}

		//Find our channel
		
		if (isset($this->chanData[$channel]))
		{
			$chanPtr = $this->chanData[$channel];

			if (!is_array($chanPtr->modes))
			{
				unset($chanPtr->modes);
				$chanPtr->modes = array();
			}

			if (!isset($chanPtr->modes['MODE_STRING']))
			{
				$chanPtr->modes['MODE_STRING'] = "";
			}

			if ($type == BY_MASK)
			{

				if ($newStatus == "b")
				{
					if ($action == "+")
					{
						if (is_array($extraStatus))
						{
							$ban = $extraStatus[0];
							$time = $extraStatus[1];
						}
						else
						{
							$ban = $extraStatus;
							$time = time();
						}

						$chanPtr->banList[] = array(	'HOST'	=>	$ban,
														'TIME'	=>	$time,
													);


					}
					else
					{
						foreach ($chanPtr->banList AS $index => $data)
						{
							if ($data['HOST'] == $extraStatus)
							{
								unset($chanPtr->banList[$index]);
								break;
							}
						}
					}
				}
			}
			else
			{
				if ($action == "+")
				{
					if (strpos($chanPtr->modes['MODE_STRING'], $newStatus) === false)
					{
						$chanPtr->modes['MODE_STRING'] .= $newStatus;
						if ($type != BY_NONE)
						{
							$chanPtr->modes[$newStatus] = $extraStatus;
						}
					}
				}
				else
				{
					$chanPtr->modes['MODE_STRING'] = str_replace($newStatus, "", $chanPtr->modes['MODE_STRING']);
					if ($type != BY_NONE)
					{
						unset($chanPtr->modes[$newStatus]);
					}
				}
			}

		}


	}

	private function purgeChanList()
	{
		foreach ($this->chanData AS $cIndex => $chanPtr)
		{
			foreach ($chanPtr->memberList AS $mIndex => $memPtr)
			{
				unset($chanPtr->memberList[$mIndex]);
			}

			unset($this->chanData[$cIndex]);
		}

		unset($this->chanData);
		$this->chanData = array();
	}

	private function removeMember($channel, $nick)
	{

		$channel = $this->myStrToLower($channel);
		$nick = trim($this->myStrToLower($nick));

		if ($channel == "")
		{
			foreach($this->chanData AS $chanPtr)
			{
				if (isset($chanPtr->memberList[$nick]))
				{
					unset($chanPtr->memberList[$nick]);
					$chanPtr->count--;
				}
			}

		}
		else
		{
			if (isset($this->chanData[$channel]))
			{
				if (isset($this->chanData[$channel]->memberList[$nick]))
				{
					unset($this->chanData[$channel]->memberList[$nick]);
					$this->chanData[$channel]->count--;
				}
			}
		}

	}

	private function removeChannel($channel)
	{
		$channel = $this->myStrToLower($channel);

		if (isset($this->chanData[$channel]))
		{
			$chanPtr = $this->chanData[$channel];

			foreach ($chanPtr->memberList AS $mIndex => $memPtr)
			{
				$this->removeMember($channel, $mIndex);
			}

			unset($chanPtr->banList);
			unset($chanPtr->modes);

			unset($this->chanData[$channel]);
		}
	}

	public function isChanMode($channel, $mode, $extra = "")
	{
		$channel = $this->myStrToLower($channel);
		$extra = $this->myStrToLower($extra);

		if (!isset($this->chanData[$channel]))
		{
			return;
		}

		$chanPtr = $this->chanData[$channel];

		if (!isset($this->modeArray[$mode]))
		{
			return false;
		}

		$type = $this->modeArray[$mode];

		if ($type == BY_MASK)
		{
			if ($mode == "b")
			{

				foreach ($chanPtr->banList AS $index => $ban)
				{

					if ($this->hostMasksMatch($ban['HOST'], $extra))
					{
						return true;
					}

				}
			}
		}
		else
		{
			if (strpos($chanPtr->modes['MODE_STRING'], $mode) !== false)
			{
				return true;
			}
		}

		return false;
	}

	public function hasModeSet($chan, $nick, $modes)
	{
		$channel = $this->myStrToLower($chan);
		$nick = $this->myStrToLower($nick);

		if (isset($this->chanData[$channel]))
		{
			if (isset($this->chanData[$channel]->memberList[$nick]))
			{
				$memPtr = $this->chanData[$channel]->memberList[$nick];

				while ($modes != "")
				{
					$mode = substr($modes, 0, 1);
					$modes = substr($modes, 1);

					if (strpos($memPtr->status, $mode) !== false)
					{
						return true;
					}
				}

			}
		}

		return false;

	}

	public function isMode($nick, $channel, $mode)
	{
		$channel = $this->myStrToLower($channel);
		$nick = $this->myStrToLower($nick);

		if (isset($this->chanData[$channel]))
		{
			if (isset($this->chanData[$channel]->memberList[$nick]))
			{
				$memPtr = $this->chanData[$channel]->memberList[$nick];

				if ($mode == "online")
				{
					return true;
				}
				else
				{
					if (strpos($memPtr->status, $mode) !== false)
					{
						return true;
					}
				}
			}
		}

		return false;
	}

	private function getHostMask($mask)
	{
		$offsetA = strpos($mask, "!");
		$offsetB = strpos($mask, "@");

		$myMask = array();

		$myMask['nick'] = $this->myStrToLower(substr($mask, 0, $offsetA));
		$myMask['ident']  = $this->myStrToLower(substr($mask, $offsetA + 1, $offsetB - $offsetA - 1));
		$myMask['host'] = $this->myStrToLower(substr($mask, $offsetB + 1));

		return $myMask;
	}

	public function hostMasksMatch($mask1, $mask2)
	{

		$maskA = $this->getHostMask($mask1);
		$maskB = $this->getHostMask($mask2);

		$ident = false;
		$nick = false;
		$host = false;

		if ($maskA['ident'] == $maskB['ident']
			|| $maskA['ident'] == "*" || $maskB['ident'] == "*")
		{
			$ident = true;
		}

		if ($maskA['nick'] == $maskB['nick']
			|| $maskA['nick'] == "*" || $maskB['nick'] == "*")
		{
			$nick = true;
		}

		if ($maskA['host'] == $maskB['host']
			|| $maskA['host'] == "*" || $maskB['host'] == "*")
		{
			$host = true;
		}

		if ($host && $nick && $ident)
		{
			return true;
		}
		else
		{
			return false;
		}

	}


	public function setToIdleStatus()
	{
		$this->status = STATUS_IDLE;
	}


	private function getStatus()
	{
		$this->reportedStatus = $this->status;
		return $this->status;
	}

	private function getStatusChange()
	{
		return ($this->reportedStatus != $this->status);
	}

	private function getException()
	{
		return $this->exception;
	}

	private function updateContext()
	{

		if ($this->getStatusChange())
		{
			$status = $this->getStatus();
			$statusStr = $this->getStatusString($status);
			$this->log("STATUS: " . $statusStr);
			$this->dccClass->dccInform("Status: " . $statusStr);

			switch($status)
			{
				case STATUS_ERROR:
					$exception = $this->getException();
					$this->log("Error: " . $exception->getMessage());
					$this->dccClass->dccInform("Error: " . $exception->getMessage());
					break;
				default:
					break;
			}

		}

	}

	public function reconnect()
	{
		$this->updateContext();

		try {

			$this->connectStartTime = time();

			$conn = new connection($this->getClientConf('server'), $this->getClientConf('port'), CONNECT_TIMEOUT);

			$this->conn = $conn;

			$conn->setSocketClass($this->socketClass);
			$conn->setIrcClass($this);
			$conn->setCallbackClass($this);
			$conn->setTimerClass($this->timerClass);
			$conn->init();

			if ($conn->getError())
			{
				throw new ConnectException($conn->getErrorMsg());
			}
			
			//Bind socket...
			if ($this->getClientConf('bind') != "")
			{
				$conn->bind($this->getClientConf('bind'));
			}

			$this->sockInt = $conn->getSockInt();
			$conn->connect();


		}
		catch (ConnectException $e)
		{
			$this->beginReconnectTimer();
			$this->status = STATUS_ERROR;
			$this->exception = $e;
			return;
		}

		$this->status = STATUS_CONNECTING;

		$this->updateContext();

		return;
	}

	public function onConnect($conn)
	{
		$this->status = STATUS_CONNECTED;

		$this->updateContext();

		$this->timeoutTimer = time();
		$ip = "";
		if ($this->getClientConf('natip') != "")
		{
			$ip = $this->getClientConf('natip');
		}
		$this->setClientIP($ip);
		
		$this->register();
		//TODO: Add registration timeout timer (with $conn to check identity)

		return false;
	}

	public function onRead($conn)
	{
		$this->updateContext();
		
		return $this->readInput();
	}

	public function onWrite($conn)
	{
		//Do nothing.. this function has no purpose for the ircClass
		return false;
	}

	public function onAccept($listenConn, $newConn)
	{
		//Do nothing.. this function has no purpose for the ircClass
		return false;
	}

	public function onDead($conn)
	{
		if ($conn->getError())
		{
			$this->status = STATUS_ERROR;
			$this->exception = new ConnectException($conn->getErrorMsg());
		}

		$this->disconnect();
		return false;
	}

	public function onConnectTimeout($conn)
	{
		$this->status = STATUS_ERROR;
		$this->exception = new ConnectException("Connection attempt timed out");
		$this->disconnect();
	}

	private function beginReconnectTimer()
	{
		$this->timerClass->addTimer(self::randomHash(), $this, "reconnectTimer", $this->conn, ERROR_TIMEOUT);
	}

	public function reconnectTimer($conn)
	{
		//If curr connection is equal to the stored connection, then no forced
		//connect was attempted, so attempt another.

		if ($this->conn === $conn)
		{
			$this->reconnect();
		}

		return false;
	}


	public function disconnect()
	{
		$this->conn->disconnect();

		$this->updateContext();

		$this->status = STATUS_ERROR;
		$this->exception = new ConnectException("Disconnected from server");

		$this->updateContext();

		//reset all vars
		$this->purgeChanList();
		$this->timeoutTimer = 0;
		$this->lastPing = 0;
		$this->lastPingTime = 0;
		$this->nickTimer = 0;
		$this->connectStartTime = 0;
		$this->lagTime = 0;
		$this->checkChansTimer = 0;
		$this->purgeTextQueue();
		$this->nick = $this->getClientConf('nick');
		$this->tempNick = "";
		unset($this->modeArray);
		unset($this->prefixArray);
		unset($this->serverConfig);
		$this->modeArray = array();
		$this->prefixArray = array();
		$this->serverConfig = array();
		
		$this->beginReconnectTimer();

		return;
	}

	private function register()
	{
		$login_string = "NICK ".$this->getClientConf('nick')."\r\n"."USER ".$this->getClientConf('ident')." "."localhost"." ".$this->getClientConf('server')." :".$this->getClientConf('realname');
		if ($this->getClientConf('serverpassword') != "")
			$login_string = "PASS ".$this->getClientConf('serverpassword')."\r\n".$login_string;

		$validate = $this->clientFormat($login_string);

		$this->timeConnected = time();
		$this->pushAfter($validate);
		$this->status = STATUS_CONNECTED_SENTREGDATA;
		
		$this->timerClass->addTimer($this->randomHash(), $this, "regTimeout", $this->sockInt, REGISTRATION_TIMEOUT);
	}


	public function regTimeout($sockInt)
	{
		if ($sockInt != $this->sockInt)
		{
			return false;
		}

		if ($this->status != STATUS_CONNECTED_SENTREGDATA)
		{
			return false;
		}

		$this->disconnect();

		$this->status = STATUS_ERROR;
		$this->exception = new ConnectionTimeout("Session Authentication Timed out");

		return false;
	}

	//The following function kills this irc object, but ONLY if there is no send queue.
	//A good way to use it is with a timer, say, ever second or so.
	public function shutDown()
	{
		if (!$this->conn->getError())
		{
			return;
		}

		/*
		if ($this->socketClass->hasWriteQueue($this->sockInt))
		{
			return true;
		}

		$this->disconnect();
		*/
		$this->parserClass->destroyModules();
		$this->dccClass->closeAll();
		$this->procQueue->removeOwner($this);
		$date = date("h:i:s a, m-d-y");
		$this->log("The bot successfully shutdown, $date");

		//Okay.. now the bot is PSEUDO dead.  It still exists, however it has no open sockets, it will not
		//attempt to reconnect to the server, and all dcc sessions, modules, timers, and processes related to it
		//have been destroyed.

		return false;

	}

	/* Some assorted timers */

	public function checkNick()
	{
		if ($this->getStatusRaw() != STATUS_CONNECTED_REGISTERED)
		{
			return true;
		}

		if ($this->nick != $this->getClientConf('nick'))
		{
			$this->changeNick($this->getClientConf('nick'));
		}

		return true;
	}

	public function checkPingTimeout()
	{
		if ($this->getStatusRaw() != STATUS_CONNECTED_REGISTERED)
		{
			return true;
		}

		try {
			if ($this->lastPing == 1)
			{
				$this->lastPing = 0;
				throw new ConnectionTimeout("The connection with the server timed out.");
			}
			else
			{
				if (time() > $this->timeoutTimer + PING_TIMEOUT + $this->lagTime)
				{
					$this->pushBefore($this->clientFormat("PING :Lagtime"));
					$this->lastPingTime = time();
					$this->lastPing = 1;
				}
			}
		}
		catch (ConnectionTimeout $e)
		{
			$this->disconnect();
			$this->status = STATUS_ERROR;
			$this->exception = $e;
		}

		return true;
	}

	public function getStatusRaw()
	{
		return $this->status;
	}


	public function getLine()
	{
		return $this->lVars;
	}

	private function readInput()
	{
		if ($this->status != STATUS_CONNECTED_REGISTERED && $this->status != STATUS_CONNECTED_SENTREGDATA)
		{
			return false;
		}

		if ($this->socketClass->isDead($this->sockInt) && !$this->socketClass->hasLine($this->sockInt))
		{
			$this->disconnect();
			$this->status = STATUS_ERROR;
			$this->exception = new ReadException("Failed while reading from socket");
			return false;
		}
		
		if ($this->socketClass->hasLine($this->sockInt))
		{
			$this->timeoutTimer = time();

			if ($this->lastPing == 1)
			{
				$this->lagTime = time() - $this->lastPingTime;
			}
			$this->lastPing = 0;

			$line = $this->socketClass->getQueueLine($this->sockInt);
			
			$this->stats['BYTESDOWN'] += strlen($line);

			$this->log($line);

			if (substr($line, 0, 1) != ":")
			{
				$line = ":Server " . $line;
			}

			$line = substr($line, 1);

			$parts = explode(chr(32), $line);

			$params = substr($line,	strlen($parts[0]) + strlen($parts[1]) +	strlen($parts[2]) + 3);
			if (strpos($params, " :"))
			{
				$params = substr($params, 0, strpos($params, " :"));
			}

			$offset1 = strpos($parts[0], '!');
			$offset2 = $offset1 + 1;
			$offset3 = strpos($parts[0], '@') + 1;
			$offset4 = $offset3 - $offset2 - 1;
			$offset5 = strpos($line, " :") + 2;

			unset($this->lVars);

			$this->lVars = array(	'from'		=> 	$parts[0],
									'fromNick'	=>	substr($parts[0], 0, $offset1),
									'fromIdent'	=>	substr($parts[0], $offset2, $offset4),
									'fromHost'	=>	substr($parts[0], $offset3),
									'cmd'		=>	$parts[1],
									'to'		=>	$parts[2],
									'text'		=>	substr($line, $offset5),
									'params'	=>	trim($params),
									'raw'		=>	":" . $line,
								);

			if ($offset5 === false)
			{
				$line['text'] = "";
			}

			if (intval($this->lVars['cmd']) > 0)
			{
				$this->parseServerMsgs($this->lVars['cmd']);
			}
			else
			{
				$this->parseMsgs();
			}
			
			$this->parserClass->parseLine($this->lVars);
		}

		if ($this->socketClass->hasQueue($this->sockInt))
		{
			return true;
		}

		return false;
	}


	private function parseServerMsgs($cmd)
	{
		switch ($cmd)
		{
			case 004:
				$this->status = STATUS_CONNECTED_REGISTERED;
				if ($this->tempNick != "")
				{
					$this->nick = $this->tempNick;
				}
				break;

			case 005:
				$this->parseServerConfig();
				if (!isset($this->modeArray) || !is_array($this->modeArray) || count($this->modeArray) <= 0)
				{
					if ($this->getServerConf("CHANMODES") != "")
					{
						$this->createModeArray();
						$this->checkChans();
					}
				}
				break;
				
			case 311:
				$params = explode(chr(32), $this->lVars['params']);
				$this->setMemberData($params[0], $params[1], $params[2]);

			case 324:
				$params = explode(chr(32), $this->lVars['params']);
				$channel = $params[0];
				$query = substr($this->lVars['params'], strlen($channel) + 1);
				$this->setChannelData($channel, "modes", $query);
				break;

			case 329:
				$params = explode(chr(32), $this->lVars['params']);
				$channel = $params[0];
				$query = substr($this->lVars['params'], strlen($channel) + 1);
				$this->setChannelData($channel, "created", $query);
				break;

			case 332:
				$this->setChannelData(trim($this->lVars['params']), "topic", $this->lVars['text']);
				break;

			case 333:
				$params = explode(chr(32), $this->lVars['params']);
				$channel = $params[0];
				$query = substr($this->lVars['params'], strlen($channel) + 1);
				$this->setChannelData($channel, "topicby", $query);
				break;

			case 352:
				$params = explode(chr(32), $this->lVars['params']);
				$this->changeMember($params[0], $params[4], $params[4], $params[1], "", "", $params[2]);
				break;

			case 353:
				$channel = substr($this->lVars['params'], 2);
				$this->updateOpList($channel);
				break;

			case 367:
				$params = explode(chr(32), $this->lVars['params']);
				$data = $this->getChannelData($params[0]);
				if ($data != NULL)
				{
					if ($data->banComplete == 1)
					{
						$this->clearBanList($params[0]);
						$data->banComplete = 0;
					}

					$this->changeChannel($params[0], "+", "b", array($params[1], $params[3]));
				}
				break;

			case 368:
				$params = explode(chr(32), $this->lVars['params']);
				$channel = $params[0];
				$this->setChannelData($channel, "bancomplete", 1);
				break;

			case 401:
				$this->removeQueues($this->lVars['params']);
				break;

			case 433:
				if ($this->getStatusRaw() != STATUS_CONNECTED_REGISTERED)
				{
					if ($this->nick == $this->getClientConf('nick'))
					{
						$this->changeNick($this->nick . rand() % 1000);
					}
					$this->nickTimer = time();
				}
				break;
		}

	}

	public function isOnline($nick, $chan)
	{
		return $this->isMode($nick, $chan, "online");
	}


	private function updateOpList($channel)
	{
		$channel = $this->myStrToLower($channel);
		$users = explode(chr(32), $this->lVars['text']);

		if (!isset($this->prefixArray) || count($this->prefixArray) <= 0)
		{
			$this->createPrefixArray();
		}

		foreach ($users AS $user)
		{
			if (trim($user) == "")
			{
				continue;
			}

			$userModes = "";
			$userNick = "";

			for ($currIndex = 0; $currIndex < strlen($user); $currIndex++)
			{
				$currChar = substr($user, $currIndex, 1);

				if (!isset($this->prefixArray[$currChar]))
				{
					$userNick = substr($user, $currIndex);
					break;
				}

				$userModes .= $currChar;
			}

			if ($userNick != $this->nick)
			{
				$this->addmember($channel, $userNick, "", $this->convertUserModes($userModes));
			}

		}

	}


	private function convertUserModes($modes)
	{
		$newModes = "";

		for ($index = 0; $index < strlen($modes); $index++)
		{
			$newModes .= $this->prefixArray[$modes[$index]];
		}

		return $newModes;
	}


	private function createPrefixArray()
	{
		$modeSymbols = substr($this->getServerConf('PREFIX'), strpos($this->getServerConf('PREFIX'), ")") + 1);

		$leftParan = strpos($this->getServerConf('PREFIX'), "(");
		$rightParan = strpos($this->getServerConf('PREFIX'), ")");
		$modeLetters = substr($this->getServerConf('PREFIX'), $leftParan + 1, $rightParan - $leftParan - 1);

		for ($index = 0; $index < strlen($modeLetters); $index++)
		{
			$this->prefixArray[$modeSymbols[$index]] = $modeLetters[$index];
		}

	}


	public function doMode()
	{
		$this->modeQueueAdded = false;

		$currAct = "";
		$currChan = "";

		$modeLineModes = "";
		$modeLineParams = "";

		$maxModesPerLine = ($this->getServerConf('MODES') == "" ? 1 : $this->getServerConf('MODES'));
		$currLineModes = 0;

		foreach($this->modeQueue AS $modeChange)
		{
			if ($modeLineModes != "" && ($currChan != $modeChange['CHANNEL'] || $currLineModes >= $maxModesPerLine))
			{
				$this->pushAfter($this->clientFormat("MODE " . $currChan . " " . $modeLineModes . " " . trim($modeLineParams)));
				$modeLineModes = "";
				$currAct = "";
				$currChan = "";
				$modeLineParams = "";
				$currLineModes = 0;
			}

			if ($currAct != $modeChange['ACT'])
			{
				$modeLineModes .= $modeChange['ACT'];
			}

			$modeLineModes .= $modeChange['MODE'];

			if ($modeChange['USER'] != "")
			{
				$modeLineParams .= $modeChange['USER'] . " ";
			}

			$currLineModes++;

			$currAct = $modeChange['ACT'];
			$currChan = $modeChange['CHANNEL'];

		}

		if ($modeLineModes != "")
		{
			$this->pushAfter($this->clientFormat("MODE " . $currChan . " " . $modeLineModes . " " . trim($modeLineParams)));
		}

		unset($this->modeQueue);
		$this->modeQueue = array();
		$this->modeQueueLength = 0;
		
		return false;

	}

	public function changeMode($chan, $act, $mode, $user)
	{
		$user = trim($user);
		$chan = trim($chan);
		$act = trim($act);
		$mode = trim($mode);

		if ($chan == "" || $mode == "")
		{
			return false;
		}

		if (!($act == "+" || $act == "-"))
		{
			return false;
		}

		if (strlen($mode) > 1)
		{
			return false;
		}

		if (!isset($this->modeArray[$mode]))
		{
			if ($user == "")
			{
				return false;
			}
		}

		if ($this->modeQueueAdded != true)
		{
			$this->timerClass->addTimer("mode_timer", $this, "doMode", "", 0, true);
			$this->modeQueueAdded = true;
		}

		$this->modeQueue[] = array('USER' => $user, 'CHANNEL' => $chan, 'ACT' => $act, 'MODE' => $mode);
		$this->modeQueueLength++;

		return true;

	}

	public function parseModes($modeString)
	{
		$modeString .= " ";

		$offset = strpos($modeString, chr(32));
		$modes = substr($modeString, 0, $offset);
		$users = substr($modeString, $offset + 1);
		$userArray = explode(chr(32), $users);

		if (count($this->modeArray) <= 0)
		{
			$this->createModeArray();
		}

		$action = "";
		$returnModes = array();

		while (trim($modes) != "")
		{
			$thisMode = substr($modes, 0, 1);
			
			$modes = substr($modes, 1);

			if ($thisMode == "-" || $thisMode == "+")
			{
				$action = $thisMode;
				continue;
			}

			if (strpos($this->getServerConf('CHANMODES'), $thisMode) !== false)
			{
				if (!isset($this->modeArray[$thisMode]))
				{
					return false;
				}

				$type = $this->modeArray[$thisMode];
				$extra = "";
				if ($type != BY_NONE)
				{
					$extra = array_shift($userArray);
				}

				$type = CHANNEL_MODE;

			}
			else
			{
				$extra = array_shift($userArray);
				$type = USER_MODE;
			}

			$returnModes[] = array(	'ACTION'	=> 	$action,
									'MODE'		=>	$thisMode,
									'EXTRA'		=>	$extra,
									'TYPE'		=>	$type,
									);

		}

		return $returnModes;
	}
	
	public static function intToSizeString($size)
	{

		$i = 20;
		while ($size > pow(2, $i))
		{
			$i += 10;
		}

		switch ($i)
		{
			case 20:  //kb
				$num = $size / 1000;
				$type = "KB";
				break;
			case 30:  //mb
				$num = $size / 1000000;
				$type = "MB";
				break;
			case 40:  //gb
				$num = $size / 1000000000;
				$type = "GB";
				break;
			case 50:  //tb
				$num = $size / 1000000000000;
				$type = "TB";
				break;
			default:  //pb
				$num = $size / 1000000000000000;
				$type = "PB";
				break;
		}

		$stringSize = round($num, 2) . $type;

		return $stringSize;

	}


	public function checkIgnore($mask)
	{
		$ignore = $this->getClientConf('ignore');

		if ($ignore == "")
		{
			return false;
		}

		if (!is_array($ignore))
		{
			$ignore = array($ignore);
		}

		foreach($ignore AS $ig)
		{
			$case = $this->hostMasksMatch($mask, $ig);

			if ($case)
			{
				return true;
			}
		}

		return false;
	}


	private function parseMsgs()
	{

		switch($this->lVars['cmd'])
		{
			case "JOIN":
				$chan = $this->lVars['to'];
				if (substr($this->lVars['to'], 0, 1) == ":")
				{
					$chan = substr($this->lVars['to'], 1);
				}
				$this->addMember($chan, $this->lVars['fromNick'], $this->lVars['fromIdent'], "", $this->lVars['fromHost']);
				if ($this->lVars['fromNick'] == $this->getNick())
				{
					$this->sendRaw("MODE " . $chan);

					if (isset($this->clientConfig['populatebans']))
					{
						$this->sendRaw("MODE " . $chan . " +b");
					}

					if (isset($this->clientConfig['populatewho']))
					{
						$this->sendRaw("WHO " . $chan);
					}
				}
				break;

			case "PART":
				if ($this->lVars['fromNick'] == $this->nick)
				{
					$this->removeChannel($this->lVars['to']);
				}
				else
				{
					$this->removeMember($this->lVars['to'], $this->lVars['fromNick']);
				}
				break;

			case "QUIT":
				if ($this->lVars['fromNick'] == $this->nick)
				{
					$this->purgeChanList();
				}
				else
				{
					$this->removeMember("", $this->lVars['fromNick']);
				}
				break;

			case "NICK":
				if ($this->lVars['fromNick'] == $this->nick)
				{
					$this->nick = $this->lVars['text'];
				}
				$this->changeMember("", $this->lVars['fromNick'], $this->lVars['text'], "", "", "");
				break;

			case "KICK":
				if ($this->myStrToLower($this->lVars['params']) == $this->myStrToLower($this->nick))
				{
					$this->removeChannel($this->lVars['to']);
					$this->joinChannel($this->lVars['to']);
				}
				else
				{
					$this->removeMember($this->lVars['to'], $this->lVars['params']);
				}
				break;

			case "MODE":
				$channel = $this->myStrToLower($this->lVars['to']);
				if ($channel == $this->myStrToLower($this->nick))
					break;
				$modes = $this->parseModes($this->lVars['params']);
				foreach($modes AS $mode)
				{
					if ($mode['TYPE'] == CHANNEL_MODE)
					{
						$this->changeChannel($channel, $mode['ACTION'], $mode['MODE'], $mode['EXTRA']);
					}
					else
					{
						$this->changeMember($channel, $mode['EXTRA'], $mode['EXTRA'], "", $mode['MODE'], $mode['ACTION']);
					}
					unset($mode);
				}
				unset($modes);
				break;
			case "NOTICE":
				if ($this->checkIgnore($this->lVars['from']))
				{
					return;
				}
				if ($this->myStrToLower($this->lVars['fromNick']) == "nickserv")
				{
					if (strpos($this->myStrToLower($this->lVars['text']), "identify") !== false)
					{
						if ($this->getClientConf('password') != "")
						{
							$this->pushBefore($this->clientFormat("PRIVMSG HostServ :ON"));
							$this->pushBefore($this->clientFormat("PRIVMSG NickServ :IDENTIFY " . $this->getClientConf('password')));
							$this->pushBefore($this->clientFormat("MODE " . $this->getClientConf('nick') . " +B"));
						}
					}
				}
				break;
			case "PRIVMSG":
				if ($this->checkIgnore($this->lVars['from']))
				{
					return;
				}
				if (strpos($this->lVars['text'], chr(1)) !== false)
				{
					$this->parseCtcp();
				}
				break;
			case "TOPIC":
				$this->setChannelData($this->lVars['to'], "topic", $this->lVars['text']);
				break;

			case "PING":
				if ($this->lVars['from'] == "Server")
				{
					$this->pushBefore($this->clientFormat("PONG :" . $this->lVars['text']));
				}

			default:
				break;


		}




	}



	private function parseCtcp()
	{
		$cmd = str_replace(chr(1), "", $this->lVars['text']) . " ";
		$query = trim(substr($cmd, strpos($cmd, chr(32)) + 1));
		$cmd = substr($this->myStrToLower($cmd), 0, strpos($cmd, chr(32)));

		$msg = "";

		switch($cmd)
		{
			case "version":
				// PLEASE DO NOT CHANGE THE FOLLOWING LINE OF CODE.  It is the only way for people to know that this project
				// exists.  If you would like to change it, please leave the project name/version or url in there somewhere,
				// so that others may find this project as you have. :)
				$msg = "PHP-iRC v" . VERSION . " [".VERSION_DATE."] by Manick (visit http://www.phpbots.org/ to download)";
				$this->notice($this->lVars['fromNick'], chr(1) . $msg . chr(1));
				$msg = "";
				$this->showModules($this->lVars['fromNick']);
				break;

			case "time":
				$msg = "My current time is " . date("l, F jS, Y @ g:i a O", time()) . ".";
				break;

			case "uptime":
				$msg = "My uptime is " . $this->timeFormat($this->getRunTime(), "%d days, %h hours, %m minutes, and %s seconds.");
				break;

			case "ping":
				$msg = "PING " . $query;

		}

		if ($msg != "")
		{
			$this->notice($this->lVars['fromNick'], chr(1) . $msg . chr(1));
		}

	}

	//Split huge lines up by spaces 255 by default
	public static function multiLine($text, $separator = " ")
	{
		$returnArray = array();
		$text = trim($text);
		$strlen = strlen($text);
		$sepSize = strlen($separator);

		while ($strlen > 0)
		{
			if (256 > $strlen)
			{
				$returnArray[] = $text;
				break;
			}

			for ($i = 255; $i > 0; $i--)
			{
				if (substr($text, $i, $sepSize) == $separator)
				{
					break;
				}
			}

			if ($i <= 0)
			{
				$returnArray[] = substr($text, 0, 255);
				$text = substr($text, 254);
				$strlen -= 255;
			}
			else
			{
				$returnArray[] = substr($text, 0, $i);
				$text = substr($text, $i - 1);
				$strlen -= $i;
			}
		}

		return $returnArray;
	}


	private function showModules($nick)
	{
		$cmdList = $this->parserClass->getCmdList();

		if (isset($cmdList['file']))
		{
			$mod = "";

			foreach($cmdList['file'] AS $module)
			{
				$class = $module['class'];

				if (isset($class->dontShow) && $class->dontShow == true)
				{
					continue;
				}
				$mod .= "[" . $class->title . " " . $class->version . "] ";
			}

			if ($mod != "")
			{
				$modArray = $this->multiLine("Running Modules: " . $mod);

				foreach ($modArray AS $myMod)
				{
					$this->notice($nick, chr(1) . $myMod . chr(1));
				}
			}
		}
		unset($cmdList);
	}


	public function getRunTime()
	{
		return (time() - $this->startTime);
	}


	public static function timeFormat($time, $format)
	{

		$days = 0;
		$seconds = 0;
		$minutes = 0;
		$hours = 0;

		if (strpos($format, "%d") !== FALSE)
		{
			$days = (int) ($time / (3600 * 24));
			$time -= ($days * (3600 * 24));
		}

		if (strpos($format, "%h") !== FALSE)
		{
			$hours = (int) ($time / (3600));
			$time -= ($hours * (3600));
		}

		if (strpos($format, "%m") !== FALSE)
		{
			$minutes = (int) ( $time / (60));
			$time -= ($minutes * (60));
		}

		$seconds = $time;

		$format = str_replace("%d", $days, $format);
		$format = str_replace("%s", $seconds, $format);
		$format = str_replace("%m", $minutes, $format);
		$format = str_replace("%h", $hours, $format);

		return $format;

	}


	private function createModeArray()
	{
		$modeArray = explode(",", $this->getServerConf('CHANMODES'));
		
		for ($i = 0; $i < count($modeArray); $i++)
		{
			for ($j = 0; $j < strlen($modeArray[$i]); $j++)
			{
				$this->modeArray[$modeArray[$i][$j]] = $i;
			}

		}
	}

	public function checkChans()
	{
		if ($this->getStatusRaw() != STATUS_CONNECTED_REGISTERED)
		{
			return true;
		}

		foreach ($this->maintainChannels AS $index => $channel)
		{
			if ($this->isOnline($this->nick, $channel['CHANNEL']) === false)
			{
				if ($channel['KEY'] != "")
				{
					$this->joinChannel($channel['CHANNEL'] . " " . $channel['KEY']);
				}
				else
				{
					$this->joinChannel($channel['CHANNEL']);
				}
			}
		}
		
		return true;
	}

	public function getStatusString($status)
	{
		$msg = "";

		switch ($status)
		{
			case STATUS_IDLE:
				$msg = "Idle";
				break;
			case STATUS_ERROR:
				$msg = "Error";
				break;
			case STATUS_CONNECTING:
				$msg = "Connecting to server...";
				break;
			case STATUS_CONNECTED:
				$msg = "Connected to server: " . $this->getClientConf('server') . " " . $this->getClientConf('port');
				break;
			case STATUS_CONNECTED_SENTREGDATA:
				$msg = "Sent registration data, awaiting reply...";
				break;
			case STATUS_CONNECTED_REGISTERED:
				$msg = "Authenticated";
				break;
			default:
				$msg = "Unknown";
		}

		return $msg;
	}


	public function purgeMaintainList()
	{
		unset($this->maintainChannels);
		$this->maintainChannels = array();
	}


	public function removeMaintain($channel)
	{
		$channel = $this->myStrToLower($channel);

		foreach ($this->maintainChannels AS $index => $chan)
		{
			if ($chan['CHANNEL'] == $channel)
			{
				unset($this->maintainChannels[$index]);
				break;
			}
		}
	}


	public function maintainChannel($channel, $key = "")
	{
		$channel = $this->myStrToLower($channel);
		$this->maintainChannels[] = array('CHANNEL' => $channel, 'KEY' => $key);
	}


	public function joinChannel($chan)
	{
		$this->pushBefore($this->clientFormat("JOIN " . $chan));
	}

	public function changeNick($nick)
	{
		$this->pushBefore($this->clientFormat("NICK " . $nick));
		$this->tempNick = $nick;
	}


	private function parseServerConfig()
	{
		$args = explode(chr(32), $this->lVars['params']);

		foreach ($args AS $arg)
		{

			if (strpos($arg, "=") === false)
			{
				$arg .= "=1";
			}

			$argParts = explode("=", $arg);

			$this->serverConfig[$argParts[0]] = $argParts[1];

		}
	}

	private function clientFormat($text)
	{
		return array("USER" => "*", "TEXT" => $text);
	}



	public function removeQueues($nick)
	{
		$nick = $this->myStrToLower($nick);

		foreach ($this->textQueue AS $index => $queue)
		{
			if ($this->myStrToLower($queue['USER']) == $nick)
			{
				unset($this->textQueue[$index]);
				$this->textQueueLength--;
			}
		}
	

	}

	public function getStats()
	{
		return $this->stats;
	}

	public function doQueue()
	{
		if ($this->status < STATUS_CONNECTED)
		{
			$this->textQueueAdded = false;
			return false;
		}

		if ($this->socketClass->hasWriteQueue($this->sockInt) !== false)
		{
			return true;
		}

		if ($this->textQueueLength < 0)
		{
			if (is_array($this->textQueue))
			{
				unset($this->textQueue);
				$this->textQueue = array();
			}
			$this->textQueueAdded = false;
			return false;
		}

		$bufferSize = $this->getClientConf("queuebuffer");

		$bufferSize = $bufferSize <= 0 ? 0 : $bufferSize;

		$sendData = "";

		$nextItem = array_shift($this->textQueue);

		if (trim($nextItem['TEXT']) != "")
		{
			$sendData .= $nextItem['TEXT'] . "\r\n";
		}
		
		unset($nextItem);

		$this->textQueueLength--;

		while ($this->textQueueLength > 0 && ((strlen($this->textQueue[0]['TEXT']) + strlen($sendData)) < $bufferSize))
		{

			$nextItem = array_shift($this->textQueue);

			if (trim($nextItem['TEXT']) != "")
			{
				$sendData .= $nextItem['TEXT'] . "\r\n";
				unset($nextItem);
			}

			$this->textQueueLength--;

		}

		$this->stats['BYTESUP'] += strlen($sendData);

		$this->writeToSocket($sendData);
		
		unset($sendData);

		return true;
	}


	private function writeToSocket($sendData)
	{

		if (DEBUG == 1)
		{
			$this->log($sendData);
		}

		try
		{
			if ($this->socketClass->sendSocket($this->sockInt, $sendData) === false)
			{
				throw new SendDataException("Could not write to socket");
			}
		}
		catch (SendDataException $e)
		{
			$this->disconnect();
			$this->exception = $e;
			$this->status = STATUS_ERROR;
		}

	}


	private function pushAfter($data)
  	{
		$this->textQueueLength++;
		$this->textQueue[] = $data;

		if ($this->textQueueAdded == false)
		{
			$this->timerClass->addTimer("queue_timer", $this, "doQueue", "", $this->getQueueTimeout(), true);
			$this->textQueueAdded = true;
		}

	}


	private function pushBefore($data)
  	{
  		$this->textQueueLength++;
		$this->textQueue = array_merge(array($data), $this->textQueue);

		if ($this->textQueueAdded == false)
		{
			$this->timerClass->addTimer("queue_timer", $this, "doQueue", "", $this->getQueueTimeout(), true);
			$this->textQueueAdded = true;
		}
	}

	public function sendRaw($text, $force = false)
	{
		if ($force == false)
		{
			$format = $this->clientFormat($text);
			$this->pushBefore($format);
		}
		else
		{
			$this->writeToSocket($text . "\r\n");
		}
	}


	public function privMsg($who, $msg, $queue = 1)
	{
		$text = array(	'USER'	=>	$who,
						'TEXT'	=>	'PRIVMSG ' . $who . ' :' .  $msg);

		if ($queue)
		{
			$this->pushAfter($text);
		}
		else
		{
			$this->pushBefore($text);
		}
	}


	public function action($who, $msg, $queue = 1)
	{
		$text = array(	'USER'	=>	$who,
						'TEXT'	=>	'PRIVMSG ' . $who . ' :' . chr(1) . 'ACTION ' .$msg . chr(1));

		if ($queue)
		{
			$this->pushAfter($text);
		}
		else
		{
			$this->pushBefore($text);
		}
	}


	public function notice($who, $msg, $queue = 1)
	{

		$text = array(	'USER'	=>	$who,
						'TEXT'	=>	'NOTICE ' . $who . ' :' .  $msg);

		if ($queue)
		{
			$this->pushAfter($text);
		}
		else
		{
			$this->pushBefore($text);
		}
	}

	public function getClientIP($long = 1)
	{
		if ($long == 1)
		{
			return $this->clientLongIP;
		}
		else
		{
			return $this->clientIP;
		}
	}

	public function setClientIP($ip = "")
	{
		if ($ip == "")
		{
			$ip = $this->socketClass->getHost($this->sockInt);
		}

		$this->clientIP = $ip;
 
 		$this->clientLongIP = ip2long($this->clientIP);
 		
 		if ($this->clientLongIP <= 0)
 		{
			$this->clientLongIP += pow(2,32);
		}
	}



	public function purgeTextQueue()
	{
		$this->textQueueTime = 0;
		unset($this->textQueue);
		$this->textQueue = array();
		$this->textQueueLength = 0;
	}

	public function getTextQueueLength()
	{
		return $this->textQueueLength;
	}

	public function log($data)
	{
		$network = $this->getServerConf('Network') == "" ? $this->getClientConf('server') : $this->getServerConf('Network');

		if (DEBUG == 1)
		{
			echo "[".date("h:i:s")."] " . "({$this->nick}@$network) > " . $data . "\n";
		}
		else
		{
			if ($this->getClientConf('logfile') != "")
			{
				error_log("[".date("h:i:s")."] " . "({$this->nick}@$network) > " . $data . "\n", 3, $this->getClientConf('logfile'));
			}
		}
	}

	public function getUsageList()
	{
		return $this->usageList;
	}

	public function floodCheck($line)
	{
		$host = $line['fromHost'];

		if (!array_key_exists($host, $this->usageList))
		{
			$this->usageList[$host] = new usageLink;
			$this->usageList[$host]->isBanned = false;
			$this->usageList[$host]->lastTimeUsed = time();
			$this->usageList[$host]->timesUsed = 1;
			$user = $this->usageList[$host];
		}
		else
		{
			$user = $this->usageList[$host];

			$floodTime = intval($this->getClientConf('floodtime'));
			if ($floodTime <= 0)
			{
				$floodTime = 60;
			}

			if ($user->isBanned == true)
			{
				if ($user->timeBanned > time() - $floodTime)
				{
					return STATUS_ALREADY_BANNED;
				}
				$user->isBanned = false;
			}


			if ($user->lastTimeUsed < time() - 10)
			{
				$user->timesUsed = 0;
			}
			$user->lastTimeUsed = time();
			$user->timesUsed++;
		}

		$numLines = intval($this->getClientConf('floodlines'));
		if ($numLines <= 0)
		{
			$numLines = 5;
		}

		if ($user->timesUsed > $numLines)
		{
			$user->isBanned = true;
			$user->timeBanned = time();
			$user->timesUsed = 0;
			$user->lastTimeUsed = 0;
			$this->removeQueues($line['fromNick']);
			return STATUS_JUST_BANNED;
		}

		return STATUS_NOT_BANNED;
	}
	
	public static function myStrToLower($text)
	{
		$textA = strtolower($text);

		$textA = str_replace("\\", "|", $textA);
		$textA = str_replace("[", "{", $textA);
		$textA = str_replace("]", "}", $textA);
		$textA = str_replace("~", "^", $textA);

		return $textA;
	}

	public static function myStrToUpper($text)
	{
		$textA = strtoupper($text);

		$textA = str_replace("|", "\\", $textA);
		$textA = str_replace("{", "[", $textA);
		$textA = str_replace("}", "]", $textA);
		$textA = str_replace("^", "~", $textA);

		return $textA;
	}

	public static function randomHash()
	{
		return md5(uniqid(rand(), true));
	}

	private function getQueueTimeout()
	{
		$timeout = $this->getClientConf("queuetimeout");
		$timeout = $timeout <= 0 ? 0 : $timeout;
		return $timeout;
	}

	public function addQuery($host, $port, $query, $line, $class, $function)
	{
		$remote = new remote($host, $port, $query, $line, $class, $function, 8);
		$remote->setIrcClass($this);
		$remote->setTimerClass($this->timerClass);
		$remote->setSocketClass($this->socketClass);
		return $remote->connect();
	}

}