/**
 * Created by zh99998 on 16/9/2.
 */
import {ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewChild} from '@angular/core';
import {Headers, Http} from '@angular/http';
import {clipboard, remote, shell} from 'electron';
import * as fs from 'fs-extra';
import * as $ from 'jquery';
import * as path from 'path';
import 'rxjs/Rx';
import {ISubscription} from 'rxjs/Subscription';
import {App} from './app';
import {AppsService} from './apps.service';
import {LoginService} from './login.service';
import {SettingsService} from './settings.sevices';
import Timer = NodeJS.Timer;
import WillNavigateEvent = Electron.WillNavigateEvent;
import _ = require('lodash');

interface SystemConf {
    use_d3d: string;
    antialias: string;
    errorlog: string;
    nickname: string;
    gamename: string;
    lastdeck: string;
    textfont: string;
    numfont: string;
    serverport: string;
    lastip: string;
    lasthost: string;
    lastport: string;
    autopos: string;
    randompos: string;
    autochain: string;
    waitchain: string;
    mute_opponent: string;
    mute_spectators: string;
    hide_setname: string;
    hide_hint_button: string;
    control_mode: string;
    draw_field_spell: string;
    separate_clear_button: string;
    roompass: string;
}

interface Server {
    id: string;
    name?: string;
    url?: string;
    address: string;
    port: number;
    hidden?: boolean;
    custom?: boolean;
    replay?: boolean;
    windbot?: string[];
}

interface Room {
    id?: string;
    title?: string;
    server?: Server;
    'private'?: boolean;
    options: Options;
    arena?: string;
    users?: { username: string, position: number }[];
}

interface Options {
    mode: number;
    rule: number;
    start_lp: number;
    start_lp_tag: number;
    start_hand: number;
    draw_count: number;
    duel_rule: number;
    no_check_deck: boolean;
    no_shuffle_deck: boolean;
    lflist?: number;
    time_limit?: number;
    auto_death: boolean;
}

export interface Points {
    exp: number;
    exp_rank: number;
    pt: number;
    arena_rank: number;
    win: number;
    lose: number;
    draw: number;
    all: number;
    ratio: number;
}

interface YGOProDistroData {
    deckPath: string;
    replayPath: string;
    systemConf?: string;
    lastDeckFormat?: string;
}

interface YGOProData {
    ygopro: YGOProDistroData;
    servers: Server[];
}


let matching: ISubscription | undefined;
let matching_arena: string | undefined;
let match_started_at: Date;

@Component({
    moduleId: module.id,
    selector: 'ygopro',
    templateUrl: 'ygopro.component.html',
    styleUrls: ['ygopro.component.css'],
})
export class YGOProComponent implements OnInit, OnDestroy, OnChanges {
    @Input()
    app: App;

    async ngOnChanges(changes: SimpleChanges) {
        if (changes.app && changes.app.previousValue && changes.app.currentValue !== changes.app.previousValue) {
            await this.reloadInfo()
        }
    }

    @Output()
    points: EventEmitter<Points> = new EventEmitter();
    decks: string[] = [];
    replays: string[] = [];
    replays2: string[] = [];
    current_deck: string;
    system_conf: string;
    numfont: string[];
    textfont: string[];

    @ViewChild('bilibili')
    bilibili: ElementRef;

    @ViewChild('youtube')
    youtube: ElementRef;
    // points: Points;

    get windbot() {
        return this.currentServer.windbot;
    }

    servers: Server[];

    selectableServers: Server[];

    currentServer: Server;

    // tslint:disable-next-line:member-ordering
    rooms_loading = true;

    lastDeckFormat: RegExp;

    default_options: Options = {
        mode: 1,
        rule: this.settingsService.getLocale().startsWith('zh') ? 0 : 1,
        start_lp: 8000,
        start_lp_tag: 16000,
        start_hand: 5,
        draw_count: 1,
        duel_rule: 5,
        no_check_deck: false,
        no_shuffle_deck: false,
        lflist: 0,
        time_limit: 180,
        auto_death: false
    };

    room: Room = {title: this.loginService.user.username + '的房间', options: Object.assign({}, this.default_options)};

    rooms: Room[] = [];
    rooms_show: Room[];

    connections: WebSocket[] = [];
    replay_connections: WebSocket[] = [];
    replay_rooms: Room[] = [];
    replay_rooms_show: Room[];
    replay_rooms_filter = {
        athletic: true,
        entertain: true,
        single: true,
        match: true,
        tag: true,
        windbot: false,
        username: ''
    };

