import core from './core';
import {Character, Conversation, userStatuses} from './interfaces';
import l from './localize';
import ChannelConversation = Conversation.ChannelConversation;
import PrivateConversation = Conversation.PrivateConversation;

export const enum ParamType {
    String, Number, Character, Enum
}

const defaultDelimiters: {[key: number]: string | undefined} = {[ParamType.Character]: ',', [ParamType.String]: ''};

export function isAction(this: void, text: string): boolean {
    return /^\/me\b/i.test(text);
}

export function isWarn(this: void, text: string): boolean {
    return /^\/warn\b/i.test(text);
}

export function isCommand(this: void, text: string): boolean {
    return text.charAt(0) === '/' && !isAction(text) && !isWarn(text);
}

export function parse(this: void | never, input: string, context: CommandContext): ((this: Conversation) => void) | string {
    const commandEnd = input.indexOf(' ');
    const name = input.substring(1, commandEnd !== -1 ? commandEnd : undefined).toLowerCase();
    const command = commands[name];
    if(command === undefined) return l('commands.unknown');
    const args = `${commandEnd !== -1 ? input.substr(commandEnd + 1) : ''}`;
    if(command.context !== undefined && (command.context & context) === 0) return l('commands.badContext');

    let index = 0;
    const values: (string | number)[] = [];

    if(command.params !== undefined)
        for(let i = 0; i < command.params.length; ++i) {
            while(args[index] === ' ') ++index;
            const param = command.params[i];
            if(index === -1) {
                if(param.optional !== undefined) continue;
                return l('commands.tooFewParams');
            }
            let delimiter = param.delimiter !== undefined ? param.delimiter : defaultDelimiters[param.type];
            if(delimiter === undefined) delimiter = ' ';
            const endIndex = delimiter.length > 0 ? args.indexOf(delimiter, index) : args.length;
            const value = args.substring(index, endIndex !== -1 ? endIndex : undefined);
            if(value.length === 0) {
                if(param.optional !== undefined) continue;
                return l('commands.tooFewParams');
            }
            values[i] = value;
            switch(param.type) {
                case ParamType.String:
                    if(i === command.params.length - 1) values[i] = args.substr(index);
                    break;
                case ParamType.Enum:
                    if((param.options !== undefined ? param.options : []).indexOf(value) === -1)
                        return l('commands.invalidParam', l(`commands.${name}.param${i}`));
                    break;
                case ParamType.Number:
                    const num = parseInt(value, 10);
                    if(isNaN(num))
                        return l('commands.invalidParam', l(`commands.${name}.param${i}`));
                    values[i] = num;
                    break;
                case ParamType.Character:
                    if(value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') {
                        values[i] = value.substring(1, value.length - 1);
                        break;
                    }
                    const char = core.characters.get(value);
                    if(char.status === 'offline') return l('commands.invalidCharacter');
            }
            index = endIndex === -1 ? args.length : endIndex + 1;
        }
    return function(this: Conversation): void {
        command.exec(this, ...values);
    };
}

export const enum CommandContext {
    Console = 1 << 0,
    Channel = 1 << 1,
    Private = 1 << 2
}

export enum Permission {
    RoomOp = -1,
    RoomOwner = -2,
    ChannelMod = 4,
    ChatOp = 2,
    Admin = 1
}

export interface Command {
    readonly context?: CommandContext,                          //default implicit Console | Channel | Private
    readonly permission?: Permission
    readonly documented?: false,                                //default true
    readonly params?: {
        readonly type: ParamType
        readonly options?: ReadonlyArray<string>,               //default undefined
        readonly optional?: true,                               //default false
        readonly delimiter?: string,                            //default ' ' (',' for type: Character)
        validator?(data: string | number): boolean              //default undefined
    }[]
    exec(context: Conversation, ...params: (string | number | undefined)[]): void
}

