#include "YaRRIncludes.h"


/************************************** Polite Request ***************************************
** I have put a lot of time and effort into YaRR. If you want to use some of the source     **
** please tell me. Anything used from YaRR must be open source, as is scripts.dll.          **
***************************************** Thank you *****************************************/


char IRC::Buffer[1024];
int IRC::Position = 0;
clock_t IRC::Activity = 0;

void IRC::Load()
{
	DLOG;
	if(!YaRRGlobal::IRC)
	{
		YaRRGlobal::IRC = Alloc(ircdata);
	}
	const char *x = Connect(&YaRRGlobal::IRC, YaRRSettings::IRCd->Host, YaRRSettings::IRCd->Port, YaRRSettings::IRCd->Nick, YaRRSettings::IRCd->Backup_Nick, YaRRSettings::IRCd->NSPassword, YaRRSettings::IRCd->ExtraCommands, YaRRCommands::IRCCallback);
	if(x)
	{
		printf("IRC: Couldn't connect \"%s\": %s\n", YaRRSettings::IRCd->Host, x);
	}
}

void IRC::Unload()
{
	DLOG;
	if(!YaRRGlobal::IRC)
	{
		return;
	}
	IterateStack(c, ircChannel *, (&YaRRGlobal::IRC->Channels))
	{
		IterateStack(u, User *, (&c->Users))
		{
			Dealloc(User, u);
		}
		Dealloc(ircChannel, c);
	}

	YaRRGlobal::IRC->Sock.Destroy();
	YaRRGlobal::IRC = 0;
	CDealloc(YaRRGlobal::IRC);
}

const char *IRC::Connect(ircdata **Data, const char *Host, int Port, const char *Nick, const char *Backup_Nick, const char *NSPassword, Stacker<char *> *EXTCommands, void (*Callback)(ircdata *, const char *))
{
	DLOG;

	if(!YaRRGlobal::IRC->Sock.Client(Host, Port))
	{
		return "Failed to connect";
	}
		
	if(!*Data)
	{
		*Data = Alloc(ircdata);
	}
	strcpy((*Data)->Host, Host);
	strcpy((*Data)->NSPassword, NSPassword);
	strcpy((*Data)->Nick, Nick);
	strcpy((*Data)->Backup_Nick, Backup_Nick);

	CopyStack((&(*Data)->EXTCommands), EXTCommands, char *);
	(*Data)->Port = Port;
	(*Data)->Callback = Callback;
	(*Data)->Ghost = 0;
	(*Data)->Ping = 1;

	Send("NICK %s\n", Nick);
	Send("USER %s 0 * :YaRR-%s\n", YaRRGlobal::IRC->Nick, YARR_VERSION);
	return 0;
}

void IRC::Send(const char *Packet, ...)
{
	DLOG;
	if(!YaRRGlobal::IRC)
	{
		return;
	}
	char buffer[2048];
	va_list va;
	_crt_va_start(va, Packet);
	vsnprintf(buffer, 2048, Packet, va);
	va_end(va);

	const char *x = YaRRFunctions::Replace((char *)YaRRFunctions::strdup2(buffer), ":colour:", "\x03");
	YaRRGlobal::IRC->Sock.SendData(x, strlen(x)); 
	CDealloc(x);
}

void IRC::SendC(const char *Channel, const char *Packet, ...)
{
	DLOG;
	if(!YaRRGlobal::IRC)
	{
		return;
	}
	char buffer[2048];
	va_list va;
	_crt_va_start(va, Packet);
	vsnprintf(buffer, 2048, Packet, va);
	va_end(va);

	char *temp = (char *)CAlloc(strlen(buffer)+256);
	sprintf(temp, "PRIVMSG %s :%s\n", Channel, buffer);
	YaRRFunctions::Replace((char *)temp, ":colour:", "\x03");
	YaRRGlobal::IRC->Sock.SendData(temp, strlen(temp));
	CDealloc((void *)temp);
}

void __stdcall IRC::SendC(char Type, const char *Message, ...)
{
	DLOG;
	if(!YaRRGlobal::IRC)
	{
		return;
	}
	char buffer[2048];
	va_list va;
	_crt_va_start(va, Message);
	vsnprintf(buffer, 2048, Message, va);
	va_end(va);

	const char *IRC_COLOUR = YaRRFunctions::Replace((char *)YaRRFunctions::strdup2(buffer), ":colour:", "\x03");

	ircChannel *c;		
	YaRRGlobal::IRC->Channels.Reset();
	while(YaRRGlobal::IRC->Channels.Iterate(&c))
	{
		if(tolower(Type) == 'p')
		{
			if(tolower(YaRRSettings::GetChannelType(c->Name)) == 'p')
			{
				char tmp[2048];
				sprintf(tmp, "PRIVMSG %s :%s\n", c->Name, IRC_COLOUR);
				YaRRGlobal::IRC->Sock.SendData(tmp, strlen(tmp));
			}
		}
		else if(tolower(Type) == 'a')
		{
			if(tolower(YaRRSettings::GetChannelType(c->Name)) == 'a')
			{
				char tmp[2048];
				sprintf(tmp, "PRIVMSG %s :%s\n", c->Name, IRC_COLOUR);
				YaRRGlobal::IRC->Sock.SendData(tmp, strlen(tmp));
			}
		}
		else if(tolower(Type) == 'b')
		{
			char tmp[2048];
			sprintf(tmp, "PRIVMSG %s :%s\n", c->Name, IRC_COLOUR);
			YaRRGlobal::IRC->Sock.SendData(tmp, strlen(tmp));
		}
	}

	CDealloc(IRC_COLOUR);	
}


