<template>
    <div style="display:flex; flex-direction: column; height:100%; justify-content: center">
        <div class="card bg-light" style="width:400px;max-width:100%;margin:0 auto" v-if="!connected">
            <div class="alert alert-danger" v-show="error">{{error}}</div>
            <h3 class="card-header" style="margin-top:0;display:flex">
                {{l('title')}}
                <a href="#" @click.prevent="$refs['logsDialog'].show()" class="btn" style="flex:1;text-align:right">
                    <span class="fa fa-file-alt"></span> <span class="btn-text">{{l('logs.title')}}</span>
                </a>
            </h3>
            <div class="card-body">
                <h4 class="card-title">{{l('login.selectCharacter')}}</h4>
                <select v-model="selectedCharacter" class="form-control custom-select">
                    <option v-for="character in ownCharacters" :value="character">{{character}}</option>
                </select>
                <div style="text-align:right;margin-top:10px">
                    <button class="btn btn-primary" @click="connect" :disabled="connecting">
                        {{l(connecting ? 'login.connecting' : 'login.connect')}}
                    </button>
                </div>
            </div>
        </div>
        <chat v-else></chat>
        <modal :action="l('chat.disconnected.title')" :buttonText="l('action.cancel')" ref="reconnecting" @submit="cancelReconnect"
            :showCancel="false" buttonClass="btn-danger">
            <div class="alert alert-danger" v-show="error">{{error}}</div>
            {{l('chat.disconnected')}}
        </modal>
        <logs ref="logsDialog"></logs>
        <div v-if="version && !connected" style="position:absolute;bottom:0;right:0">{{version}}</div>
    </div>
</template>

<script lang="ts">
    import Vue from 'vue';
    import Component from 'vue-class-component';
    import {Prop} from 'vue-property-decorator';
    import Modal from '../components/Modal.vue';
    import Channels from '../fchat/channels';
    import Characters from '../fchat/characters';
    import {Keys} from '../keys';
    import ChatView from './ChatView.vue';
    import {errorToString, getKey} from './common';
    import Conversations from './conversations';
    import core from './core';
    import l from './localize';
    import Logs from './Logs.vue';

    type BBCodeNode = Node & {bbcodeTag?: string, bbcodeParam?: string, bbcodeHide?: boolean};

    function copyNode(str: string, node: BBCodeNode, range: Range, flags: {endFound?: true}): string {
        if(node === range.endContainer) flags.endFound = true;
        if(node.bbcodeTag !== undefined)
            str = `[${node.bbcodeTag}${node.bbcodeParam !== undefined ? `=${node.bbcodeParam}` : ''}]${str}[/${node.bbcodeTag}]`;
        if(node.nextSibling !== null && !flags.endFound) {
            if(node instanceof HTMLElement && getComputedStyle(node).display === 'block') str += '\r\n';
            str += scanNode(node.nextSibling!, range, flags);
        }
        if(node.parentElement === null) return str;
        return copyNode(str, node.parentNode!, range, flags);
    }

    function scanNode(node: BBCodeNode, range: Range, flags: {endFound?: true}, hide?: boolean): string {
        let str = '';
        hide = hide || node.bbcodeHide;
        if(node === range.endContainer) {
            if(node instanceof HTMLElement && node.children.length === 1 && node.firstElementChild instanceof HTMLImageElement)
                str += scanNode(node.firstElementChild, range, flags, hide);
            flags.endFound = true;
        }
        if(node.bbcodeTag !== undefined) str += `[${node.bbcodeTag}${node.bbcodeParam !== undefined ? `=${node.bbcodeParam}` : ''}]`;
        if(node instanceof Text) str += node === range.endContainer ? node.nodeValue!.substr(0, range.endOffset) : node.nodeValue;
        else if(node instanceof HTMLImageElement) str += node.alt;
        if(node.firstChild !== null && !flags.endFound) str += scanNode(node.firstChild, range, flags, hide);
        if(node.bbcodeTag !== undefined) str += `[/${node.bbcodeTag}]`;
        if(node instanceof HTMLElement && getComputedStyle(node).display === 'block') str += '\r\n';
        if(node.nextSibling !== null && !flags.endFound) str += scanNode(node.nextSibling, range, flags, hide);
        return hide ? '' : str;
    }

    @Component({
        components: {chat: ChatView, modal: Modal, logs: Logs}
    })
    export default class Chat extends Vue {
        @Prop({required: true})
        readonly ownCharacters!: string[];
        @Prop({required: true})
        readonly defaultCharacter!: string | undefined;
        selectedCharacter = this.defaultCharacter || this.ownCharacters[0]; //tslint:disable-line:strict-boolean-expressions
        @Prop()
        readonly version?: string;
        error = '';
        connecting = false;
        connected = false;
        l = l;
        copyPlain = false;

        mounted(): void {
            document.title = l('title', core.connection.character);
            document.addEventListener('copy', ((e: ClipboardEvent) => {
                if(this.copyPlain) {
                    this.copyPlain = false;
                    return;
                }
                const selection = document.getSelection();
                if(selection.isCollapsed) return;
                const range = selection.getRangeAt(0);
                const start = range.startContainer;
                let startValue = start.nodeValue !== null ?
                    start.nodeValue.substring(range.startOffset, start === range.endContainer ? range.endOffset : undefined) : '';
                if(start instanceof HTMLElement && start.children.length === 1 && start.firstElementChild instanceof HTMLImageElement)
                    startValue += scanNode(start.firstElementChild, range, {});
                e.clipboardData.setData('text/plain', copyNode(startValue, start, range, {}));
                e.preventDefault();
            }) as EventListener);
            window.addEventListener('keydown', (e) => {
                if(getKey(e) === Keys.KeyC && e.shiftKey && (e.ctrlKey || e.metaKey) && !e.altKey) {
                    this.copyPlain = true;
                    document.execCommand('copy');
                    e.preventDefault();
                }
            });
            core.register('characters', Characters(core.connection));
            core.register('channels', Channels(core.connection, core.characters));
            core.register('conversations', Conversations());
            core.connection.onEvent('closed', (isReconnect) => {
                if(isReconnect) (<Modal>this.$refs['reconnecting']).show(true);
                if(this.connected) core.notifications.playSound('logout');
                this.connected = false;
                this.connecting = false;
                document.title = l('title');
            });
            core.connection.onEvent('connecting', async() => {
                this.connecting = true;
                if(core.state.settings.notifications) await core.notifications.requestPermission();
            });
            core.connection.onEvent('connected', () => {
                (<Modal>this.$refs['reconnecting']).hide();
                this.error = '';
                this.connecting = false;
                this.connected = true;
                core.notifications.playSound('login');
                document.title = l('title.connected', core.connection.character);
            });
            core.watch(() => core.conversations.hasNew, (hasNew) => {
                document.title = (hasNew ? '💬 ' : '') + l(core.connection.isOpen ? 'title.connected' : 'title', core.connection.character);
            });
            core.connection.onError((e) => {
                this.error = errorToString(e);
                this.connecting = false;
            });
        }

        cancelReconnect(): void {
            core.connection.close();
            (<Modal>this.$refs['reconnecting']).hide();
        }

        connect(): void {
            this.connecting = true;
            core.connection.connect(this.selectedCharacter).catch((e: Error) => {
                if((<Error & {request?: object}>e).request !== undefined) {//catch axios network errors
                    this.error = l('login.connectError', e.message);
                    this.connecting = false;
                } else throw e;
            });
        }
    }
</script>