class UT2K4IRCLink extends BufferedTCPLink;

var IpAddr			ServerIpAddr;

var string			ServerAddress;
var int				ServerPort;

var string			NickName;
var string			UserIdent;
var string			FullName;
var string			DefaultChannel;

var localized string InvalidAddressText;
var localized string ErrorBindingText;
var localized string ResolveFailedText;
var localized string ConnectedText;
var localized string ConnectingToText;
var localized string TimeOutError;
var localized string InviteString;

var UT2K4IRC_System SystemPage;

var string DisconnectReason;
var string VersionString;

struct CommandAlias
{
	var() string AliasText;
	var() string RealCommand;
};

var config array<CommandAlias> Shortcuts;

var transient float		SinceLastLevCheck;

function PostBeginPlay()
{
	Super.PostBeginPlay();
	Disable('Tick');
}

function CloseMe()
{
	if ( SystemPage != None )
		SystemPage.CloseLink(Self, False);

	else DestroyLink();
}


function Connect(UT2K4IRC_System InSystemPage, string InServer, string InNickName, string InUserIdent, string InFullName, string InDefaultChannel)
{
	local int i;

    log("UT2K4IRCLink Connect:"@InServer@InNickName@InUserIdent@InFullName@InDefaultChannel,'IRC');

	SystemPage = InSystemPage;
	NickName = InNickName;
	FullName = InFullName;
	UserIdent = InUserIdent;
	DefaultChannel = InDefaultChannel;

	i = InStr(InServer, ":");
	if(i == -1)
	{
		ServerAddress = InServer;
		ServerPort = 6667;
	}
	else
	{
		ServerAddress = Left(InServer, i);
		ServerPort = Int(Mid(InServer, i+1));
	}

	ResetBuffer();
	ServerIpAddr.Port = ServerPort;
	SetTimer(20, False);
	SystemPage.SystemText( ConnectingToText@ServerAddress );
	Resolve( ServerAddress );
}

function string ChopLeft(string Text)
{
	while(Text != "" && InStr(": !", Left(Text, 1)) != -1)
		Text = Mid(Text, 1);
	return Text;
}

function string RemoveNickPrefix(string Nick)
{
	while(Nick != "" && InStr(":@+%", Left(Nick, 1)) != -1)
		Nick = Mid(Nick, 1);
	return Nick;
}

function string Chop(string Text)
{
	while(Text != "" && InStr(": !", Left(Text, 1)) != -1)
		Text = Mid(Text, 1);
	while(Text != "" && InStr(": !", Right(Text, 1)) != -1)
		Text = Left(Text, Len(Text)-1);

	return Text;
}

function Resolved( IpAddr Addr )
{
	// Set the address
	ServerIpAddr.Addr = Addr.Addr;

	// Handle failure.
	if( ServerIpAddr.Addr == 0 )
	{
		if(SystemPage != None)
		{
			SystemPage.SystemText( InvalidAddressText );
			CloseMe();
		}
		return;
	}

	// Display success message.
	Log( "UT2004 UT2K4IRCLink: Server is "$ServerAddress$":"$ServerIpAddr.Port,'IRC' );

	// Bind the local port.
	if( BindPort() == 0 )
	{
		if(SystemPage != None)
		{
			SystemPage.SystemText( ErrorBindingText );
			CloseMe();
		}
		return;
	}

	Open( ServerIpAddr );
}

event Closed()
{
	CloseMe();
}

// Host resolution failue.
function ResolveFailed()
{
	if(SystemPage != None)
	{
		SystemPage.SystemText(ResolveFailedText);
		CloseMe();
	}
}

event Timer()
{
	if(SystemPage != None)
	{
		SystemPage.SystemText( TimeOutError );
		CloseMe();

        log("Failed to resolve "$ServerAddress,'IRC');
	}
	return;
}

event Opened()
{
	SetTimer(0, False);
	if(SystemPage != None)
		SystemPage.SystemText(ConnectedText);
	Enable('Tick');
	GotoState('LoggingIn');
}