    matching: ISubscription | undefined;
    matching_arena: string | undefined;
    match_time: string;
    match_cancelable: boolean;
    match_interval: Timer | undefined;

    join_password: string;
    host_password = (this.loginService.user.external_id ^ 0x54321).toString();
    title_username_change_handler = _.debounce(this.title_username_change, 500);

    constructor(private http: Http, private appsService: AppsService, private loginService: LoginService,
                public settingsService: SettingsService, private ref: ChangeDetectorRef) {
        switch (process.platform) {
            // linux should have fonts set by default
            case 'linux':
                this.numfont = [
                    '/usr/share/fonts/truetype/DroidSansFallbackFull.ttf',
                    '/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc',
                    '/usr/share/fonts/google-noto-cjk/NotoSansCJK-Bold.ttc',
                    '/usr/share/fonts/noto-cjk/NotoSansCJK-Bold.ttc',
                ];
                this.textfont = [
                    '/usr/share/fonts/truetype/DroidSansFallbackFull.ttf',
                    '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc',
                    '/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc',
                    '/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc',
                ];
                break;
            case 'darwin':
                this.numfont = ['/System/Library/Fonts/SFNSTextCondensed-Bold.otf', '/System/Library/Fonts/Supplemental/Arial.ttf'];
                this.textfont = ['/System/Library/Fonts/PingFang.ttc'];
                break;
            case 'win32':
                this.numfont = [path.join(process.env['SystemRoot']!, 'Fonts', 'arialbd.ttf')];
                this.textfont = [
                    path.join(process.env['SystemRoot']!, 'Fonts', 'msyh.ttc'),
                    path.join(process.env['SystemRoot']!, 'Fonts', 'msyh.ttf'),
                    path.join(process.env['SystemRoot']!, 'Fonts', 'simsun.ttc')
                ];
                break;
        }

        if (matching) {
            this.matching = matching;
            this.matching_arena = matching_arena;
            this.refresh_match();
            this.match_interval = setInterval(() => {
                this.refresh_match();
            }, 1000);
        }
    }

    refresh_rooms() {
        this.rooms_show = this.rooms.filter((room) => room.server === this.currentServer);
    }

    refresh_replay_rooms() {
        this.replay_rooms_show = this.replay_rooms.filter((room) => {
            if (!room.arena && room.server && room.server !== this.currentServer) {
                return false;
            }
            return ((this.replay_rooms_filter.athletic && room.arena === 'athletic') ||
                (this.replay_rooms_filter.entertain && room.arena === 'entertain') ||
                (this.replay_rooms_filter.single && room.options.mode === 0 && !room.arena && !room.id!.startsWith('AI#')) ||
                (this.replay_rooms_filter.match && room.options.mode === 1 && !room.arena && !room.id!.startsWith('AI#')) ||
                (this.replay_rooms_filter.tag && room.options.mode === 2 && !room.arena && !room.id!.startsWith('AI#')) ||
                (this.replay_rooms_filter.windbot && room.id!.startsWith('AI#')));
        }).sort((a, b) => {
            // if (a.arena === 'athletic' && b.arena === 'athletic') {
            //     return a.dp - b.dp;
            // } else if (a.arena === 'entertain' && b.arena === 'entertain') {
            //     return a.exp - b.exp;
            // }
            let [a_priority, b_priority] = [a, b].map((room) => {
                if (room.arena === 'athletic') {
                    return 0;
                } else if (room.arena === 'entertain') {
                    return 1;
                } else if (room.id!.startsWith('AI#')) {
                    return 5;
                } else {
                    return room.options.mode + 2;
                }
            });
            return a_priority - b_priority;
        });
        if (this.replay_rooms_filter.username.length > 0) {
          this.handle_filter_username();
        }
    }
    
    handle_filter_username() {
      const filter_username = this.replay_rooms_filter.username.toLowerCase().trim();
      if (filter_username === '') return;
      let username_filter_rooms = [];
      this.replay_rooms_show.forEach(room => {
        const [user_a, user_b] = room.users;
        const user_a_name = (user_a && user_a.username.toLowerCase()) || '';
        const user_b_name = (user_b && user_b.username.toLowerCase()) || '';
        if (user_a_name !== '' || user_b_name !== '') {
          const targetStr = `${user_a_name}/${user_b_name}`;
          if (targetStr.includes(filter_username)) {
            username_filter_rooms.push(room);
          }
        }
      });
      this.replay_rooms_show = username_filter_rooms;
    }

