
const parseIrcCmd = require('./ircCmd.js').parse
    ,accountManager = require("../models/accounts.js").accountManager

    ,MultiChatManager = require("../multichatManager.js").MultiChatManager
    ,slackManager = require("../slackManager.js").SlackManager

    ,PrivateMessageRoom = require("../room.js").PrivateMessageRoom

    ,CONFIG = require("../../config.js");

const DEBUG = true;

const SERVER_CONFIG = {
    port: CONFIG.port,
    hostname: CONFIG.hostname,
    created: (new Date()).toString()
};

IrcConnection = module.exports.IrcConnection = function(sock) {
    var _this = this;

    this.account = null;
    this.nickname;
    this.username;
    this.hostname;
    this.servername;
    this.realname;

    this.polling = false;
    this.joinedChannels = {};

    this.pendingMsgs = [];

    this.pingInterval = 0;
    this.pingSent = 0;

    this.sock = sock;
    this.sock.on("data", (data) => {
        data.toString("utf-8").split(/\r?\n/g).forEach((str) => { _this.parse(str); });
    });
    this.sock.once("close", () => {
        console.info("[IRC] closed connection" +(this.account ? (" for user #" +this.account.id) : ""));
        if (_this.pingInterval)
            clearInterval(_this.pingInterval);
        _this.polling = false;
    });
}

IrcConnection.prototype.delayCacheExpiration = function() {
    var services = this.account.getServices(),
        now = Date.now();
    // update cache interval to avoid expiration
    for (var serviceId in services) {
        switch (services[serviceId].type) {
            case "Slack":
                slackManager.lazyGet(serviceId, services[serviceId].oauthParam, now);
            break;

            default:
                console.error("Unknown service type for ", services[serviceId], " with account #" +req.account.id);
        }
    }
};

IrcConnection.prototype.write = function(line) {
    if (DEBUG)
        console.log("[IRC] >>> " +line);
    this.sock.write(line +"\r\n");
};

IrcConnection.prototype.isAuthed = function() {
    return this.account && this.nickname && this.username;
};

IrcConnection.prototype.checkConnectionDone = function(account, nickname, username, hostname, servername, realname) {
    var authed = this.isAuthed();

    if (account)
        this.account = account;
    if (nickname)
        this.nickname = nickname;
    if (username)
        this.username = username;
    if (hostname)
        this.hostname = hostname;
    if (servername)
        this.servername = servername;
    if (realname)
        this.realname = realname;

    if (!authed && this.isAuthed()) {
        console.info("[IRC] Authed new user #" +this.account.id);
        this.write(":" +SERVER_CONFIG.hostname +" 001 " +this.nickname +" :Welcome");
        this.write(":" +SERVER_CONFIG.hostname +" 002 " +this.nickname +" :Your host is " +SERVER_CONFIG.hostname +"[" +this.sock.localAddress +"/" +SERVER_CONFIG.port +"], running version mimouchat");
        this.write(":" +SERVER_CONFIG.hostname +" 003 " +this.nickname +" :This server was created " +SERVER_CONFIG.created);
        this.write(":" +SERVER_CONFIG.hostname +" 004 " +this.nickname +" " +SERVER_CONFIG.hostname +" mimouchat-irc DOQRSZaghilopswz CFILMPQSbcefgijklmnopqrstvz bkloveqjfI");
        this.write(":" +SERVER_CONFIG.hostname +" 375 " +this.nickname +" :- Mimouchat Message of the Day -");
        this.write(":" +SERVER_CONFIG.hostname +" 372 " +this.nickname +" :- Welcome to mimouchat -");
        this.write(":" +SERVER_CONFIG.hostname +" 376 " +this.nickname +" :End of /MOTD command.");
        this.write(":" +this.nickname +" MODE " +this.nickname +" :+i");

        this.chatContext = new MultiChatManager();
        var services = this.account.getServices(),
            now = Date.now();
        for (var serviceId in services) {
            switch (services[serviceId].type) {
                case "Slack":
                    this.chatContext.push(slackManager.lazyGet(serviceId, services[serviceId].oauthParam, now));
                break;

                default:
                    console.error("Unknown service type for ", services[serviceId], " with account #" +req.account.id);
            }
        }
        this.pingInterval = setInterval(this.sendPing.bind(this), 60000);
        this.polling = true;
        this.poll();
        return true;
    }
    return false;
};

const NAMES_NICK_LIMIT = 3;

IrcConnection.prototype.sendPing = function() {
    if (this.pingSent) {
        this.sock.end();
    }
    this.pingSent = '' +Math.floor(Math.random() * 1000000) +1;
    this.write(':' +SERVER_CONFIG.hostname +' PING :' +this.pingSent);
};

