/// <reference types="@ms/types/dist/typings/bukkit" />
/// <reference types="@ms/types/dist/typings/sponge" />

import { server, plugin as pluginApi } from '@ms/api'
import { inject } from '@ms/container';
import { plugin, interfaces, cmd, listener, tab } from '@ms/plugin'
import Tellraw from '@ms/common/dist/tellraw'

const ByteArrayInputStream = Java.type("java.io.ByteArrayInputStream");
const ByteArrayOutputStream = Java.type("java.io.ByteArrayOutputStream");
const StandardCharsets = Java.type("java.nio.charset.StandardCharsets");
const GZIPInputStream = Java.type("java.util.zip.GZIPInputStream");
const GZIPOutputStream = Java.type("java.util.zip.GZIPOutputStream");
const Consumer = Java.type("java.util.function.Consumer");
const ByteArray = Java.type("byte[]")

class MiaoMessage {
    // public static final String CHANNEL = "MiaoChat:Default".toLowerCase();
    public static CHANNEL: string = "MiaoChat:Default".toLowerCase()
    // public static final String NORMAL_CHANNEL = "MiaoChat:Normal".toLowerCase();
    public static NORMAL_CHANNEL: string = "MiaoChat:Normal".toLowerCase()
    // private static final int MAX_MESSAGE_LENGTH = 32000;
    private static MAX_MESSAGE_LENGTH = 32000;

    private static copy(input, output) {
        let buffer = new ByteArray(1024);
        let n: number;
        while ((n = input.read(buffer)) != -1) {
            output.write(buffer, 0, n);
        }
        input.close();
        output.close();
    }
    public static encode(input: any): any {
        return new MiaoMessage(input).encode();
    }
    public static decode(input: any): MiaoMessage {
        let baos = new ByteArrayOutputStream();
        MiaoMessage.copy(new GZIPInputStream(new ByteArrayInputStream(input)), baos);
        return new MiaoMessage(baos.toString(StandardCharsets.UTF_8.name()));
    }

    // private String json;
    constructor(public json: any) { }

    public encode(): any {
        let baos = new ByteArrayOutputStream();
        MiaoMessage.copy(new ByteArrayInputStream(this.json.getBytes(StandardCharsets.UTF_8)), new GZIPOutputStream(baos));
        if (baos.size() > MiaoMessage.MAX_MESSAGE_LENGTH) { return null; }
        return baos.toByteArray();
    }
}

@plugin({ name: 'MiaoChat', version: '1.0.0', author: 'MiaoWoo', source: __filename })
export class MiaoChat extends interfaces.Plugin {
    @inject(server.Server)
    private Server: server.Server
    @inject(server.ServerType)
    private ServerType: string

    private spongeChannel: any;
    private spongeListener: any;

    private config = {
        Version: "1.8.5",
        BungeeCord: true,
        Server: "生存服",
        ChatFormats: {
            default: {
                index: 50,
                permission: "MiaoChat.default",
                range: 0,
                format: "[world][player]: ",
                item: true,
                itemformat: "&6[&b%s&6]&r"
            },
            admin: {
                index: 49,
                permission: "MiaoChat.admin",
                format: "[admin][world][player][help]: ",
                range: 0,
                item: true,
                itemformat: "&6[&b%s&6]&r"
            }
        },
        StyleFormats: {
            world: {
                text: "&6[&a%player_world%&6]",
                hover: [
                    "&6当前所在位置:",
                    "&6世界: &d%player_world%",
                    "&6坐标: &aX:%player_x% Y: %player_y% Z: %player_z%",
                    "",
                    "&c点击即可TP我!"
                ],
                click: {
                    type: "COMMAND",
                    command: "/tpa %player_name%"
                }
            },
            player: {
                text: "&b%player_name%",
                hover: [
                    "&6玩家名称: &b%player_name%",
                    "&6玩家等级: &a%player_level%",
                    "&6玩家血量: &c%player_health%",
                    "&6玩家饥饿: &d%player_food_level%",
                    "&6游戏模式: &4%player_gamemode%",
                    "",
                    "&c点击与我聊天"
                ],
                click: {
                    type: "SUGGEST",
                    command: "/tell %player_name%"
                }
            },
            admin: {
                text: "&6[&c管理员&6]"
            },
            help: {
                text: "&4[求助]",
                hover: [
                    "点击求助OP"
                ],
                click: {
                    type: "COMMAND",
                    command: "管理员@%player_name% 我需要你的帮助!"
                }
            }
        }
    }

    private chatFormats: any[];
    private styleFormats: any;
    // 用于匹配 '[xx]' 聊天格式
    private FORMAT_PATTERN = /[\[]([^\[\]]+)[\]]/ig;

    private PlaceholderAPI: { setPlaceholders: (player: any, str: string) => string };

