#include animate.js
#include channels.js
#include messages.js
#include parsing.js
#include users.js
#include ui/baka.jsx
#include ui/emotes.js
#include ui/markup.js
#include ui/menus.js
#include ui/messages.jsx

const MamiSockChatHandlers = function(ctx, client, setLoadingOverlay, sockChatReconnect, pingIndicator, pingToggle) {
    if(typeof ctx !== 'object' || ctx === null)
        throw 'ctx must be an non-null object';
    if(typeof client !== 'object' || client === null)
        throw 'client must be an non-null object';
    if(typeof setLoadingOverlay !== 'function')
        throw 'setLoadingOverlay must be a function';
    if(typeof sockChatReconnect !== 'function')
        throw 'sockChatReconnect must be a function';
    if(typeof pingIndicator !== 'object' || pingIndicator === null)
        throw 'pingIndicator must be an non-null object';
    if(!(pingToggle instanceof Element))
        throw 'pingToggle must be an instance of Element';

    const handlers = {};
    let dumpEvents = false;

    handlers['conn:open'] = ev => {
        if(dumpEvents) console.log('conn:open', ev.detail);

        setLoadingOverlay('spinner', 'Connecting...', 'Authenticating...', true);
    };

    let legacyUmiConnectFired = false;
    handlers['conn:ready'] = ev => {
        if(dumpEvents) console.log('conn:ready');

        client.authenticate().then(() => {
            if(!legacyUmiConnectFired) {
                legacyUmiConnectFired = true;
                ctx.globalEvents.dispatch('umi:connect');
            }
        });
    };

    handlers['conn:lost'] = ev => {
        if(dumpEvents) console.log('conn:lost', ev.detail);

        if(ctx.conMan.isActive || ctx.isUnloading || ev.detail.wasKicked)
            return;

        sockChatRestarting = ev.detail.isRestarting;
        sockChatReconnect();
    };


    handlers['ping:send'] = () => {
        if(dumpEvents) console.log('ping:send');
    };
    handlers['ping:long'] = ev => {
        if(dumpEvents) console.log('ping:long', ev.detail);

        sockChatReconnect();
    };
    handlers['ping:recv'] = ev => {
        if(dumpEvents) console.log('ping:recv', ev.detail);

        let strength = 3;
        if(ev.detail.diff >= 800) --strength;
        if(ev.detail.diff >= 400) --strength;
        if(ev.detail.diff >= 200) --strength;

        pingToggle.title = `${ev.detail.diff.toLocaleString()}ms`;
        pingIndicator.setStrength(strength);
    };


    handlers['session:start'] = ev => {
        if(dumpEvents) console.log('session:start', ev.detail);

        sockChatRestarting = false;

        const userInfo = new MamiUserInfo(
            ev.detail.user.id,
            ev.detail.user.name,
            ev.detail.user.colour,
            new MamiUserStatusInfo(ev.detail.user.status.isAway, ev.detail.user.status.message),
            new MamiUserPermsInfo(
                ev.detail.user.perms.rank, ev.detail.user.perms.kick,
                ev.detail.user.perms.nick, ev.detail.user.perms.chan,
            ),
        );
        Umi.User.setCurrentUser(userInfo);
        Umi.Users.Add(userInfo);

        Umi.UI.Markup.Reset();
        Umi.UI.Emoticons.Init();
        Umi.Parsing.Init();

        if(ctx.views.count() > 1)
            ctx.views.pop(ctx => MamiAnimate({
                async: true,
                duration: 120,
                easing: 'inOutSine',
                start: () => {
                    ctx.toElem.style.zIndex = '100';
                    ctx.fromElem.style.pointerEvents = 'none';
                    ctx.fromElem.style.zIndex = '200';
                },
                update: t => {
                    ctx.fromElem.style.transform = `scale(${1 + (.25 * t)})`;
                    ctx.fromElem.style.opacity = 1 - (1 * t).toString();
                },
                end: () => {
                    ctx.toElem.style.zIndex = null;
                },
            }));
    };
    handlers['session:fail'] = ev => {
        if(dumpEvents) console.log('session:fail', ev.detail);

        if(ev.detail.baka !== undefined) {
            new MamiForceDisconnectNotice(ev.detail.baka).pushOn(ctx.views);
            return;
        }

        if(ev.detail.session.needsAuth) {
            location.assign(futami.get('login'));
            return;
        }

        setLoadingOverlay('cross', 'Failed!', ev.detail.session.outOfConnections ? 'Too many active connections.' : 'Unspecified reason.');
    };
    handlers['session:term'] = ev => {
        if(dumpEvents) console.log('session:term', ev.detail);

        new MamiForceDisconnectNotice(ev.detail.baka).pushOn(ctx.views);
    };


    handlers['user:add'] = ev => {
        if(dumpEvents) console.log('user:add', ev.detail);

        if(ev.detail.user.self)
            return;

        const userInfo = new MamiUserInfo(
            ev.detail.user.id,
            ev.detail.user.name,
            ev.detail.user.colour,
            new MamiUserStatusInfo(ev.detail.user.status.isAway, ev.detail.user.status.message),
            new MamiUserPermsInfo(
                ev.detail.user.perms.rank, ev.detail.user.perms.kick,
                ev.detail.user.perms.nick, ev.detail.user.perms.chan,
            ),
        );
        Umi.Users.Add(userInfo);

        if(ev.detail.msg !== undefined)
            Umi.Messages.Add(new Umi.Message(
                ev.detail.msg.id, ev.detail.msg.time, undefined, '', ev.detail.msg.channel, false,
                {
                    isError: false,
                    type: ev.detail.msg.botInfo.type,
                    args: ev.detail.msg.botInfo.args,
                    target: userInfo,
                }
            ));
    };
    handlers['user:remove'] = ev => {
        if(dumpEvents) console.log('user:remove', ev.detail);

        const userInfo = Umi.Users.Get(ev.detail.user.id);
        if(userInfo === null)
            return;

        if(ev.detail.msg !== undefined)
            Umi.Messages.Add(new Umi.Message(
                ev.detail.msg.id,
                ev.detail.msg.time,
                undefined,
                '',
                ev.detail.msg.channel,
                false,
                {
                    isError: false,
                    type: ev.detail.msg.botInfo.type,
                    args: ev.detail.msg.botInfo.args,
                    target: userInfo,
                },
            ));

        Umi.Users.Remove(userInfo);
    };
    handlers['user:update'] = ev => {
        if(dumpEvents) console.log('user:update', ev.detail);

        const userInfo = Umi.Users.Get(ev.detail.user.id);
        userInfo.name = ev.detail.user.name;
        userInfo.colour = ev.detail.user.colour;
        userInfo.avatarChangeTime = Date.now();
        userInfo.status = new MamiUserStatusInfo(ev.detail.user.status.isAway, ev.detail.user.status.message);
        userInfo.perms = new MamiUserPermsInfo(
            ev.detail.user.perms.rank, ev.detail.user.perms.kick,
            ev.detail.user.perms.nick, ev.detail.user.perms.chan,
        );
        Umi.Users.Update(userInfo.id, userInfo);
    };
    handlers['user:clear'] = () => {
        if(dumpEvents) console.log('user:clear');

        const self = Umi.User.getCurrentUser();
        Umi.Users.Clear();
        if(self !== undefined)
            Umi.Users.Add(self);
    };


    handlers['chan:add'] = ev => {
        if(dumpEvents) console.log('chan:add', ev.detail);

        Umi.Channels.Add(new MamiChannelInfo(
            ev.detail.channel.name,
            ev.detail.channel.hasPassword,
            ev.detail.channel.isTemporary,
        ));
    };
    handlers['chan:remove'] = ev => {
        if(dumpEvents) console.log('chan:remove', ev.detail);

        Umi.Channels.Remove(Umi.Channels.Get(ev.detail.channel.name));
    };
    handlers['chan:update'] = ev => {
        if(dumpEvents) console.log('chan:update', ev.detail);

        const chanInfo = Umi.Channels.Get(ev.detail.channel.previousName);
        chanInfo.name = ev.detail.channel.name;
        chanInfo.hasPassword = ev.detail.channel.hasPassword;
        chanInfo.isTemporary = ev.detail.channel.isTemporary;
        Umi.Channels.Update(ev.detail.channel.previousName, chanInfo);
    };
    handlers['chan:clear'] = () => {
        if(dumpEvents) console.log('chan:clear');

        Umi.Channels.Clear();
    };
    handlers['chan:focus'] = ev => {
        if(dumpEvents) console.log('chan:focus', ev.detail);

        Umi.Channels.Switch(Umi.Channels.Get(ev.detail.channel.name));
    };
    handlers['chan:join'] = ev => {
        if(dumpEvents) console.log('chan:join', ev.detail);

        const userInfo = new MamiUserInfo(
            ev.detail.user.id,
            ev.detail.user.name,
            ev.detail.user.colour,
            new MamiUserStatusInfo(ev.detail.user.status.isAway, ev.detail.user.status.message),
            new MamiUserPermsInfo(
                ev.detail.user.perms.rank, ev.detail.user.perms.kick,
                ev.detail.user.perms.nick, ev.detail.user.perms.chan,
            )
        );
        Umi.Users.Add(userInfo);

        if(ev.detail.msg !== undefined)
            Umi.Messages.Add(new Umi.Message(
                ev.detail.msg.id, null, undefined, '', ev.detail.msg.channel, false,
                {
                    isError: false,
                    type: leave.msg.botInfo.type,
                    args: [ userInfo.name ],
                    target: userInfo,
                },
            ));
    };
    handlers['chan:leave'] = ev => {
        if(dumpEvents) console.log('chan:leave', ev.detail);

        if(ev.detail.user.self)
            return;

        const userInfo = Umi.Users.Get(ev.detail.user.id);
        if(userInfo === null)
            return;

        if(ev.detail.msg !== undefined)
            Umi.Messages.Add(new Umi.Message(
                ev.detail.msg.id, null, undefined, '', ev.detail.msg.channel, false,
                {
                    isError: false,
                    type: ev.detail.msg.botInfo.type,
                    args: [ userInfo.name ],
                    target: userInfo,
                },
            ));

        Umi.Users.Remove(userInfo);
    };


    handlers['msg:add'] = ev => {
        if(dumpEvents) console.log('msg:add', ev.detail);

        const senderInfo = ev.detail.msg.sender;
        const userInfo = senderInfo.name === undefined
            ? Umi.Users.Get(senderInfo.id)
            : new MamiUserInfo(
                senderInfo.id,
                senderInfo.name,
                senderInfo.colour,
                new MamiUserStatusInfo(senderInfo.status.isAway, senderInfo.status.message),
                new MamiUserPermsInfo(
                    senderInfo.perms.rank, senderInfo.perms.kick,
                    senderInfo.perms.nick, senderInfo.perms.chan,
                )
            );

        // hack
        let channelName = ev.detail.msg.channel;
        if(channelName !== undefined && channelName.startsWith('@~')) {
            const chanUserInfo = Umi.Users.Get(channelName.substring(2));
            if(chanUserInfo !== null)
                channelName = `@${chanUserInfo.name}`;
        }

        // also hack
        if(ev.detail.msg.flags.isPM) {
            if(Umi.Channels.Get(channelName) === null)
                Umi.Channels.Add(new MamiChannelInfo(channelName, false, true, true));

            // this should be raised for other channels too, but that is not possible yet
            Umi.UI.Menus.Attention('channels');
        }

        Umi.Messages.Add(new Umi.Message(
            ev.detail.msg.id,
            ev.detail.msg.time,
            userInfo,
            ev.detail.msg.text,
            channelName,
            false,
            ev.detail.msg.botInfo,
            ev.detail.msg.flags.isAction,
            ev.detail.msg.silent,
        ));
    };
    handlers['msg:remove'] = ev => {
        if(dumpEvents) console.log('msg:remove', ev.detail);

        Umi.Messages.Remove(Umi.Messages.Get(ev.detail.msg.id));
    };
    handlers['msg:clear'] = () => {
        if(dumpEvents) console.log('msg:clear');

        Umi.UI.Messages.RemoveAll();
    };


    return {
        setDumpEvents: state => {
            dumpEvents = !!state;
        },
        register: () => {
            for(const name in handlers)
                client.watch(name, handlers[name]);
        },
        unregister: () => {
            for(const name in handlers)
                client.unwatch(name, handlers[name]);
        },
    };
};