    title_username_change(input_value: string) {
      if (input_value.length) {
        this.handle_filter_username();
      } else {
        this.refresh_replay_rooms();
      }
    }

    getYGOProData(app: App) {
        const ygoproData = <YGOProData>app.data;
        for (const child of this.appsService.findChildren(app)) {
            if (child.isYGOPro && child.isInstalled() && child.isReady()) {
                const childData = this.getYGOProData(child);
                _.mergeWith(ygoproData, childData, (objValue, srcValue) => {
                    if (_.isArray(objValue)) {
                        return objValue.concat(srcValue);
                    }
                });
            }
        }
        return ygoproData;
    }

    async reloadInfo() {
        const ygoproData = this.getYGOProData(this.app);
        const previousServer = this.currentServer;
        this.servers = ygoproData.servers;
        this.selectableServers = this.servers.filter(s => !s.hidden);
        if (!previousServer || !this.selectableServers.includes(previousServer)) { 
            this.currentServer = this.selectableServers[0];
        }
        if (ygoproData.ygopro.lastDeckFormat) {
            // console.log(`Deck format pattern: ${ygoproData.ygopro.lastDeckFormat}`)
            this.lastDeckFormat = new RegExp(ygoproData.ygopro.lastDeckFormat);
        } else {
            this.lastDeckFormat = undefined;
        }

        this.system_conf = this.app.systemConfPath;
        console.log(`Will load system conf file from ${this.system_conf}`);
        await this.refresh(true);
    }

    async ngOnInit() {
        let locale: string;
        if (this.settingsService.getLocale().startsWith('zh')) {
            locale = 'zh-CN';
        } else {
            locale = 'en-US';
        }

        await this.reloadInfo();

        let modal = $('#game-list-modal');

        modal.on('show.bs.modal', () => {
            this.rooms_loading = true;
            this.connections = this.servers.filter(server => server.custom).map((server) => {
                let url = new URL(server.url!);
                url['searchParams'].set('filter', 'waiting');
                let connection = new WebSocket(url.toString());
                connection.onclose = (event: CloseEvent) => {
                    this.rooms = this.rooms.filter(room => room.server !== server);
                    this.refresh_rooms();
                };
                connection.onerror = (event: ErrorEvent) => {
                    console.log('error', server.id, event);
                    this.rooms = this.rooms.filter(room => room.server !== server);
                    this.refresh_rooms();
                };
                connection.onmessage = (event) => {
                    let message = JSON.parse(event.data);
                    switch (message.event) {
                        case 'init':
                            this.rooms_loading = false;
                            this.rooms = this.rooms.filter(room => room.server !== server).concat(
                                message.data.map((room: Room) => Object.assign({server: server}, room))
                            );
                            break;
                        case 'create':
                            this.rooms.push(Object.assign({server: server}, message.data));
                            break;
                        case 'update':
                            Object.assign(this.rooms.find(room => room.server === server && room.id === message.data.id), message.data);
                            break;
                        case 'delete':
                            this.rooms.splice(this.rooms.findIndex(room => room.server === server && room.id === message.data), 1);
                    }
                    this.refresh_rooms();
                    this.ref.detectChanges();
                };
                return connection;
            });
        });

        modal.on('hide.bs.modal', () => {
            for (let connection of this.connections) {
                connection.close();
            }
            this.connections = [];
        });

        // TODO: 跟上面的逻辑合并
        let replay_modal = $('#game-replay-modal');

        replay_modal.on('show.bs.modal', () => {
            this.replay_connections = this.servers.filter(server => server.replay).map((server) => {
                let url = new URL(server.url!);
                url['searchParams'].set('filter', 'started');
                let connection = new WebSocket(url.toString());
                connection.onclose = () => {
                    this.replay_rooms = this.replay_rooms.filter(room => room.server !== server);
                    this.refresh_replay_rooms();
                };
                connection.onmessage = (event) => {
                    let message = JSON.parse(event.data);
                    switch (message.event) {
                        case 'init':
                            this.replay_rooms = this.replay_rooms.filter(room => room.server !== server).concat(
                                message.data.map((room: Room) => Object.assign({
                                    server: server,
                                    'private': /^\d+$/.test(room.title!)
                                }, room))
                            );
                            break;
                        case 'create':
                            this.replay_rooms.push(Object.assign({
                                server: server,
                                'private': /^\d+$/.test(message.data.title!)
                            }, message.data));
                            break;
                        case 'delete':
                            this.replay_rooms.splice(
                                this.replay_rooms.findIndex(room => room.server === server && room.id === message.data),
                                1
                            );
                    }
                    this.refresh_replay_rooms();
                    this.ref.detectChanges();
                };
                return connection;
            });
        });

        replay_modal.on('hide.bs.modal', () => {
            for (let connection of this.replay_connections) {
                connection.close();
            }
            this.replay_connections = [];
        });

        let watchDropdownMenu = $('#watch-filter');

        watchDropdownMenu.on("change", "input[type='checkbox']", (event) => {
            // $(event.target).closest("label").toggleClass("active", (<HTMLInputElement> event.target).checked);
            this.refresh_replay_rooms();
        });

        replay_modal.on('click', (event) => {
            if (!watchDropdownMenu.is(event.target) && !watchDropdownMenu.has(event.target).length) {
                watchDropdownMenu.removeClass('show');
            }
        });

        $('#watchDropdownMenuButton').on('click', () => {
            watchDropdownMenu.toggleClass('show');
        });

        remote.ipcMain.on('YGOPro', (e: any, type: string) => {
            console.log('rrrrr');
            this.request_match(type);
        });
    }


