var net = require('net');

var IRCserver = require('./server');
var IRCuser = require('./user');
var IRCchannel = require('./channel');

var uplink = null;
var connection = null;
var recvData = '';
var users = [];
var servers = [];
var channels = [];
var me = null;
var events = {};

function addInternalEvents(){
	events.netSynced.unshift(function(){
		for(var i=0; i<users.length; i++){
			var user = users[i];
			if(user.uplink == me){
				uplink.introduceUser(user);
			}
		}
		for(var i=0; i<channels.length; i++){
			uplink.syncChannel(channels[i]);
		}
	});
}

function findServer(s){
	for(var i=0; i<servers.length; i++){
		srv = servers[i];
		if(srv.sid == s || srv.name == s) return srv;
	}
	return null;
}

function findUser(u){
	for(var i=0; i<users.length; i++){
		usr = users[i];
		if(usr.uid == u || usr.name == u) return usr;
	}
	return null;
}

function findChannel(c){
	for(var i=0; i<channels.length; i++){
		usr = channels[i];
		if(channels[i].name == c) return channels[i];
	}
	return null;
}

function removeServer(server){
	var length = servers.length;
	var done = false;
	do {
		for(var i=0; i<length; i++){
			if(servers.length != length) break;
			if(servers[i].uplink == server){
				removeServer(servers[i]); // remove connected servers recursively
			}
		}
		if(i == length) done = true;
	} while(!done);
	for(var i=users.length-1; i>=0; i--){
		if(users[i].uplink == server){
			quitUser(users[i]); // remove users connected to this server
		}
	}
	for(var i=servers.length-1; i>=0; i--){ // finally, remove the given server
		if(servers[i] == server){
			console.log('Removing server '+servers[i].name);
			events.doEvent('serverDelete', server);
			servers.splice(i, 1);
			delete server;
			break;
		}
	}
}

function getChannel(name){
	var lowerName = name.toLowerCase();
	var channel = null;
	for(var i=0; i<channels.length; i++){
		if(channels[i].name == lowerName){
			channel = channels[i];
			break;
		}
	}
	if(!channel){
		console.log('Creating channel '+name);
		channel = new IRCchannel;
		channel.setEvents(events);
		channel.name = name;
		channels.push(channel);
		events.doEvent('channelCreate', channel);
	}
	return channel;
}

function killUser(user){
	console.log('KILL');
	console.log(user);
// TODO
}

function newUser(nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink){ // nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink
	var user;
	if((user = findUser(uid)) || (user = findUser(nick))){
		console.log(user);
		throw 'User already exists';
	}
	console.log('Introducing user '+nick);
	user = new IRCuser;
	user.setEvents(events);
	user.introduce(nick, distance, TS, ident, host, uid, account, umodes, vhost, cloakedHost, ip, realname, uplink);
	users.push(user);
	events.doEvent('userCreate', user);
	return user;
}

function newServer(name, sid, desc, distance, uplink){
	if(findServer(sid) || findServer(name)){
		throw 'Server already exists';
	}
	console.log('Introducing server '+name+' (SID='+sid+', uplink="'+uplink.name+'")');
	server = new IRCserver;
	server.setEvents(events);
	server.introduce(name, sid, desc, distance, uplink);
	servers.push(server);
	events.doEvent('serverCreate', server);
	return server;
}

function ircSendData(tags, from, cmd, args){
	var data = makeTagsString(tags);
	if(from){
		data += ':' + from + ' ';
	}
	data += cmd;
	if(args) for(var i=0; i<args.length; i++){
		var arg = args[i];
		if(arg.indexOf(' ') >= 0){ // valid for last arg only
			arg = ':' + arg;
		}
		data += ' ' + arg;
	}
	console.log('<<< '+data);
	data += "\r\n";
	connection.write(data);
}

function ircDataReceived(data){
	data = data.toString('utf8');
	recvData += data;
	var buffer = '';
	for(var i=0; i<recvData.length; i++){
		var c = recvData.charAt(i);
		switch(c){
			default: buffer += c; break;
			case '\r': case '\n':
				if(buffer.length > 0)
					ircMessage(buffer);
				buffer = '';
				break;
		}				
	}
	recvData = buffer;
}