function Tick(float DeltaTime)
{
	local string Line;

	DoBufferQueueIO();
	if(ReadBufferedLine(Line))
		ProcessInput(Line);

    Super.Tick(DeltaTime);

	if( GetStateName() == 'LoggedIn' )
	{
		// Every 5 seconds, keep the IRC client away string up to date.
		SinceLastLevCheck += DeltaTime;
		if(SinceLastLevCheck > 5)
		{
			if(SystemPage != None)
				SystemPage.UpdateAway();

			SinceLastLevCheck = 0;
		}
	}
}

function SendCommandText(string Cmd)
{
	local int i;
	local string Text, Temp;

	// Add colons for commands: PRIVMSG, QUIT, KILL, KICK, NOTICE
	Divide(Cmd, " ", Cmd, Text);
	ReplaceCommandAlias(Cmd);

//	log("IRCLink::SendCommandText() Cmd:"$Cmd,'IRC');
//	log("IRCLink::SendCommandText() Text:"$Text, 'IRC');
	switch(Cmd)
	{

	// CMD nick :message
	case "PRIVMSG":
	case "NOTICE":
	case "KILL":

		if ( Text == "" )
			break;

		Text = ChopLeft(Text);

		if ( Divide(Text, " ", Temp, Text) )
		{
			Temp @= ":" $ ChopLeft(Text);
			Text = Temp;
		}

		break;

	// disable CTCP
	case "CTCP":
		break;

	// CMD #channel nick :message
	case "KICK":

		if ( Text == "" )
			break;

		Text = ChopLeft(Text);

		if ( Divide(Text, " ", Temp, Text) )
		{
			i = InStr(Text, " ");
			if (i != -1)
				Temp @= ":"$Text;	// adding reason for kick

			else if ( Text != "" )
				Temp @= Text;

			Text = Temp;
		}

		Text = SystemPage.GetCurrentChannelName() @ Text;	// adding channel name
		break;

	// CMD :message
	case "QUIT":
		if (Text != "")
			Text = " :"$ChopLeft(Text);
		break;

	case "JOIN":
		JoinChannel(Text);
		return;

	case "PART":
		if ( Text == "" )
			Text = SystemPage.GetCurrentChannelName();

		PartChannel(Text);
		return;
	}

	if ( Cmd != "" )
		SendBufferedData( Cmd $ Eval(Text != "", " " $ Text, "") $ CRLF );
}

function ReplaceCommandAlias(out string Text)
{
	local int i;

	for (i = 0; i < Shortcuts.Length; i++)
	{
		if (Shortcuts[i].AliasText ~= Text)
		{
			Text = Shortcuts[i].RealCommand;
			return;
		}
	}
}

function SendBufferedData(string Text)
{
	ReplaceText(Text, "$*", "");
	ReplaceText(Text, "#*", "");

//	log("IRCLink::SendingBufferedData >"$ Text );
	Super.SendBufferedData(Text);
}

function SendChannelText(string Channel, string Text)
{
	if ( Channel == "" )
		Channel = SystemPage.GetCurrentChannelName();

	SendBufferedData("PRIVMSG "$Channel$" :"$Text$CRLF);
}

function SendChannelAction(string Channel, string Text)
{
	if ( Channel == "" )
		Channel = SystemPage.GetCurrentChannelName();

	if (Left(Channel,1) != "#")
		Channel = "#" $ Channel;

	SendBufferedData("PRIVMSG "$Channel$" :"$Chr(1)$"ACTION "$Text$Chr(1)$CRLF);
}

function ProcessInput(string Line)
{
	// Respond to PING
	if(Left(Line, 5) == "PING ")
		SendBufferedData("PONG "$Mid(Line, 5)$CRLF);
}