void IRC::SendALL(const char *Packet, ...)
{
	DLOG;
	if(!YaRRGlobal::IRC)
	{
		return;
	}
	char buffer[2048];
	va_list va;
	_crt_va_start(va, Packet);
	vsnprintf(buffer, 2048, Packet, va);
	va_end(va);

	YaRRGlobal::IRC->Sock.SendData(buffer, strlen(buffer));
}


void IRC::Disconnect(const char *Message, ...)
{
	DLOG;
	if(!YaRRGlobal::IRC)
	{
		return;
	}
	char buffer[1024];
	va_list va;
	_crt_va_start(va, Message);
	vsnprintf(buffer+6, 1024, Message, va);
	va_end(va);

	memcpy((void *)buffer, "QUIT :", 6);

	YaRRGlobal::IRC->Sock.SendData(buffer, strlen(buffer));
}


void IRC::Disconnect()
{
	DLOG;
	IRC::Disconnect("");
}

void IRC::Think(GameObject *o)
{
	DLOG;
	if(!YaRRGlobal::IRC)
	{
		return;
	}

	if(!YaRRGlobal::IRC->Sock.Is_Connected())
	{
		Commands->Send_Custom_Event(o, o, 102, 102, 0.0);
		IRC::Position = 0;
		return;

	}

	if(YaRRGlobal::IRC->Ghost)
	{
		IRC::Send("PRIVMSG NickServ :GHOST %s %s\n", YaRRGlobal::IRC->Nick, YaRRGlobal::IRC->NSPassword);
		IRC::Send("NICK %s\n", YaRRGlobal::IRC->Nick);
		IRC::Send("USER %s 0 * :YaRR-%s\n", YaRRGlobal::IRC->Nick, YARR_VERSION);
		YaRRGlobal::IRC->Ghost = 0;
	}

	for(char c;;)
	{
		if(YaRRGlobal::IRC->Sock.RecviveData(&c, 1))
		{
			IRC::Activity = clock();
			if(c == 13 || c == 10)
			{
				IRC::Buffer[IRC::Position] = 0;
				IRC::Position = 0;
				YaRRGlobal::IRC->Callback(YaRRGlobal::IRC, IRC::Buffer);
				printf("\"%s\"\n", IRC::Buffer);
				memset(IRC::Buffer, 0, 1024);
				
			}
			else
			{
				IRC::Buffer[IRC::Position] = c;
				IRC::Position++;
			}
			
		}
		else
		{
			break;
		}
	}

	if(clock() - IRC::Activity > 60000)
	{
		if(!YaRRGlobal::IRC->Ping)
		{
			IRC::Send("PING :YaRR\n");
			YaRRGlobal::IRC->Ping = 1;
		}
	}
	if(clock() - IRC::Activity > 1200000)
	{
		YaRRGlobal::IRC->Sock.Destroy();
		IRC::Activity = clock();
	}
}

bool IRC::FindStatus(const char *Nick, const char *Channel, char *access)
{
	DLOG;
	if(!YaRRGlobal::IRC)
	{
		return 0;
	}
	IterateStack(c, ircChannel *, (&(YaRRGlobal::IRC->Channels)))
	{
		if(stricmp(c->Name, Channel) == 0)
		{
			c->Users.Reset();
			User *u;
			while(c->Users.Iterate(&u))
			{
				if(stricmp(u->Nick, Nick) == 0)
				{
					*access = u->Modes;
					return 1;
				}
				
			}
			
		}
	}
	return 0;
}


TCPSocket::TCPSocket()
{
	memset((void *)this, 0, sizeof(TCPSocket));
}

TCPSocket::TCPSocket(TCPSocket &_Socket)
{
	memcpy((void *)this, &_Socket, sizeof(TCPSocket));
}

TCPSocket::TCPSocket(const char *Host, int Port)
{
	memset((void *)this, 0, sizeof(TCPSocket));
	this->Client(Host, Port);
}

TCPSocket::TCPSocket(const char *IP, int Port, int Backlog)
{
	memset((void *)this, 0, sizeof(TCPSocket));
	this->Server(IP, Port, Backlog);
}

TCPSocket::TCPSocket(SOCKET &_Socket, int _Mode)
{
	memset((void *)this, 0, sizeof(TCPSocket));
	this->Socket = _Socket;
	this->Mode = _Mode;
	this->Connected = 1;
}