    load() {
        if (!Object.values) {
            Object.defineProperty(Object, "values", {
                enumerable: false,
                configurable: true,
                writable: true,
                value: function(target) {
                    "use strict";
                    var values = [];
                    for (var key in target) {
                        var desc = Object.getOwnPropertyDescriptor(target, key);
                        if (desc !== undefined && desc.enumerable) values.push(target[key]);
                    }
                    return values;
                }
            });
        }
        this.chatFormats = Object.values(this.config.ChatFormats);
        this.chatFormats.sort(this.compare('index'));
        this.initFormat(this.chatFormats);
        this.styleFormats = this.config.StyleFormats;
    }

    private compare(prop: string) {
        return function(obj1: { [x: string]: any; }, obj2: { [x: string]: any; }) {
            var val1 = obj1[prop];
            var val2 = obj2[prop];
            if (!isNaN(Number(val1)) && !isNaN(Number(val2))) {
                val1 = Number(val1);
                val2 = Number(val2);
            }
            if (val1 < val2) {
                return -1;
            } else if (val1 > val2) {
                return 1;
            } else {
                return 0;
            }
        }
    }

    enable() {
        this.PlaceholderAPI = {
            setPlaceholders: (player: any, string: string) => {
                return string;
            }
        }
    }

    disable() {
    }

    bukkitenable() {
        // 尝试加载 Bukkit 的 PlaceholderAPI
        try {
            //@ts-ignore
            this.PlaceholderAPI = base.getClass("me.clip.placeholderapi.PlaceholderAPI").static;
            this.logger.log('[PAPI] Found Bukkit PlaceholderAPI Hooking...')
        } catch (ex) {
            this.logger.console("§cCan't found me.clip.placeholderapi.PlaceholderAPI variable will not be replaced! Err: " + ex)
        }
        var Bukkit = org.bukkit.Bukkit;
        var PluginMessageListener = Java.type("org.bukkit.plugin.messaging.PluginMessageListener")
        Bukkit.getMessenger().registerIncomingPluginChannel(base.getInstance(), MiaoMessage.CHANNEL, new PluginMessageListener({
            onPluginMessageReceived: (/**String */ var1, /**Player */ var2, /**byte[] */var3) => {
                this.sendChatAll(MiaoMessage.decode(var3).json)
            }
        }));
        Bukkit.getMessenger().registerOutgoingPluginChannel(base.getInstance(), MiaoMessage.CHANNEL);
    }

    bukkitdisable() {
        var Bukkit = org.bukkit.Bukkit;
        Bukkit.getMessenger().unregisterIncomingPluginChannel(base.getInstance(), MiaoMessage.CHANNEL)
        Bukkit.getMessenger().unregisterOutgoingPluginChannel(base.getInstance(), MiaoMessage.CHANNEL)
    }

    spongeenable() {
        // 尝试加载 Sponge 的 PlaceholderAPI
        try {
            var spongePapi = this.Server.getService('me.rojo8399.placeholderapi.PlaceholderService');
            var s = org.spongepowered.api.text.serializer.TextSerializers.formattingCode('§');
            if (spongePapi) {
                this.PlaceholderAPI = {
                    setPlaceholders: (player: any, string: string) => {
                        return s.serialize(spongePapi.replacePlaceholders(string, player, player));
                    }
                };
                this.logger.log('[PAPI] Found Sponge PlaceholderAPI Hooking...')
            }
        } catch (ex) {
            this.logger.console("§cCan't found me.rojo8399.placeholderapi.PlaceholderService variable will not be replaced! Err: " + ex)
        }
        var Sponge = org.spongepowered.api.Sponge
        var RawDataListener = Java.type("org.spongepowered.api.network.RawDataListener")
        var ChannelRegistrar = Sponge.getChannelRegistrar()
        this.spongeChannel = ChannelRegistrar.getOrCreateRaw(base.getInstance(), MiaoMessage.CHANNEL)
        this.spongeListener = new RawDataListener({
            handlePayload: (/* ChannelBuf */ data, /**RemoteConnection */ connection, /**Platform.Type */ side) => {
                this.sendChatAll(MiaoMessage.decode(data.readBytes(data.available())).json)
            }
        })
        this.spongeChannel.addListener(this.spongeListener)
    }

    spongedisable() {
        this.spongeChannel.removeListener(this.spongeListener)
    }

    bungeeenable() {
        let bungee: net.md_5.bungee.api.ProxyServer = base.getInstance().getProxy()
        bungee.registerChannel(MiaoMessage.CHANNEL);
        bungee.registerChannel(MiaoMessage.NORMAL_CHANNEL);
    }

    bungeedisable() {
        let bungee: net.md_5.bungee.api.ProxyServer = base.getInstance().getProxy()
        bungee.unregisterChannel(MiaoMessage.CHANNEL);
        bungee.unregisterChannel(MiaoMessage.NORMAL_CHANNEL);
    }

    @cmd({ servers: ["bungee"] })
    mct(sender: any, command: string, args: string[]) {
        this.logger.log(sender, command, args);
        sender.sendMessage(JSON.stringify({ command, ...args }))
    }