    async refresh(init?: boolean) {
        this.decks = await this.get_decks();
        if (this.lastDeckFormat) {
            const systemConfString = await this.load_system_conf();

            let lastDeck: string;
            if (systemConfString) {
                // console.log(`System conf string: ${systemConfString}`);
                const lastDeckMatch = systemConfString.match(this.lastDeckFormat);
                if (lastDeckMatch) {
                    lastDeck = lastDeckMatch[1];
                    // console.log(`Last deck ${lastDeck} read from ${this.system_conf}.`);
                } else {
                    // console.error(`Deck pattern not found from pattern ${this.system_conf}: ${lastDeckMatch}`);
                }
            } else {
                // console.error(`System conf ${this.system_conf} not found.`);
            }

            if (lastDeck && this.decks.includes(lastDeck)) {
                // console.log(`Got last deck ${lastDeck}.`);
                this.current_deck = lastDeck;
            } else if (init) {
                this.current_deck = this.decks[0];
            }
        }

        this.replays = await this.get_replays();
        this.replays2 = await this.get_replays2();

        // https://mycard.moe/ygopro/api/user?username=ozxdno
        try {
            let points = await this.http.get('https://mycard.moe/ygopro/api/user', {
                search: {
                    username: this.loginService.user.username
                }
            })
                .map((response) => response.json())
                .toPromise();
            this.points.emit(points);
        } catch (error) {
            console.log(error);
        }
    };

    async get_decks(): Promise<string[]> {
        try {
            /*
            const deckPath = this.app.ygoproDeckPath;
            let result: string[] = [];
            const files: string[] = await fs.readdir(deckPath);
            for (const file of files) {
                if (file.startsWith('.git')) { // fuck
                   continue;
                }
                if (path.extname(file) === '.ydk') {
                    result.push(path.basename(file, '.ydk'));
                } else {
                    const fullPath = path.join(deckPath, file);
                    const stat = await fs.stat(fullPath);
                    if (stat.isDirectory()) {
                        const innerDecks = (await fs.readdir(fullPath))
                            .filter((iFile) => path.extname(iFile) === '.ydk')
                            .map((iFile) => path.join(file, path.basename(iFile, '.ydk')));
                        result = result.concat(innerDecks);
                    }
                }
            }
            return result;
            */
            let files: string[] = await fs.readdir(this.app.ygoproDeckPath);
            return files.filter(file => path.extname(file) === '.ydk').map(file => path.basename(file, '.ydk'));
        } catch (error) {
            console.error(`Load deck fail: ${error.toString()}`);
            return [];
        }
    }

    async get_replays(): Promise<string[]> {
        try {
            let files: string[] = await fs.readdir(this.app.ygoproReplayPath);
            return files.filter(file => path.extname(file) === '.yrp').map(file => path.basename(file, '.yrp'));
        } catch (error) {
            console.error(`Load replay fail: ${error.toString()}`);
            return [];
        }
    }
    async get_replays2(): Promise<string[]> {
        try {
            let files: string[] = await fs.readdir(this.app.ygoproReplayPath);
            return files.filter(file => path.extname(file) === '.yrp3d').map(file => path.basename(file, '.yrp3d'));
        } catch (error) {
            console.error(`Load replay fail: ${error.toString()}`);
            return [];
        }
    }