state LoggingIn
{
	function Timer()
	{
		SendBufferedData("NICK "$NickName$CRLF);
		SetTimer(1, false);
	}

	function ProcessInput(string Line)
	{
		local string Temp;

        log("LoggingIn: "$Line,'IRC');

		Temp = ParseDelimited(Line, " ", 2);

		if(ParseDelimited(Line, " ", 1)== "ERROR")
			SystemPage.SystemText(ChopLeft(ParseDelimited(Line, ":", 2, True)));

		if( Temp == "433" )
		{
			// Nick in use
			SystemPage.SystemText(ChopLeft(ParseDelimited(Line, " ", 3, True)));
			SetTimer(0, false);

			SystemPage.NotifyNickInUse();
		}
		else
		if( Temp=="432" )
		{
			// Invalid nick
			SystemPage.SystemText(ChopLeft(ParseDelimited(Line, " ", 3, True)));
			SetTimer(0, false);

			SystemPage.NotifyInvalidNick();
		}
		else
		if( SystemPage.IsDigit(Temp) )
		{
			SystemPage.SystemText(ChopLeft(ParseDelimited(Line, " ", 3, True)));
			SetTimer(0, False);
			GotoState('LoggedIn');
		}

		Global.ProcessInput(Line);
	}

	function SendCommandText(string Text)
	{
		if(ParseDelimited(Text, " ", 1) ~= "NICK")
		{
			SystemPage.ChangedNick(NickName, Chop(ParseDelimited(Text, " ", 2)));
		}
		Global.SendCommandText(Text);
	}

Begin:
	if (SystemPage.Link != None && SystemPage.Link != Self)
	{
		SystemPage.CloseLink(SystemPage.Link, True);
		SystemPage.Link = Self;
	}

	else if (SystemPage.Link == None)
		SystemPage.Link = Self;

	SendBufferedData("USER "$UserIdent$" localhost "$ServerAddress$" :"$FullName$CRLF);
	SendBufferedData("NICK "$NickName$CRLF);
//	SetTimer(1, false);
}