IrcConnection.prototype.sendJoin = function(channel) {
    let res = this.joinedChannels[channel.id] = { topic: "", purpose: "" },
        chanCompat = this.chanCompat(channel.id);

    this.write(":" +this.nickname +" JOIN " +chanCompat);
    if (channel.members) {
        let chanMembers = [];

        channel.members.forEach((userId) => {
            if (!chanMembers.length || chanMembers[chanMembers.length -1].length >= NAMES_NICK_LIMIT)
                chanMembers.push([]);
            chanMembers[chanMembers.length -1].push(this.userCompat(userId));
        }, this);
        chanMembers.forEach((userNames) => {
            if (userNames.length) {
                this.write(":" +SERVER_CONFIG.hostname +" 353 " +this.nickname +' @ ' +chanCompat +" :" +userNames.join(' '));
            }
        });
        this.write(":" +SERVER_CONFIG.hostname +" 366 " +this.nickname +' ' +chanCompat +" :End of /NAMES list.");
    }
    return res;
};

IrcConnection.prototype.sendTopic = function(channel) {
    this.joinedChannels[channel.id].topic = channel.topic.value;
    this.write(":" +(channel.topic.creator ? this.chatContext.getUser(channel.topic.creator).getName() : SERVER_CONFIG.hostname) +" TOPIC " +this.chanCompat(channel.id) +" :" +channel.topic.value);
};

IrcConnection.prototype.sendPurpose = function(channel) {
    this.joinedChannels[channel.id].purpose = channel.purpose.value;
    //this.write(":" +this.nickname +" JOIN #" +channel.id);
};

IrcConnection.prototype.sendStaticUpdate = function(static) {
    for (var team in static) {
        if (static[team].channels)
            static[team].channels.forEach((channel) => {
                if (!channel.user) {
                    var jChannel = this.joinedChannels[channel.id];

                    if (!jChannel)
                        jChannel = this.sendJoin(channel);
                    if (channel.purpose && jChannel.purpose !== channel.purpose.value)
                        this.sendPurpose(channel);
                    if (jChannel.topic !== (channel.topic ? channel.topic.value : ""))
                        this.sendTopic(channel);
                }
            });
    }
};

IrcConnection.prototype.userCompat = function(userId, replaceSelf) {
    var ctx = this.chatContext.getUserContext(userId);
    if (!ctx) {
        return userId;
    }
    ctx = ctx.getChatContext();
    if (replaceSelf !== false && ctx.self && ctx.self.id === userId) {
        return this.nickname;
    }
    return ctx.users[userId].getName() +'@' +ctx.team.name;
};

IrcConnection.prototype.chanCompat = function(chanId) {
    var ctx = this.chatContext.getChannelContext(chanId);
    if (!ctx) {
        return '#' +chanId;
    }
    ctx = ctx.getChatContext();
    var chan = ctx.channels[chanId];
    if (chan instanceof PrivateMessageRoom)
        return this.userCompat(chan.user.id, false);
    return '#' +chan.name.replace(/\s/g, '') +'@' +ctx.team.name;
};

IrcConnection.prototype.writeMsg = function(msg, chanCompatId) {
    var pretext = ":" +(msg.user ? this.userCompat(msg.user) : msg.username) +" PRIVMSG " +chanCompatId +" :";
    if (msg.isMeMessage)
        pretext += String.fromCharCode(0x01) +"ACTION ";
    this.write(pretext +msg.text);
};

IrcConnection.prototype.writeNotice = function(msg) {
    this.write(":" +SERVER_CONFIG.hostname +" NOTICE " +SERVER_CONFIG.hostname +" :" +msg);
};

IrcConnection.prototype.sendLiveUpdate = function(live) {
    for (var chanId in live) {
        let chanCompatId = this.chanCompat(chanId, this);
        live[chanId].forEach((msg) => {
            if (msg.user && this.chatContext.isMe(msg.user)) {
                if (msg.pendingId) {
                    var pos = this.pendingMsgs.indexOf(msg.pendingId);
                    if (pos >= 0) {
                        this.pendingMsgs.splice(pos, 1);
                        return;
                    }
                }
            }
            this.writeMsg(msg, chanCompatId);
        }, this);
    }
};

IrcConnection.prototype.poll = function() {
    var now = Date.now();
    var _this = this;
    // FIXME add new services / contexts
    this.chatContext.poll(this.version || 0, now, null, false).then((data) => {
        if (_this.polling) {
            if (data.static)
                _this.sendStaticUpdate(data.static);
            if (data.live)
                _this.sendLiveUpdate(data.live);
            if (data.v)
                _this.version = data.v;
            _this.poll();
        }
    }).catch(() => {
        _this.poll();
    });
};