function makeTagsString(tags){
	return '';
	//TODO
}

function ircMessage(data){
	var msg = irc.parseLine(data);
	uplink.processMessage(msg);
}

function ircConnectionClosed(e){
	events.doEvent('uplinkLost', me);
	console.log('Closed: '+e);
	process.exit(1);
}

function processReplyTags(inputTags, newTags){
	return newTags;
	// TODO
}

function quitUser(user){
	console.log('Removing user '+user.name);
	for(var i=0; i<channels.length; i++){
		channels[i].removeUser(user);
	}
	for(var i=0; i<users.length; i++){
		if(users[i] == user){
			users.splice(i, 1);
			events.doEvent('userDelete', user);
			delete user;
			break;
		}
	}
}

function removeEmptyChannels(){
	for(var i=channels.length - 1; i >= 0; i--){
		if(channels[i].users.length == 0){
			storeChannelData(channels[i]);
			console.log('Removing channel '+channels[i].name);
			var channel = channels[i];
			channels.splice(i, 1);
			delete channel;
		}
	}
}

function storeChannelData(channel){
// TODO maybe move a level up
}

setInterval(removeEmptyChannels, 1000);

var irc = {
	'users': users,
	'channels': channels,
	'servers': servers,
	'getChannel': getChannel,
	'setConnection': function(host, port, protocol){
		uplink = require('./protocol/' + protocol);
		me = new IRCserver;
		me.setEvents(events);
		me.introduce('serwisy.pirc.pl', '11K', 'Serwisy', 0, null);
		servers.push(me);
		uplink.setHandlers({
			send: ircSendData,
			findServer: findServer,
			findUser: findUser,
			killUser: killUser,
			newServer: newServer,
			newUser: newUser,
			getChannel: getChannel,
			quitUser: quitUser,
			removeServer: removeServer,
			findChannel: findChannel
		});
		uplink.setSettings({
			ID: me.sid,
			password: 'myservicespassword',
			name: me.name,
			description: me.description,
			version: 'k4be-services',
			me: me,
			maxUsers: 0
		});
		addInternalEvents();
		uplink.setEvents(events);
		connection = new net.Socket();
		connection.connect(port, host, uplink.connected.bind(this));
		connection.on('data', ircDataReceived);
		connection.on('close', ircConnectionClosed);
	},
	'setEvents': function(newEvents){
		events = newEvents;
	},
	'makeUser': function(nick, ident, host, realname){
		var user = new IRCuser;
		user.setEvents(events);
		var uid = uplink.makeUid();
		user.introduce(nick, 0, Math.floor(new Date() / 1000).toString(10), ident, host, uid, null, '+SoB'/*TODO*/, null, null, null, realname, me);
		users.push(user);
		return user;
	},
	'messagedata': function() {
		this.text = '';
		this.args = [];
		this.tags = [];
		this.command = '';
		this.sender = {
			'nick': '',
			'ident': '',
			'host': '',
			'server': false,
			'user': false
		};
		this.time = new Date();
		this.reply = function(tags, cmd, args){
			var outTags = processReplyTags(this.tags, tags);
			ircSendData(outTags, this.sender.nick, cmd, args);
		}
		this.originalString = '';
	},
	'parseTags': function(tagsLine){
		var tags = [];
		var tagState = 'keyName';
		var keyValue;
		var keyName = '';
		for(var i = 0; i < tagsLine.length; i++){
			var cchar = tagsLine.charAt(i);
			switch(tagState){
				case 'keyName':
					switch(cchar){
						case '=':
							tagState = 'keyValue';
							keyValue = '';
							break;
						case ';':
							tags[keyName] = '';
							keyName = ''; // staying in tagStateKeyName
							break;
						default: keyName += cchar; break;
					}
					break;
				case 'keyValue':
					switch(cchar){
						case '\\': tagState = 'keyValueEscape'; break;
						case ';':
							tags[keyName] = keyValue;
							keyName = '';
							tagState = 'keyName';
							break;
						default: keyValue += cchar; break;
					}
					break;
				case 'keyValueEscape':
					switch(cchar){
						case ':': keyValue += ';'; break;
						case 's': keyValue += ' '; break;
						case 'r': keyValue += '\r'; break;
						case 'n': keyValue += '\n'; break;
						default: keyValue += cchar; break;
					}
					tagState = 'keyValue';
					break;
			}
		}
		if(keyName.length > 0) tags[keyName] = keyValue; // flush last tag
		return tags;
	},
	'parseLine': function(line){
		var ircmsg = new irc.messagedata();

		var line = line.trim();
		line.replace(/^\s+|\s+$/gm,'');
		
		if(line == ''){
			return;
		}
		
		ircmsg.originalString = line;
		
		var msglen = line.length;
	
		var pstate = 'start';
		var currArg = '';
		var tags = '';
		var haveText = false;
		
		console.log('>>> ' + line);

		for(var i = 0; i < msglen; i++){
			var cchar = line.charAt(i);
			switch(pstate){
				case 'start':
					switch(cchar){
						case '@': pstate = 'tags'; break;
						case ':': pstate = 'senderNick'; break;
						default:
							pstate = 'command';
							ircmsg.command += cchar;
							break;
					}
					break;
				case 'tags':
					switch(cchar){
						case ' ':
							pstate = 'start';
							ircmsg.tags = irc.parseTags(tags);
							break;
						default: tags += cchar; break;
					}
					break;
				case 'senderNick':
					switch(cchar){
						case '!': pstate = 'senderUser'; break;
						case '@': pstate = 'senderHost'; break;
						case ' ': pstate = 'command'; break;
						default: ircmsg.sender.nick += cchar; break;
					}
					break;
				case 'senderUser':
					switch(cchar){
						case '@': pstate = 'senderHost'; break;
						case ' ': pstate = 'command'; break;
						default: ircmsg.sender.ident += cchar; break;
					}
					break;
				case 'senderHost':
					switch(cchar){
						case ' ': pstate = 'command'; break;
						default: ircmsg.sender.host += cchar; break;
					}
					break;
				case 'command':
					switch(cchar){
						case ' ': pstate = 'args'; break;
						default: ircmsg.command += cchar; break;
					}
					break;
				case 'args':
					switch(cchar){
						case ' ':
							if(currArg != ''){
								ircmsg.args.push(currArg);
							}
							currArg = '';
							break;
						case ':':
							if(prevChar == ' '){
								pstate = 'message';
								haveText = true;
							} else {
								currArg += cchar;
							}
							break;
						default: currArg += cchar; break;
					}
					break;
				case 'message':
					ircmsg.text += cchar;
					break;
			}
			var prevChar = cchar;
		}
		if(pstate == 'args'){
			ircmsg.args.push(currArg);
		}
		
		ircmsg.sender = uplink.processSender(ircmsg.sender);
		if(!ircmsg.sender){
			return false;
		}
		
		if(!ircmsg.sender.server && !ircmsg.sender.user){
			if(ircmsg.sender.ident == '' && ircmsg.sender.host == '' && ircmsg.sender.nick.indexOf('.')!=-1){
				ircmsg.sender.server = true;
			} else {
				ircmsg.sender.user = true;
			}
		}

		if(!haveText){
			ircmsg.text = ircmsg.args[ircmsg.args.length-1]; // handling last argument as text if : is missing
		} else {
			ircmsg.args.push(ircmsg.text); // handling text as a last argument as required by the protocol
		}

// add u@h
/*		if(ircmsg.sender.user){
			if(ircmsg.sender.ident) users.getUser(ircmsg.sender.nick).setIdent(ircmsg.sender.ident);
			if(ircmsg.sender.host) users.getUser(ircmsg.sender.nick).setHost(ircmsg.sender.host);
		}*/

// process known tags
/*		if('time' in ircmsg.tags){
			ircmsg.time = parseISOString(ircmsg.tags['time']);
		}

		if('account' in ircmsg.tags){
			users.getUser(ircmsg.sender.nick).setAccount(ircmsg.tags['account']);
		}*/

//		console.log(ircmsg);

		return ircmsg;
	}
};

module.exports = irc;