state LoggedIn
{
	function ProcessInput(string Line)
	{
		local string Temp, Temp2, Temp3;
		local bool bAddModifier;
		local int i;
		local string Command;

		Global.ProcessInput(Line);

		if( SystemPage == None )
		    return;

//        log("LoggedIn::ProcessInput >"$Line,'IRC');

		Command = ParseDelimited(Line, " ", 2);

		if(ParseDelimited(Line, " ", 1) == "ERROR")
		{
			SystemPage.SystemText(ChopLeft(ParseDelimited(Line, ":", 2, True)));
		}

		switch ( Command )
		{
		case "JOIN":
			Temp = ParseDelimited(Line, ":!", 2);
			//Log("JOIN:"$Line$" Temp:"$Temp$" NickName:"$NickName);
			if(Temp ~= NickName)
				Temp = "";
			SystemPage.JoinedChannel(Chop(ParseDelimited(Line, " ", 3)), Temp);
			break;

		case "PART":
			Temp = ParseDelimited(Line, ":!", 2);
			if(Temp ~= NickName)
				Temp = "";
			SystemPage.PartedChannel(Chop(ParseDelimited(Line, " ", 3)), Temp);
			break;

		case "NICK":
			SystemPage.ChangedNick(ParseDelimited(Line, ":!", 2), Chop(ParseDelimited(Line, " ", 3)));
			break;

		case "QUIT":
			SystemPage.UserQuit(ParseDelimited(Line, ":!", 2), ChopLeft(ParseDelimited(Line, " ", 3, True)));
			break;

		case "353":	// NAMES
			Temp2 = ParseDelimited(Line, "#", 2);
			Temp2 = ParseDelimited(Temp2, " :", 1);

			Temp = ParseDelimited(Line, ":", 3, True);
			while(Temp != "")
			{
				// Nickname
				Temp3 = ParseDelimited(Temp, " ", 1);

				SystemPage.UserInChannel("#"$Temp2, RemoveNickPrefix(Temp3));

				if(Left(Temp3, 1) == "@")
					SystemPage.ChangeOp("#"$Temp2, RemoveNickPrefix(Temp3), True);
				else
				if(Left(Temp3, 1) == "%")
					SystemPage.ChangeHalfOp("#"$Temp2, RemoveNickPrefix(Temp3), True);
				else
				if(Left(Temp3, 1) == "+")
					SystemPage.ChangeVoice("#"$Temp2, RemoveNickPrefix(Temp3), True);

				Temp = ParseDelimited(Temp, " ", 2, True);
			}
			break;

		case "333":	// Channel formed info
		case "366":	// End of NAMES
		case "331":	// RPL_NOTOPIC
			break;

		case "332": // RPL_TOPIC
			Temp = Chop(ParseDelimited(Line, " ", 4));
			Temp2 = Chop(ParseDelimited(Line, " ", 5, True));

			SystemPage.ChangeTopic(Temp, Temp2);
			break;

		case "341":   // RPL_INVITING
			break;

		case "301":   // RPL_AWAY
			SystemPage.PrintAwayMessage(Chop(ParseDelimited(Line, " ", 4)), ChopLeft(ParseDelimited(Line, ":", 3, True)));
			break;

		case "NOTICE":
			Temp = ParseDelimited(Line, ": ", 2);
			Temp2 = ParseDelimited(Line, ":! ", 2);

			if(InStr(Temp, "!") != -1 && InStr(Temp2, ".") == -1)
			{
				// it's a Nick.
				Temp = ChopLeft(ParseDelimited(Line, " ", 4, True));
				if(Asc(Left(Temp, 1)) == 1 && Asc(Right(Temp, 1)) == 1)
					SystemPage.CTCP("", Temp2, Mid(Temp, 1, Len(Temp) - 2));
				else
					SystemPage.UserNotice(Temp2, Temp);
			}
			else
				SystemPage.SystemText(ChopLeft(ParseDelimited(Line, " ", 4, True)));

			break;

		case "MODE":
			// channel mode
			Temp = Chop(ParseDelimited(Line, " ", 4));
			// channel
			Temp3 = Chop(ParseDelimited(Line, " ", 3));
			i = 5;
			bAddModifier = True;
			while(Temp != "")
			{
				Temp2 = Left(Temp, 1);
				if(Temp2 == "+")
					bAddModifier = True;
				if(Temp2 == "-")
					bAddModifier = False;

				if(Temp2 == "o")
				{
					SystemPage.ChangeOp(Temp3, Chop(ParseDelimited(Line, " ", i)), bAddModifier);
					i++;
				}

				if(Temp2 == "h")
				{
					SystemPage.ChangeHalfOp(Temp3, Chop(ParseDelimited(Line, " ", i)), bAddModifier);
					i++;
				}

				if(Temp2 == "v")
				{
					SystemPage.ChangeVoice(Temp3, Chop(ParseDelimited(Line, " ", i)), bAddModifier);
					i++;
				}

				Temp = Mid(Temp, 1);
			}

			SystemPage.ChangeMode(Temp3, ParseDelimited(Line, ":!", 2), ChopLeft(ParseDelimited(Line, " ", 4, True)));

			break;

		case "KICK":
			// FIXME: handle multiple kicks in a single message
			SystemPage.KickUser(Chop(ParseDelimited(Line, " ", 3)), Chop(ParseDelimited(Line, " ", 4)), ParseDelimited(Line, ":!", 2), ChopLeft(ParseDelimited(Line, ":", 3, True)));
			break;

		case "INVITE":
			SystemPage.SystemText(ParseDelimited(Line, ":!", 2)@InviteString@ParseDelimited(Line, ":", 3));
			break;

		case "PRIVMSG":
			Temp = Chop(ParseDelimited(Line, " ", 3));
			Temp2 = ChopLeft(ParseDelimited(Line, " ", 4, True));

			if(Mid(Temp2, 1, 7) == "ACTION " && Asc(Left(Temp2, 1))==1 && Asc(Right(Temp2, 1))==1)
			{
				Temp2 = Mid(Temp2, 8);
				Temp2 = Left(Temp2, Len(Temp2) - 1);

				if(Temp != "" && InStr("&#@", Left(Temp, 1)) != -1)
					SystemPage.ChannelAction(Temp, ParseDelimited(Line, ":!", 2), Temp2);
				else
					SystemPage.PrivateAction(ParseDelimited(Line, ":!", 2), Temp2);
			}
			else
			if(Asc(Left(Temp2, 1))==1 && Asc(Right(Temp2, 1))==1)
			{
				Temp2 = Mid(Temp2, 1, Len(Temp2) - 2);

				switch(Temp2)
				{
				case "VERSION":
					// if _RO_
					SendBufferedData("NOTICE "$ParseDelimited(Line, ":!", 2)$" :"$Chr(1)$"VERSION "$VersionString$Level.ROVersion$Chr(1)$CRLF);
					// else
					//SendBufferedData("NOTICE "$ParseDelimited(Line, ":!", 2)$" :"$Chr(1)$"VERSION "$VersionString$Level.EngineVersion$Chr(1)$CRLF);
					SystemPage.CTCP(Temp, ParseDelimited(Line, ":!", 2), Temp2);
					break;
				default:
					SystemPage.CTCP(Temp, ParseDelimited(Line, ":!", 2), Temp2);
					break;
				}
			}
			else
			{
				if(Temp != "" && InStr("&#@", Left(Temp, 1)) != -1)
					SystemPage.ChannelText(Temp, ParseDelimited(Line, ":!", 2), Temp2);
				else
					SystemPage.PrivateText(ParseDelimited(Line, ":!", 2), Temp2);
			}

			break;

		case "TOPIC":
			Temp = Chop(ParseDelimited(Line, " ", 3));
			Temp2 = ChopLeft(ParseDelimited(Line, " ", 4, True));

			SystemPage.ChangeTopic(Temp, Temp2);
			break;

		case "305":	// No longer away
		case "306": // Now marked as away
			break;


		case "475": // ERR_BADCHANNELKEY
			SystemPage.SystemText(ChopLeft(ParseDelimited(Line, " ", 4, True)));
   			SystemPage.NotifyChannelKey(Chop(ParseDelimited(ChopLeft(ParseDelimited(Line, " ", 4, True)), ":", 1)));
			break;

		default:
			if ( SystemPage.IsDigit(Command) )
				SystemPage.SystemText(ChopLeft(ParseDelimited(Line, " ", 4, True)));

			break;
		}
	}

Begin:
	if(DefaultChannel != "")
		JoinChannel(DefaultChannel);
}