    async get_font(files: string[]): Promise<string | undefined> {
        for (let file of files) {
            if (await fs.pathExists(file)) {
                return file;
            }
        }
    }

    async delete_deck(deck: string) {
        if (confirm('确认删除?')) {
            try {
                await fs.unlink(path.join(this.app.ygoproDeckPath, deck + '.ydk'));
            } catch (error) {
            }
            return this.refresh();
        }
    }

    /*
    async fix_fonts(data: SystemConf) {
        if (!await this.get_font([data.numfont])) {
            let font = await this.get_font(this.numfont);
            if (font) {
                data['numfont'] = font;
            }
        }

        if (data.textfont === 'c:/windows/fonts/simsun.ttc 14' || !await this.get_font([data.textfont.split(' ', 2)[0]])) {
            let font = await this.get_font(this.textfont);
            if (font) {
                data['textfont'] = `${font} 14`;
            }
        }
    };*/

    async load_system_conf(): Promise<string> {
        if (!this.system_conf) {
            return null;
        }
        try {
            // console.log(`Loading system conf from ${this.system_conf}`)
            let data = await fs.readFile(this.system_conf, {encoding: 'utf-8'});
            return data;
        } catch(e) {
            return null;
        }
    };

    /*
    save_system_conf(data: SystemConf) {
        return fs.writeFile(this.system_conf, ini.unsafe(ini.stringify(data, <EncodeOptions>{whitespace: true})));
    };*/

    async join(name: string, server: Server) {
        /*let system_conf = await this.load_system_conf();
        await this.fix_fonts(system_conf);
        system_conf.lastdeck = this.current_deck;
        system_conf.lastip = server.address;
        system_conf.lasthost = server.address;
        system_conf.lastport = server.port.toString();
        system_conf.roompass = name;
        system_conf.nickname = this.loginService.user.username;
        await this.save_system_conf(system_conf);*/
        // return this.start_game(['-h', server.address, '-p', server.port.toString(), '-w', name, '-n', this.loginService.user.username, '-d', this.current_deck, '-j']);
        return this.start_game('main', {server, password: name, username: this.loginService.user.username, deck: this.current_deck});
    };

    async edit_deck(deck: string) {
        /*let system_conf = await this.load_system_conf();
        await this.fix_fonts(system_conf);
        system_conf.lastdeck = deck;
        await this.save_system_conf(system_conf);*/
        // return this.start_game(['-d', deck]);
        return this.start_game('deck', { deck })
    }

    async watch_replay(replay: string) {
        /*let system_conf = await this.load_system_conf();
        await this.fix_fonts(system_conf);
        await this.save_system_conf(system_conf);*/
        // return this.start_game(['-r', path.join('replay', replay + '.yrp')]);
        return this.start_game('replay', { replay: path.join(this.app.ygoproReplayPath, `${replay}.yrp`) });
    }
    async watch_replay2(replay: string) {
        return this.start_game('replay', { replay: path.join(this.app.ygoproReplayPath, `${replay}.yrp3d`) });
    }

    join_windbot(name?: string) {
        if (!name) {
            name = this.windbot[Math.floor(Math.random() * this.windbot.length)];
        }
        return this.join('AI#' + name, this.currentServer);
    }

