#include chat.js
#include cookies.js
#include copyright.jsx
#include emotes.jsx
#include flashii.js
#include messages.jsx
#include notify.js
#include reconnect.jsx
#include servers.js
#include settings.js
#include sockchat.js
#include sound.js
#include styles.js
#include title.jsx
#include txtrigs.js

var AmiContext = function(title, auth, loading) {
    var pub = {};

    const flashii = new Flashii(`${window.FII_URL}/api`);
    pub.flashii = flashii;

    var settings = new AmiSettings;
    pub.settings = settings;

    settings.define('style', 'string', '');

    settings.define('soundMute', 'boolean');
    settings.define('soundVolume', 'number', 0.8);
    settings.define('soundPack', 'string');

    settings.define('enableNotifs', 'boolean');
    settings.define('msgAutoScroll', 'boolean', true, true, true);
    settings.define('runJokeTriggers', 'boolean', true);
    settings.define('dumpPackets', 'boolean');

    settings.define('parseEmoticons', 'boolean', true);
    settings.define('parseLinks', 'boolean', true);

    settings.define('bbAutoEmbedV1', 'boolean', false);

    settings.define('bbParse', 'boolean', true);
    settings.define('bbParseColour', 'boolean', true);
    settings.define('bbParseImage', 'boolean', true);
    settings.define('bbParseVideo', 'boolean', true);
    settings.define('bbParseAudio', 'boolean', true);

    settings.define('bbPersistBold', 'boolean');
    settings.define('bbPersistItalics', 'boolean');
    settings.define('bbPersistUnderline', 'boolean');
    settings.define('bbPersistStrike', 'boolean');
    settings.define('bbPersistSjis', 'boolean');
    settings.define('bbPersistColour', 'string');

    settings.define('migrated', 'boolean', false);

    if(!settings.get('migrated')) {
        settings.set('migrated', true);

        var scBBEnable = JSON.parse(AmiCookies.getCookie('sockchat_bbenable') || null),
            scOpts = JSON.parse(AmiCookies.getCookie('sockchat_opts') || null),
            scPersist = JSON.parse(AmiCookies.getCookie('sockchat_persist') || null),
            scStyle = AmiCookies.getCookie('sockchat_style');

        if(typeof scStyle === 'string')
            settings.set('style', scStyle);

        if(scOpts) {
            if(typeof scOpts.bbcode === 'boolean')
                settings.set('bbParse', scOpts.bbcode);
            if(typeof scOpts.emotes === 'boolean')
                settings.set('parseEmoticons', scOpts.emotes);
            if(typeof scOpts.links === 'boolean')
                settings.set('parseLinks', scOpts.links);
            if(typeof scOpts.sound === 'boolean')
                settings.set('soundMute', !scOpts.sound);
            if(typeof scOpts.spack === 'string')
                settings.set('soundPack', scOpts.spack);
            if(typeof scOpts.volume === 'string')
                settings.set('soundVolume', parseFloat(scOpts.volume));
            else if(typeof scOpts.volume === 'number')
                settings.set('soundVolume', scOpts.volume);
        }

        if(scBBEnable) {
            if(typeof scBBEnable.color === 'boolean')
                settings.set('bbParseColour', scBBEnable.color);
            if(typeof scBBEnable.img === 'boolean')
                settings.set('bbParseImage', scBBEnable.img);
            if(typeof scBBEnable.video === 'boolean')
                settings.set('bbParseVideo', scBBEnable.video);
            if(typeof scBBEnable.audio === 'boolean')
                settings.set('bbParseAudio', scBBEnable.audio);
        }

        if(scPersist) {
            if(typeof scPersist.b === 'object')
                settings.set('bbPersistBold', !!scPersist.b.enable);
            if(typeof scPersist.i === 'object')
                settings.set('bbPersistItalics', !!scPersist.i.enable);
            if(typeof scPersist.u === 'object')
                settings.set('bbPersistUnderline', !!scPersist.u.enable);
            if(typeof scPersist.s === 'object')
                settings.set('bbPersistStrike', !!scPersist.s.enable);
            if(typeof scPersist.color === 'object')
                settings.set('bbPersistColour', scPersist.color.enable ? (scPersist.color.value || '').toString() : null);
        }

        AmiCookies.removeCookie('sockchat_bbenable');
        AmiCookies.removeCookie('sockchat_opts');
        AmiCookies.removeCookie('sockchat_persist');
        AmiCookies.removeCookie('sockchat_style');
    }

    var sound = new AmiSound;
    pub.sound = sound;

    settings.watch('soundVolume', function(value) {
        sound.setVolume(value);
    });
    settings.watch('soundPack', function(value) {
        sound.setPackName(value);
    });

    var textTriggers = new AmiTextTriggers;
    pub.textTriggers = textTriggers;
    settings.watch('runJokeTriggers', function(value) {
        if(!textTriggers.hasTriggers())
            futami.getJson('texttriggers')
                .success(textTriggers.addTriggers)
                .run();
    });

    var styles = new AmiStyles;
    pub.styles = styles;

    styles.register('beige', 'Beige');
    styles.register('black', 'Black', true);
    styles.register('blue', 'Blue');
    styles.register('cobalt', 'Cobalt');
    styles.register('halext', 'Halext');
    styles.register('legacy', 'Legacy');
    styles.register('lithium', 'Lithium');
    styles.register('mio', 'Mio');
    styles.register('misuzu', 'Misuzu');
    styles.register('nico', 'Nico');
    styles.register('oxygen', 'Oxygen');
    styles.register('sulfur', 'Sulfur');
    styles.register('techno', 'Techno');
    styles.register('white', 'White');
    styles.register('yuuno', 'Yuuno');

    settings.watch('style', function(value) {
        try {
            styles.apply(value);
        } catch(ex) {
            styles.setDefault();
        }
    });

    var notifications = new AmiNotify;
    pub.notifications = notifications;

    settings.watch('enableNotifs', function(value) {
        if(!value) {
            notifications.disable();
            return;
        }

        notifications.enable(function(state) {
            if(!state) settings.set('enableNotifs', false);
        });
    });

    var emoticons = new AmiEmoticons;
    pub.emoticons = emoticons;

    pub.servers = new AmiServers;
    pub.windowTitle = new AmiWindowTitle(title);

    var sockChat = new AmiSockChat(auth);
    pub.sockChat = sockChat;

    var chat = new AmiChat(pub, title, document.body);
    pub.chat = chat;

    settings.watch('parseEmoticons', function(value) {
        if(!value) {
            chat.emoticonList.hide();
            emoticons.clear();
            return;
        }

        flashii.v1.emotes({ fields: ['url', 'strings', 'min_rank'] })
            .success(emotes => {
                if(Array.isArray(emotes))
                    emoticons.load(emotes);
                chat.emoticonList.render(emoticons);
            }).run();
    });

    var reconnecter = new AmiReconnecter(chat);
    pub.reconnecter = reconnecter;

    pub.pageIsHidden = function() {
        if('visibilityState' in document)
            return document.visibilityState !== 'visible';
        if('hidden' in document)
            return document.hidden;
        return false;
    };

    sockChat.watch('conn:init', function(info) {
        console.log('conn:init', info);

        if(!info.wasConnected)
            loading.setTextRaw(AmiStrings.getMenuString('conn'));
    });
    sockChat.watch('conn:ready', function(info) {
        console.log('conn:ready', info);

        if(!info.wasConnected)
            loading.setTextRaw(AmiStrings.getMenuString('auth'));

        chat.statusIndicator.setGreen('Ready!');
        if(reconnecter.isActive())
            reconnecter.session(function(man) { man.success(); });
    });
    sockChat.watch('conn:lost', function(info) {
        console.log('conn:lost', info);

        if(info.wasConnected) {
            chat.statusIndicator.setRed('Connection with server has been lost.');
            if(!reconnecter.isActive())
                reconnecter.start(function() { sockChat.open(); });

            UI.AddMessage('rc', null, UI.ChatBot, info.msg, false, false);
        } else {
            var msg = {
                '_1000': 'The connection has been ended.',
                '_1001': 'Something went wrong on the server side.',
                '_1002': 'Your client sent broken data to the server.',
                '_1003': 'Your client sent data to the server that it doesn\'t understand.',
                '_1005': 'No additional information was provided.',
                '_1006': 'You lost connection unexpectedly!',
                '_1007': 'Your client sent broken data to the server.',
                '_1008': 'Your client did something the server did not agree with.',
                '_1009': 'Your client sent too much data to the server at once.',
                '_1011': 'Something went wrong on the server side.',
                '_1012': 'The server is restarting, reconnecting soon...',
                '_1013': 'You cannot connect to the server right now, try again later.',
                '_1015': 'Your client and the server could not establish a secure connection.',
            }['_' + info.code.toString()] || ('Something caused an unexpected connection loss, the error code was: ' + info.code.toString() + '.');

            loading.hideIndicator();
            loading.setTextRaw(AmiStrings.getMenuString('term') + '<br/><br/>' + msg);
        }
    });
    sockChat.watch('conn:error', function() {
        console.log('conn:error');

        if(reconnecter.isActive())
            reconnecter.session(function(man) { man.fail(); });
    });

    sockChat.watch('ping:send', function() {
        console.log('ping:send');

        chat.statusIndicator.setYellow('Pinging...');
    });
    sockChat.watch('ping:long', function() {
        console.log('ping:long');

        chat.statusIndicator.setRed('Server is taking longer than 2 seconds to respond.');
    });
    sockChat.watch('ping:recv', function(info) {
        console.log('ping:recv', info);

        chat.statusIndicator.setGreen('Server took ' + info.diff.toLocaleString() + 'ms to respond.');
    });

    sockChat.watch('session:start', function(info) {
        console.log('session:start', info);

        if(!info.wasConnected) {
            loading.hideIndicator();
            loading.setTextRaw('Welcome!');
            loading.close();
            loading = undefined;
        }

        UserContext.self = new User(info.user.id, info.user.name, info.user.colour, info.user.permsRaw);
        UserContext.self.channel = info.channel.name;
        UI.AddUser(UserContext.self, false);
        UI.RedrawUserList();
    });
    sockChat.watch('session:fail', function(info) {
        console.log('session:fail', info);

        var overlay = AmiStrings.getMenuString(info.session.reason);
        if(info.baka) {
            if(info.baka.perma)
                overlay += '<br/>' + AmiStrings.getMenuString('eot');
            else
                overlay += '<br/>' + info.baka.until.toLocaleString();
        }
        overlay += `<br/><br/><a href="${window.FII_URL}/_sockchat/login?legacy=1">${AmiStrings.getMenuString('back')}</a>`;

        loading.hideIndicator();
        loading.setTextRaw(overlay);
    });
    sockChat.watch('session:term', function(info) {
        console.log('session:term', info);

        var overlay = AmiStrings.getMenuString(info.baka.type);
        if(info.baka.until)
            overlay += '<br/>' + info.baka.until.toLocaleString();
        overlay += `<br/><br/><a href="${window.FII_URL}/_sockchat/login?legacy=1">${AmiStrings.getMenuString('back')}</a>`;

        if(loading === undefined)
            loading = new AmiLoadingOverlay(document.body);
        loading.setTextRaw(overlay);
    });

    sockChat.watch('user:add', function(info) {
        console.log('user:add', info);

        if(UserContext.self.id !== info.user.id)
            UI.AddUser(new User(info.user.id, info.user.name, info.user.colour, info.user.permsRaw, info.user.hidden));

        if(info.msg) {
            UI.AddMessage(info.msg.id, info.msg.time, UI.ChatBot, info.msg.text, true, false);
            sound.playEventSound('join');
        }
    });
    sockChat.watch('user:remove', function(info) {
        console.log('user:remove', info);

        if(info.msg) {
            var sounds = {
                'flood': ['flood', 'kick', 'leave'],
                'kick': ['kick', 'leave'],
                'timeout': ['timeout', 'leave'],
            };

            UI.AddMessage(info.msg.id, info.msg.time, UI.ChatBot, info.msg.text, true, false);
            ami.sound.playEventSound(info.leave.type in sounds ? sounds[info.leave.type] : 'leave');
        }

        UI.RemoveUser(info.user.id);
    });
    sockChat.watch('user:update', function(info) {
        console.log('user:update', info);

        if(UserContext.self.id === info.user.id) {
            UserContext.self.username = info.user.name;
            UserContext.self.color = info.user.colour;
            UserContext.self.permstr = info.user.permsRaw;
            UserContext.self.EvaluatePermString();
            UI.ModifyUser(UserContext.self);
        } else {
            UserContext.users[info.user.id].username = info.user.name;
            UserContext.users[info.user.id].color = info.user.colour;
            UserContext.users[info.user.id].permstr = info.user.permsRaw;
            UserContext.users[info.user.id].EvaluatePermString();
            UI.ModifyUser(UserContext.users[info.user.id]);
        }
    });
    sockChat.watch('user:clear', function() {
        console.log('user:clear');

        UserContext.users = {};
        UI.RedrawUserList();
    });

    sockChat.watch('chan:add', function(info) {
        console.log('chan:add', info);

        UI.AddChannel(info.channel.name, info.channel.hasPassword, info.channel.isTemporary, false);
    });
    sockChat.watch('chan:remove', function(info) {
        console.log('chan:remove', info);

        UI.RemoveChannel(info.channel.name);
    });
    sockChat.watch('chan:update', function(info) {
        console.log('chan:update', info);

        UI.ModifyChannel(info.channel.previousName, info.channel.name, info.channel.hasPassword, info.channel.isTemporary);
    });
    sockChat.watch('chan:clear', function() {
        console.log('chan:clear');

        UI.RedrawChannelList();
    });
    sockChat.watch('chan:focus', function(info) {
        console.log('chan:focus', info);

        UI.SwitchChannel(info.channel.name);
    });
    sockChat.watch('chan:join', function(info) {
        console.log('chan:join', info);

        if(UserContext.self.id !== info.user.id) {
            UI.AddUser(new User(info.user.id, info.user.name, info.user.colour, info.user.permsRaw));
            UI.AddMessage(info.msg.id, null, UI.ChatBot, info.msg.text, true, false);
            ami.sound.playEventSound('join');
        }
    });
    sockChat.watch('chan:leave', function(info) {
        console.log('chan:leave', info);

        if(UserContext.self.id !== info.user.id) {
            UI.AddMessage(info.msg.id, null, UI.ChatBot, info.msg.text, true, false);
            UI.RemoveUser(info.user.id);
            ami.sound.playEventSound('leave');
        }
    });

    sockChat.watch('msg:add', function(info) {
        console.log('msg:add', info);

        if(info.msg.silent !== undefined)
            UI.AddMessage(info.msg.id, info.msg.time, info.msg.isBot ? UI.ChatBot : new User(info.msg.sender.id, info.msg.sender.name, info.msg.sender.colour, info.msg.sender.permsRaw), info.msg.text, !info.msg.silent, !info.msg.silent, info.msg.flagsRaw);
        else if(info.msg.isBot)
            UI.AddMessage(info.msg.id, info.msg.time, UI.ChatBot, info.msg.text, true, true, info.msg.flagsRaw);
        else if(UserContext.self.id === info.msg.sender.id)
            UI.AddMessage(info.msg.id, info.msg.time, UserContext.self, info.msg.text, true, true, info.msg.flagsRaw);
        else
            UI.AddMessage(info.msg.id, info.msg.time, UserContext.users[info.msg.sender.id], info.msg.text, true, true, info.msg.flagsRaw);
    });
    sockChat.watch('msg:remove', function(info) {
        console.log('msg:remove', info);
    });
    sockChat.watch('msg:clear', function() {
        console.log('msg:clear');
    });

    return pub;
};