const commands: { readonly [key: string]: Command | undefined } = {
    me: {
        exec: () => "stub",
        context: CommandContext.Channel | CommandContext.Private,
        params: [{ type: ParamType.String }],
    },
    reward: {
        exec: (_, character: string) =>
            core.connection.send("RWD", { character }),
        permission: Permission.Admin,
        params: [{ type: ParamType.Character }],
    },
    greports: {
        permission: Permission.ChannelMod,
        exec: () => core.connection.send("PCR"),
    },
    join: {
        exec: (_, channel: string) => core.connection.send("JCH", { channel }),
        params: [{ type: ParamType.String }],
    },
    close: {
        exec: (conv: PrivateConversation | ChannelConversation) => conv.close(),
        context: CommandContext.Private | CommandContext.Channel,
    },
    priv: {
        exec: (_, character: string) =>
            core.conversations
                .getPrivate(core.characters.get(character))
                .show(),
        params: [{ type: ParamType.Character }],
    },
    uptime: {
        exec: () => core.connection.send("UPT"),
    },
    clear: {
        exec: (conv: Conversation) => conv.clear(),
    },
    status: {
        exec: (_, status: Character.Status, statusmsg: string = "") =>
            core.connection.send("STA", { status, statusmsg }),
        params: [
            { type: ParamType.Enum, options: userStatuses },
            { type: ParamType.String, optional: true },
        ],
    },
    roll: {
        exec: (
            conv: ChannelConversation | PrivateConversation,
            dice: string
        ) => { 
            if (dice.toLocaleLowerCase() === "inf") {
                conv.infoText = "Inf took many lives during its reign. Thankfully, you have been spared.";
                return;
            }
            else if (Conversation.isChannel(conv))
                core.connection.send("RLL", { channel: conv.channel.id, dice });
            else 
                core.connection.send("RLL", {
                    recipient: conv.character.name,
                    dice,
                });
        },
        context: CommandContext.Channel | CommandContext.Private,
        params: [{ type: ParamType.String }],
    },
    bottle: {
        exec: (conv: ChannelConversation | PrivateConversation) => {
            if (Conversation.isChannel(conv))
                core.connection.send("RLL", {
                    channel: conv.channel.id,
                    dice: "bottle",
                });
            else
                core.connection.send("RLL", {
                    recipient: conv.character.name,
                    dice: "bottle",
                });
        },
        context: CommandContext.Channel | CommandContext.Private,
    },
    warn: {
        exec: () => "stub",
        permission: Permission.RoomOp,
        context: CommandContext.Channel,
        params: [{ type: ParamType.String }],
    },
    kick: {
        exec: (conv: ChannelConversation, character: string) =>
            core.connection.send("CKU", {
                channel: conv.channel.id,
                character,
            }),
        permission: Permission.RoomOp,
        context: CommandContext.Channel,
        params: [{ type: ParamType.Character }],
    },
    ban: {
        exec: (conv: ChannelConversation, character: string) =>
            core.connection.send("CBU", {
                channel: conv.channel.id,
                character,
            }),
        permission: Permission.RoomOp,
        context: CommandContext.Channel,
        params: [{ type: ParamType.Character }],
    },
    unban: {
        exec: (conv: ChannelConversation, character: string) =>
            core.connection.send("CUB", {
                channel: conv.channel.id,
                character,
            }),
        permission: Permission.RoomOp,
        context: CommandContext.Channel,
        params: [{ type: ParamType.Character }],
    },
    banlist: {
        exec: (conv: ChannelConversation) =>
            core.connection.send("CBL", { channel: conv.channel.id }),
        permission: Permission.RoomOp,
        context: CommandContext.Channel,
    },
    timeout: {
        exec: (conv: ChannelConversation, character: string, length: number) =>
            core.connection.send("CTU", {
                channel: conv.channel.id,
                character,
                length,
            }),
        permission: Permission.RoomOp,
        context: CommandContext.Channel,
        params: [
            { type: ParamType.Character, delimiter: "," },
            { type: ParamType.Number, validator: (x) => x >= 1 },
        ],
    },
    gkick: {
        exec: (_, character: string) =>
            core.connection.send("KIK", { character }),
        permission: Permission.ChatOp,
        params: [{ type: ParamType.Character }],
    },
    gban: {
        exec: (_, character: string) =>
            core.connection.send("ACB", { character }),
        permission: Permission.ChatOp,
        params: [{ type: ParamType.Character }],
    },
    gunban: {
        exec: (_, character: string) =>
            core.connection.send("UNB", { character }),
        permission: Permission.ChatOp,
        params: [{ type: ParamType.Character }],
    },
    gtimeout: {
        exec: (_, character: string, time: number, reason: string) =>
            core.connection.send("TMO", { character, time, reason }),
        permission: Permission.ChatOp,
        params: [
            { type: ParamType.Character, delimiter: "," },
            { type: ParamType.Number, validator: (x) => x >= 1 },
            { type: ParamType.String },
        ],
    },
    setowner: {
        exec: (conv: ChannelConversation, character: string) =>
            core.connection.send("CSO", {
                channel: conv.channel.id,
                character,
            }),
        permission: Permission.RoomOwner,
        context: CommandContext.Channel,
        params: [{ type: ParamType.Character }],
    },
    ignore: {
        exec: (_, character: string) =>
            core.connection.send("IGN", { action: "add", character }),
        params: [{ type: ParamType.Character }],
    },
    unignore: {
        exec: (_, character: string) =>
            core.connection.send("IGN", { action: "delete", character }),
        params: [{ type: ParamType.Character }],
    },
    ignorelist: {
        exec: (conv: Conversation) =>
            (conv.infoText = l(
                "chat.ignoreList",
                core.characters.ignoreList.join(", ")
            )),
    },
    makeroom: {
        exec: (_, channel: string) => core.connection.send("CCR", { channel }),
        params: [{ type: ParamType.String }],
    },
    gop: {
        exec: (_, character: string) =>
            core.connection.send("AOP", { character }),
        permission: Permission.Admin,
        params: [{ type: ParamType.Character }],
    },
    gdeop: {
        exec: (_, character: string) =>
            core.connection.send("DOP", { character }),
        permission: Permission.Admin,
        params: [{ type: ParamType.Character }],
    },
    op: {
        exec: (conv: ChannelConversation, character: string) =>
            core.connection.send("COA", {
                channel: conv.channel.id,
                character,
            }),
        permission: Permission.RoomOwner,
        context: CommandContext.Channel,
        params: [{ type: ParamType.Character }],
    },
    deop: {
        exec: (conv: ChannelConversation, character: string) =>
            core.connection.send("COR", {
                channel: conv.channel.id,
                character,
            }),
        permission: Permission.RoomOwner,
        context: CommandContext.Channel,
        params: [{ type: ParamType.Character }],
    },
    scop: {
        exec: (_, character: string) =>
            core.connection.send("SCP", { action: "add", character }),
        permission: Permission.Admin,
        params: [{ type: ParamType.Character }],
    },
    scdeop: {
        exec: (_, character: string) =>
            core.connection.send("SCP", { action: "remove", character }),
        permission: Permission.Admin,
        params: [{ type: ParamType.Character }],
    },
    oplist: {
        exec: (conv: ChannelConversation) =>
            core.connection.send("COL", { channel: conv.channel.id }),
        context: CommandContext.Channel,
    },
    invite: {
        exec: (conv: ChannelConversation, character: string) =>
            core.connection.send("CIU", {
                channel: conv.channel.id,
                character,
            }),
        permission: Permission.RoomOp,
        context: CommandContext.Channel,
        params: [{ type: ParamType.Character }],
    },
    closeroom: {
        exec: (conv: ChannelConversation) =>
            core.connection.send("RST", {
                channel: conv.channel.id,
                status: "private",
            }),
        permission: Permission.RoomOwner,
        context: CommandContext.Channel,
    },
    openroom: {
        exec: (conv: ChannelConversation) =>
            core.connection.send("RST", {
                channel: conv.channel.id,
                status: "public",
            }),
        permission: Permission.RoomOwner,
        context: CommandContext.Channel,
    },
    setmode: {
        exec: (conv: ChannelConversation, mode: "ads" | "chat" | "both") =>
            core.connection.send("RMO", { channel: conv.channel.id, mode }),
        permission: Permission.RoomOwner,
        context: CommandContext.Channel,
        params: [{ type: ParamType.Enum, options: ["ads", "chat", "both"] }],
    },
    setdescription: {
        exec: (conv: ChannelConversation, description: string) =>
            core.connection.send("CDS", {
                channel: conv.channel.id,
                description,
            }),
        permission: Permission.RoomOp,
        context: CommandContext.Channel,
        params: [{ type: ParamType.String }],
    },
    code: {
        exec: (conv: ChannelConversation) => {
            const active = <HTMLElement>document.activeElement;
            const elm = document.createElement("textarea");
            elm.value = `[session=${conv.channel.name}]${conv.channel.id}[/session]`;
            document.body.appendChild(elm);
            elm.select();
            document.execCommand("copy");
            document.body.removeChild(elm);
            active.focus();
            conv.infoText = l("commands.code.success");
        },
        permission: Permission.RoomOwner,
        context: CommandContext.Channel,
    },
    killchannel: {
        exec: (conv: ChannelConversation) =>
            core.connection.send("KIC", { channel: conv.channel.id }),
        permission: Permission.RoomOwner,
        context: CommandContext.Channel,
    },
    createchannel: {
        exec: (_, channel: string) => core.connection.send("CRC", { channel }),
        permission: Permission.ChatOp,
        params: [{ type: ParamType.String }],
    },
    broadcast: {
        exec: (_, message: string) => core.connection.send("BRO", { message }),
        permission: Permission.Admin,
        params: [{ type: ParamType.String }],
    },
    reloadconfig: {
        exec: (_, save?: "save") =>
            core.connection.send(
                "RLD",
                save !== undefined ? { save } : undefined
            ),
        permission: Permission.Admin,
        params: [{ type: ParamType.Enum, options: ["save"], optional: true }],
    },
    xyzzy: {
        exec: (_, command: string, arg: string) =>
            core.connection.send("ZZZ", { command, arg }),
        permission: Permission.Admin,
        params: [
            { type: ParamType.String, delimiter: " " },
            { type: ParamType.String },
        ],
    },
    elf: {
        exec: (conv: Conversation) =>
            (conv.infoText = elf[Math.floor(Math.random() * elf.length)]),
        documented: false,
    },
};