    @cmd({ servers: ["!bungee"] })
    mchat(sender: any, command: string, args: string[]) {
        this.logger.log(sender, command, args);
        sender.sendMessage(JSON.stringify({ command, ...args }))
    }

    @tab()
    tabmchat(_sender: any, _command: string, _args: string[]) {
    }

    @listener({ servers: ['bukkit'] })
    AsyncPlayerChatEvent(event: org.bukkit.event.player.AsyncPlayerChatEvent) {
        this.sendChat(event.getPlayer(), event.getMessage(), function() {
            event.setCancelled(true);
        });
    }

    @listener({ servers: ['sponge'] })
    MessageChannelEvent$Chat(event: org.spongepowered.api.event.message.MessageChannelEvent.Chat) {
        //@ts-ignore
        var player = event.getCause().first(org.spongepowered.api.entity.living.player.Player.class).orElse(null);
        if (player == null) {
            return;
        }
        var plain = event.getRawMessage().toPlain();
        if (plain.startsWith(Tellraw.duplicateChar)) {
            return;
        }
        this.sendChat(player, plain, function() {
            event.setMessageCancelled(true)
        });
    }

    @listener({ servers: ['bungee'] })
    PluginMessageEvent(e: any) {
        let bungee: net.md_5.bungee.api.ProxyServer = base.getInstance().getProxy()
        let event = e as net.md_5.bungee.api.event.PluginMessageEvent
        if (event.getTag() == MiaoMessage.CHANNEL || event.getTag() == MiaoMessage.NORMAL_CHANNEL) {
            let origin = event.getSender().getAddress();
            bungee.getServers().forEach(server => {
                if (server.getAddress() != origin && server.getPlayers().size() > 0) {
                    server.sendData(event.getTag(), event.getData())
                }
            });
        }
    }

    initFormat(chatFormats: any[]) {
        chatFormats.forEach(chatFormat => {
            var chat_format_str = chatFormat.format;
            var temp = [];
            var r: any[];
            while (r = this.FORMAT_PATTERN.exec(chat_format_str)) {
                temp.push(r[1]);
            }
            var format_list = [];
            temp.forEach(t => {
                var arr = chat_format_str.split('[' + t + ']', 2);
                if (arr[0]) {
                    format_list.push(arr[0]);
                }
                format_list.push(t);
                chat_format_str = arr[1];
            });
            if (chat_format_str) {
                format_list.push(chat_format_str);
            }
            chatFormat.format_list = format_list;
        })
    }

    sendChat(player: any, plain: string, callback: { (): void; }) {
        var chat_format = this.getChatFormat(player);
        if (!chat_format) {
            console.debug('未获得用户', player.getName(), '的 ChatRule 跳过执行...');
            return;
        }
        callback();
        var tr = Tellraw.create();
        chat_format.format_list.forEach((format) => {
            var style = this.styleFormats[format];
            if (style) {
                tr.then(this.replace(player, style.text));
                if (style.hover) {
                    tr.tip(this.replace(player, style.hover.join('\n')));
                }
                if (style.click && style.click.type && style.click.command) {
                    var command = this.replace(player, style.click.command);
                    switch (style.click.type.toUpperCase()) {
                        case "COMMAND":
                            tr.command(command);
                            break;
                        case "OPENURL":
                            tr.link(command);
                            break;
                        case "SUGGEST":
                            tr.suggest(command);
                            break;
                        default:
                    }
                }
            } else {
                tr.then(this.replace(player, format));
            }
        });
        let json = tr.then(this.replace(player, plain)).json()
        this.sendChatAll(json)
        let mm = MiaoMessage.encode(json);
        switch (this.ServerType) {
            case "bukkit":
                if (mm == null) {
                    player.sendPluginMessage(base.getInstance(), MiaoMessage.NORMAL_CHANNEL, MiaoMessage.encode(tr.string()));
                } else {
                    player.sendPluginMessage(base.getInstance(), MiaoMessage.CHANNEL, mm);
                }
                break;
            case "sponge":
                if (mm == null) {
                    this.spongeChannel.sendTo(player, new Consumer({
                        accept: (channelBuf) => channelBuf.writeBytes(MiaoMessage.encode(tr.string()))
                    }))
                } else {
                    this.spongeChannel.sendTo(player, new Consumer({
                        accept: (channelBuf) => channelBuf.writeBytes(mm)
                    }))
                }
                break;
        }
    }

    sendChatAll(json: string) {
        this.Server.getOnlinePlayers().forEach(player => this.Server.sendJson(player, json))
    }

    getChatFormat(player: any) {
        for (var i in this.chatFormats) {
            var format = this.chatFormats[i];
            if (player.hasPermission(format.permission)) {
                return format;
            }
        }
        return null;
    }

    replace(player: any, target: string) {
        return this.PlaceholderAPI.setPlaceholders(player, target)
    }
}