function JoinChannel(string Channel)
{
    log("UT2K4IRCLink: JoinChannel: "$Channel,'IRC');
	if ( Channel == "" )
		Channel = SystemPage.GetCurrentChannelName();

	if(Left(Channel, 1) != "#")
		Channel = "#" $ Channel;

	SendBufferedData("JOIN "$Channel$CRLF);
}

function PartChannel(string Channel)
{
	if ( Channel == "" )
		Channel = SystemPage.GetCurrentChannelName();

	if(Left(Channel, 1) != "#")
		Channel = "#" $ Channel;

	SendBufferedData("PART "$Channel$CRLF);
}

function SetNick(string NewNick)
{
	if ( NewNick == "" )
		return;

	if ( NewNick ~= "chanserv" )
		return;

	if ( NewNick ~= "q" )
		return;

	if ( NewNick ~= "nickserv" )
		return;

	SendCommandText("NICK "$NewNick);
}

function SetAway(string AwayText)
{
	SendBufferedData("AWAY :"$AwayText$CRLF);
}

function DestroyLink()
{
	SystemPage = None;
	SetTimer(0.0,False);

	if(IsConnected())
	{
		SendText("QUIT :"$DisconnectReason$CRLF);
		Close();
	}
	else
		Destroy();
}

defaultproperties
{
	InvalidAddressText="Invalid server address, aborting."
	ErrorBindingText="Error binding local port, aborting."
	TimeOutError="Timeout connecting to server."
	ResolveFailedText="Failed to resolve server address, aborting."
	ConnectedText="Connected."
	ConnectingToText="Connecting to"
	DisconnectReason="Disconnected"
	VersionString="Killing Floor IRC Client version "
	InviteString="invites you to join"

	Shortcuts(0)=(AliasText="MSG",RealCommand="PRIVMSG")
	Shortcuts(1)=(AliasText="LEAVE",RealCommand="PART")
	Shortcuts(2)=(AliasText="J",RealCommand="JOIN")
	Shortcuts(3)=(AliasText="P",RealCommand="PART")
	Shortcuts(4)=(AliasText="N",RealCommand="NICK")
}