const elf = [ //Ceran is to be thanked for most of these.
    `Now no one can say there's "not enough Elf." It's a well-kept secret, but elves love headpets. You should try it sometime.`,
    `Your task for the day: provide a soft cushion in a sunny area for maximum elf comfort, earn +2 Bliss pts.`,
    `Your task for the day: pet an elf at least (3) times, receive Good Karma.`,
    `Your task for the day: make dinner for an elf, receive +3 Luck pts.`,
    `The reason that elves' ears are so long is so that they can better hear your sins. Now that's an Elf Fact!`,
    `A "straight" elf is basically an oxymoron.`,
    `Don't forget to water your elf today!`,
    `Please don't let your elf eat out of the trash can.`,
    `Elves are not allowed to have soda after 12pm, but they will anyway.`,
    `Pet an elf on the ears (4) times. Any more and the elf will bite. Now that's an Elf Fact!`,
    `There are two kinds of people in the world. People who like elves. And people with questionable taste.`,
    `Love yourself, pet an elf!!!`,
    `Your task for the day: leave out snacks for your local elves, they'll help dispel vermin!`,
    `An elf in your home will discourage the presence of predators! Or summon them. I forget which.`,
    `If you crack open an elf, there's just a smaller elf within. It's just smaller and smaller elves all the way down.`,
    `In case of an emergency, an elf's ass can be used as a flotation device. Now that's an Elf Fact!`,
    `Running low on inventory space? An elf can be used to slot additional cylindrical artifacts! Now that's an Elf Fact!`,
    `The average elf can consume half their body weight in cum. Now that's an Elf Fact!`,
    `Your task for the day: subjugate yourself to your elven overlords in order to achieve righteous bliss.`,
    `Your task for the day: Rub an elf's tummy. Be wary of them bear-trapping your arm as the forces of darkness consume their soul.`,
    `Listen, that elfroot you found in my sock drawer is PURELY medicinal!`,
    `What are elves? We just don't know.`,
    `As long as you pet an elf's head, they will be content. What will happen if you stop? No one's ever come back to tell the tale.`,
    `Elves are very helpful creatures. Just ask them to carry something for you, and they will. Especially eggs.`
];

export default commands;