IrcConnection.prototype.parse = function(str) {
    var cmd = parseIrcCmd(str),
        _this = this;
    if (cmd) {
        if (DEBUG)
            console.log("[IRC] <<< " +str);
        if (cmd.command === "QUIT") {
            _this.sock.end();
        } else if (!this.isAuthed()) {
            if (cmd.command === "PASS") {
                accountManager.fromIrcPass(cmd.args[0], (account) => {
                    if (account) {
                        _this.checkConnectionDone(account);
                    } else {
                        console.error("[IRC] Invalid password");
                        _this.sock.end();
                    }
                });
            }
            else if (cmd.command === "NICK") {
                this.checkConnectionDone(null, cmd.args[0]);
            }
            else if (cmd.command === "USER") {
                this.checkConnectionDone(null, null, cmd.args[0], cmd.args[1], cmd.args[2], cmd.args[3]);
            }
        } else {
            switch (cmd.command) {
                case "PING":
                    this.write(":" +SERVER_CONFIG.hostname +" PONG :" +cmd.args[0]);
                break;
                case "PONG":
                    if (cmd.args[0] === this.pingSent) {
                        this.pingSent = 0;
                        this.delayCacheExpiration();
                    } else {
                        console.error("[IRC] wrong PONG response (sent " +this.pingSent +", got " +cmd.args[0] +')');
                        this.sock.end();
                    }
                break;
                case "PRIVMSG":
                    var msg = cmd.args[1],
                        target = cmd.args[0].split('@', 2),
                        sent = false;
                    if (target[0][0] === '#')
                        target[0] = target[0].substr(1);
                    ctxLoop: for (let i =0, nbCtx = this.chatContext.contexts.length; i < nbCtx; i++) {
                        let ctx = this.chatContext.contexts[i];
                        if (ctx.getChatContext().team && ctx.getChatContext().team.name === target[1]) {
                            for (let chanId in ctx.getChatContext().channels) {
                                let chan = ctx.getChatContext().channels[chanId];
                                if (chan.name === target[0] || (chan.user && chan.user.getName() === target[0])) {
                                    if (msg.charCodeAt(0) === 0x01 && msg.substr(1, 7) === "ACTION ") {
                                        if (msg.charCodeAt(msg.length -1) === 0x01)
                                            msg = msg.substr(8, msg.length -9);
                                        else
                                            msg = msg.substr(8);
                                        let msgId = ctx.sendMeMsg(chan, [ msg ]);
                                        this.pendingMsgs.push(msgId);
                                    } else {
                                        let msgId = ctx.sendMsg(chan, [ msg ]);
                                        this.pendingMsgs.push(msgId);
                                    }
                                    sent = true;
                                    break ctxLoop;
                                }
                            }
                        }
                    }
                    if (!sent)
                        this.write(":" +SERVER_CONFIG.hostname +" NOTICE * :No such user or team " +cmd.args[0]);
                break;

                case "WHOIS":
                    var target = cmd.args[0].split('@', 2);
                    ctxLoop: for (let i =0, nbCtx = this.chatContext.contexts.length; i < nbCtx; i++) {
                        let ctx = this.chatContext.contexts[i];
                        if (ctx.getChatContext().team && ctx.getChatContext().team.name === target[1]) {
                            for (let userId in ctx.getChatContext().users) {
                                let user = ctx.getChatContext().users[userId];
                                if (user.getName() === target[0]) {
                                    this.write(":" +SERVER_CONFIG.hostname +" 311 " +cmd.args[0] +" " + user.name.replace(/\s/g, '_') +' ' +SERVER_CONFIG.hostname +' :' +user.realName);
                                    this.write(":" +SERVER_CONFIG.hostname +" 312 " +cmd.args[0] +" " +SERVER_CONFIG.hostname +ctx.getChatContext().team.name.replace(/\s/g, '_') +' :' +user.realName);
                                    this.write(":" +SERVER_CONFIG.hostname +" 318 " +cmd.args[0] +" " +cmd.args[0] +' :End of /WHOIS list.');
                                    break ctxLoop;
                                }
                            }
                        }
                    }
                break;

                case "HISTO":
                case "HISTORY":
                    if (cmd.args[0].toUpperCase() === "HELP") {
                        this.writeNotice("Usage: /histo #channel [count=50]");
                    } else {
                        var target = cmd.args[0].split('@', 2),
                            found = false;

                        if (target[0][0] === '#')
                            target[0] = target[0].substr(1);
                        ctxLoop: for (let i =0, nbCtx = this.chatContext.contexts.length; i < nbCtx; i++) {
                            let ctx = this.chatContext.contexts[i];
                            if (ctx.getChatContext().team && ctx.getChatContext().team.name === target[1]) {
                                for (let chanId in ctx.getChatContext().channels) {
                                    let chan = ctx.getChatContext().channels[chanId];
                                    if (chan.name === target[0] || (chan.user && chan.user.getName() === target[0])) {
                                        found = true;
                                        ctx.fetchHistory(chan, (msgs) => {
                                            msgs.sort((a, b) => {
                                                return a.ts -b.ts;
                                            });
                                            msgs.forEach((msg) => {
                                                msg.user = msg.user || msg.userId;
                                                this.writeMsg(msg, cmd.args[0]);
                                            });
                                        }, Math.max(5, Math.min(150, cmd.args[1] || 50)));
                                        break ctxLoop;
                                    }
                                }
                            }
                        }
                        if (!found)
                            this.writeNotice("Chan not found: " +cmd.args[0]);
                    }
                break;

                default:
                    if (DEBUG)
                        console.log(cmd);
                break;
            }
        }
    }
};

