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;
                else 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;
                else 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}]
    },
    ad: {
        exec: (conv: ChannelConversation, message: string) =>
            core.connection.send('LRP', {channel: conv.channel.id, message}),
        context: CommandContext.Channel,
        params: [{type: ParamType.String}]
    },
    roll: {
        exec: (conv: ChannelConversation | PrivateConversation, dice: string) => {
            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;