    async start_game(action: string, param: any) {
        let data: any;
        let start_time: string;
        let exp_rank_ex: number;
        let arena_rank_ex: number;
        let win = remote.getCurrentWindow();

        win.minimize();
        await new Promise(async (resolve, reject) => {
            const children = this.appsService.findChildren(this.app);
            let child = await this.app.spawnApp(children, action, param);
            child.on('error', (error) => {
                reject(error);
                win.restore();
            });
            child.on('exit', async (code, signal) => {
                // error 触发之后还可能会触发exit，但是Promise只承认首次状态转移，因此这里无需重复判断是否已经error过。
                await this.refresh();
                resolve(null);
                win.restore();
            });
            try {
                this.http.get('https://mycard.moe/ygopro/api/history', {
                    search: {
                        page: 1,
                        username: this.loginService.user.username,
                        type: 0,
                        page_num: 1
                    }
                })
                    .map((response) => response.json())
                    .toPromise()
                    .then((d) => {
                        start_time = d.data[0].start_time;
                    });
            } catch (error) {
                console.log(error);
            }
            try {
                this.http.get('https://sapi.moecube.com:444/ygopro/arena/user', {search: {username: this.loginService.user.username}})
                    .map((response) => response.json())
                    .toPromise()
                    .then((d2) => {
                        exp_rank_ex = d2.exp_rank;
                        arena_rank_ex = d2.arena_rank;
                    });
            } catch (error) {
                console.log(error);
            }
        });
        try {
            await this.http.get('https://mycard.moe/ygopro/api/history', {
                search: {
                    page: 1,
                    username: this.loginService.user.username,
                    // username: "星光pokeboy",
                    type: 0,
                    page_num: 1
                }
            })
                .map((response) => response.json())
                .toPromise()
                .then((d) => {
                    data = d.data[0];
                    data.myname = this.loginService.user.username;
                });

            await this.http.get('https://sapi.moecube.com:444/ygopro/arena/user', {
                search: {
                    username: this.loginService.user.username,
                }
            })
                .map((response) => response.json())
                .toPromise()
                .then((data2) => {
                    data.athletic_win = data2.athletic_win;
                    data.athletic_lose = data2.athletic_lose;
                    data.entertain_win = data2.entertain_win;
                    data.entertain_lose = data2.entertain_lose;
                    data.exp_rank = data2.exp_rank;
                    data.arena_rank = data2.arena_rank;
                    data.exp_rank_ex = exp_rank_ex;
                    data.arena_rank_ex = arena_rank_ex;
                    if (start_time !== data.start_time) {
                        this.appsService.showResult('app/end_YGOPro_single.html', data, 202, 222);
                    }
                });
        } catch (error) {
            console.log(error);
        }
    };

    create_room(room: Room) {
        let options_buffer = Buffer.alloc(6);
        // 建主密码 https://docs.google.com/document/d/1rvrCGIONua2KeRaYNjKBLqyG9uybs9ZI-AmzZKNftOI/edit
        options_buffer.writeUInt8(((room.private ? 2 : 1) << 4) |
            (room.options.duel_rule << 1) |
            (room.options.auto_death ? 0x1 : 0), 1);
        options_buffer.writeUInt8(
            room.options.rule << 5 |
            room.options.mode << 3 |
            (room.options.no_check_deck ? 1 << 1 : 0) |
            (room.options.no_shuffle_deck ? 1 : 0)
            , 2);
        options_buffer.writeUInt16LE(room.options.start_lp, 3);
        options_buffer.writeUInt8(room.options.start_hand << 4 | room.options.draw_count, 5);
        let checksum = 0;
        for (let i = 1; i < options_buffer.length; i++) {
            checksum -= options_buffer.readUInt8(i);
        }
        options_buffer.writeUInt8(checksum & 0xFF, 0);

        let secret = this.loginService.user.external_id % 65535 + 1;
        for (let i = 0; i < options_buffer.length; i += 2) {
            options_buffer.writeUInt16LE(options_buffer.readUInt16LE(i) ^ secret, i);
        }

        let password = options_buffer.toString('base64') + (room.private ? this.host_password :
            room.title!.replace(/\s/, String.fromCharCode(0xFEFF)));
        // let room_id = crypto.createHash('md5').update(password + this.loginService.user.username).digest('base64')
        //     .slice(0, 10).replace('+', '-').replace('/', '_');

        if (room.private) {
            new Notification('YGOPro 私密房间已建立', {
                body: `房间密码是 ${this.host_password}, 您的对手可在自定义游戏界面输入密码与您对战。`
            });
        }
        this.join(password, this.currentServer);
    }

    copy(text: string, event: Event) {
        clipboard.writeText(text);
        const copyWrapper = $('#copy-wrapper');
        copyWrapper.tooltip({trigger: 'manual'});
        copyWrapper.tooltip('show');
    }

    join_room(room: Room) {
        let options_buffer = Buffer.alloc(6);
        options_buffer.writeUInt8(3 << 4, 1);
        let checksum = 0;
        for (let i = 1; i < options_buffer.length; i++) {
            checksum -= options_buffer.readUInt8(i);
        }
        options_buffer.writeUInt8(checksum & 0xFF, 0);

        let secret = this.loginService.user.external_id % 65535 + 1;
        for (let i = 0; i < options_buffer.length; i += 2) {
            options_buffer.writeUInt16LE(options_buffer.readUInt16LE(i) ^ secret, i);
        }


        let name = options_buffer.toString('base64') + room.id;

        this.join(name, room.server!);
    }