TCPSocket::~TCPSocket()
{
	shutdown(this->Socket, SD_BOTH);
	closesocket(this->Socket);
	memset((void *)this, 0, sizeof(TCPSocket));
}

bool TCPSocket::Client(const char *Host, int Port)
{
	hostent *he;
	if ((he = gethostbyname(Host)) == 0) 
	{
		return 0;
	}
	SOCKET s_ = socket(AF_INET,SOCK_STREAM,0);
	if (s_ == INVALID_SOCKET) 
	{
		return 0;
	}
	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons((u_short)Port);
	addr.sin_addr = *((in_addr *)he->h_addr);
	memset(&(addr.sin_zero), 0, 8); 
	if (connect(s_, (sockaddr *) &addr, sizeof(sockaddr))) 
	{
		return 0;
	}

	this->Socket = s_;
	this->Connected = 1;
	this->Mode = 0;
	this->Port = Port;
	strcpy_s(this->Host, 256, Host);
	return 1;
}

bool TCPSocket::Server(const char *IP, int Port, int Backlog)
{
	SOCKET server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (server == INVALID_SOCKET) 
	{
		return 0;
	}

	sockaddr_in service;
	service.sin_family = AF_INET;
	service.sin_addr.s_addr = inet_addr(IP == 0 ? "127.0.0.1" : IP);
	service.sin_port = htons((u_short)Port);

	if(bind(server, (SOCKADDR*) &service, sizeof(service)) == SOCKET_ERROR) 
	{
		return 0;
	}

	if (listen(server, Backlog) == SOCKET_ERROR) 
	{
		return 0;
	}
	
	this->Socket = server;
	this->Mode = 1;
	this->Connected = 1;
	return 1;
}

bool TCPSocket::Is_Connected()
{
	if(this->Mode == 1)
	{
		return 1;
	}

	fd_set fd;
	TIMEVAL tv;

	tv.tv_sec = 0;
	tv.tv_usec = 1000;

	FD_ZERO(&fd);
	FD_SET(this->Socket, &fd);

	if(select((int)this->Socket, 0, &fd, 0, &tv) == -1)
	{
		return 0;
	}

	if(FD_ISSET(this->Socket, &fd))
	{
		FD_CLR(this->Socket, &fd);
		return 1;
	}
	FD_CLR(this->Socket, &fd);

	return 0;
}

bool TCPSocket::Is_DataAvaliable()
{
	if(!this->Is_Connected())
	{
		return 0;
	}
	if(this->Mode != 0)
	{
		return 0;
	}
	
	fd_set fd;
	TIMEVAL tv;

	tv.tv_sec = 0;
	tv.tv_usec = 1000;

	FD_ZERO(&fd);
	FD_SET(this->Socket, &fd);

	if(select((int)this->Socket, &fd, 0, 0, &tv) == -1)
	{
		return 0;
	}

	if(FD_ISSET(this->Socket, &fd))
	{
		FD_CLR(this->Socket, &fd);
		return 1;
	}
	FD_CLR(this->Socket, &fd);
	return 0;
}

bool TCPSocket::Is_ConnectionWaiting()
{
	if(this->Mode != 1)
	{
		return 0;
	}
	
	fd_set fd;
	TIMEVAL tv;

	tv.tv_sec = 0;
	tv.tv_usec = 1000;

	FD_ZERO(&fd);
	FD_SET(this->Socket, &fd);

	if(select((int)this->Socket, &fd, 0, 0, &tv) == -1)
	{
		return 0;
	}

	if(FD_ISSET(this->Socket, &fd))
	{
		FD_CLR(this->Socket, &fd);
		return 1;
	}
	FD_CLR(this->Socket, &fd);
	return 0;
}

bool TCPSocket::RecviveData(char *Data, int Length)
{
	if(this->Is_DataAvaliable())
	{
		int ret = recv(this->Socket, Data, Length, 0);
		if(ret <= 0)
		{
			this->Destroy();
			return 0;
		}
	}
	else
	{
		return 0;
	}
	return 1;
}

bool TCPSocket::SendData(const char *Data, int Length)
{
	if(!this->Is_Connected())
	{
		return 0;
	}

	int Send_ = send(this->Socket, Data, Length, 0);
	if(Send_ != Length)
	{
		if(Send_ == -1)
		{
			return 0;
		}
		else
		{
			this->SendData(Data+Send_, Length-Send_);
		}
	}

	return 1;
}

bool TCPSocket::Destroy()
{
	shutdown(this->Socket, SD_BOTH);
	closesocket(this->Socket);

	memset((void *)this, 0, sizeof(TCPSocket));
	return 0;
}

bool TCPSocket::Accept(TCPSocket *NewConnection)
{
	if(!this->Is_ConnectionWaiting())
	{
		return 0;
	}

	sockaddr_in addr;
	int s_sa = sizeof(sockaddr);
	SOCKET client = accept(this->Socket, (sockaddr *)&addr, &s_sa);
	if(client == INVALID_SOCKET) 
	{
		return 0;
	}

	NewConnection->TCPSocket::TCPSocket(client, 0);
	return 1;
}