    join_private(password: string) {
        let options_buffer = Buffer.alloc(6);
        options_buffer.writeUInt8(5 << 4, 1);
        let checksum = 0;
        for (let i = 1; i < options_buffer.length; i++) {
            checksum -= options_buffer.readUInt8(i);
        }
        options_buffer.writeUInt8(checksum & 0xFF, 0);

        let secret = this.loginService.user.external_id % 65535 + 1;
        for (let i = 0; i < options_buffer.length; i += 2) {
            options_buffer.writeUInt16LE(options_buffer.readUInt16LE(i) ^ secret, i);
        }

        let name = options_buffer.toString('base64') + password.replace(/\s/, String.fromCharCode(0xFEFF));

        this.join(name, this.currentServer);
    }

    request_match(arena = 'entertain') {
        let headers = new Headers();
        headers.append('Authorization',
            'Basic ' + Buffer.from(this.loginService.user.username + ':' + this.loginService.user.external_id).toString('base64'));
        match_started_at = new Date();
        this.matching_arena = matching_arena = arena;
        this.matching = matching = this.http.post('https://sapi.moecube.com:444/ygopro/match', null, {
            headers: headers,
            search: {
                arena,
                locale: this.settingsService.getLocale()
            }
        }).map(response => response.json())
            .subscribe((data) => {
                this.join(data['password'], {id: '_match', address: data['address'], port: data['port']});
            }, (error) => {
                alert(`匹配失败`);
                this.matching = matching = undefined;
                this.matching_arena = matching_arena = undefined;
                if (this.match_interval) {
                    clearInterval(this.match_interval);
                    this.match_interval = undefined;
                }
            }, () => {
                this.matching = matching = undefined;
                this.matching_arena = matching_arena = undefined;
                if (this.match_interval) {
                    clearInterval(this.match_interval);
                    this.match_interval = undefined;
                }
            });

        this.refresh_match();
        this.match_interval = setInterval(() => {
            this.refresh_match();
        }, 1000);
    }

    cancel_match() {
        this.matching!.unsubscribe();
        this.matching = matching = undefined;
        this.matching_arena = matching_arena = undefined;
        if (this.match_interval) {
            clearInterval(this.match_interval);
            this.match_interval = undefined;
        }
    }

    ngOnDestroy() {
        if (this.match_interval) {
            clearInterval(this.match_interval);
            this.match_interval = undefined;
        }

        remote.ipcMain.removeAllListeners('YGOPro');
    }

    refresh_match() {
        let match_time = Math.floor((new Date().getTime() - match_started_at.getTime()) / 1000);
        let minute = Math.floor(match_time / 60).toString();
        if (minute.length === 1) {
            minute = '0' + minute;
        }
        let second = (match_time % 60).toString();
        if (second.length === 1) {
            second = '0' + second;
        }
        this.match_time = `${minute}:${second}`;
        this.match_cancelable = match_time <= 5 || match_time >= 180;
    }

    bilibili_loaded() {
        this.bilibili.nativeElement.insertCSS(`
            #b_app_link {
                visibility: hidden;
            }
            .wrapper {
                padding-top: 0 !important;
                overflow-y: hidden;
            }
            .nav-bar, .top-title, .roll-bar, footer {
                display: none !important;
            }
            html, body {
                background-color: initial !important;
            }
        `);
    }

    bilibili_navigate(event: WillNavigateEvent) {
        // event.preventDefault();
        // https://github.com/electron/electron/issues/1378
        this.bilibili.nativeElement.src = 'http://m.bilibili.com/search.html?keyword=YGOPro';
        shell.openExternal(event.url);
    }

    // youtube_loaded () {
    //
    // }
    //
    // youtube_navigate (event: WillNavigateEvent) {
    //     this.youtube.nativeElement.src = 'https://m.youtube.com/results?search_query=YGOPro';
    //     shell.openExternal(event.url);
    // }

    avatar_fallback(event) {
        if (!event.target.getAttribute('fallback')) {
            event.target.src = "images/noavatar.png";
            event.target.setAttribute('fallback', true);
        }
    }
}
