/// <reference path="Rulebook.ts" />
/// <reference path="Rule.ts" />
/// <reference path="../../Elements/Classes/Say.ts" />
class Action {
    public static check = new Rulebook<Action>("Check any Action");
    public static carry = new Rulebook<Action>("Carry out any Action");

    public extraChecks : Array<Rulebook<Action>> = [];
    public extraCarries : Array<Rulebook<Action>> = [];

    public _actor : Thing;
    public nouns : Array<Thing> = [];
    public say : Say = new Say();

    public actingAgressively = false;
    public actingSubmissively = false;
    public requiresTurn = true;
    public requiresNoun = true;
    public requiresVisibility = true; // First noun must be visible and in the same room


    public constructor (actor : Thing, ...nouns : Array<any>) {
        this.actor = actor;
        nouns.forEach((value, index, array) => {
            this.setNoun(index, value);
        });
    }


    public async execute () : Promise<Say> {
        this.say = new Say();

        let checkRulebooks = [];
        let carryRulebooks = [];
        let cClass = this.constructor;
        while (cClass != Action) {
            if ((<typeof Action> cClass).check != undefined) {
                checkRulebooks.push((<typeof Action> cClass).check);
            }
            if ((<typeof Action> cClass).carry != undefined) {
                carryRulebooks.push((<typeof Action> cClass).carry);
            }
            cClass = Object.getPrototypeOf(cClass);
        }

        /**
         * Check if action goes through
         */
        let result = await Action.check.execute({
            noun : this
        }, ...checkRulebooks);

        // There are now multiple results! A false result means a fail Check! But it can also return a new action!
        if (result == false) {
            return;
        } else if(result instanceof Action) {
            console.debug(Rulebook.getIndentation() + "[ACTION] Instead of...");
            await result.execute();
            this.say.add(result.say);
            this.nouns = result.nouns;
            return;
        }

        /**
         * Carry Out
         */
        await Action.carry.execute({
            noun : this
        }, ...carryRulebooks);

        return this.say;
    }

    get actor(): Thing {
        return this._actor;
    }

    set actor(value: Thing) {
        this._actor = value;
    }

    public getNoun (n : number) : any {
        if (this.nouns.length > n) {
            return this.nouns[n];
        }
        return undefined;
    }

    public setNoun (n : number, noun : any) {
        while (this.nouns.length < n) {
            this.nouns.push(undefined);
        }
        this.nouns[n] = noun;
    }

    /**
     * Needs to return a string explaining what the player will do if he does this action.
     * For instance, ActionTaking should return something like return "take " + this.nouns[0].getName(),
     *   which would read as "take thing".
     *   remember that things implement PRINTABLE interface, so you can get their names.
     * @returns {string}
     */
    public getCommandText () {
        return "do";
    }

    /**
     * If an action is stopped, it means it failed so spectacularly that it didn't even begin.
     * Which means if the player is doing it, it'll not take a turn.
     */
    public stop () {
        this.requiresTurn = false;
    }
}

// Action.addCarryRule(new Rule({
//     name : "Testing say in actions rule",
//     priority : Rule.PRIORITY_LOWEST,
//     firstPriority : Rule.PRIORITY_LOWEST,
//     code : (rule, rulebook) => {
//         let action = <Action> rulebook.noun;
//         action.say.add("You do nothing all turn. What was the point, really?");
//     }
// }))

Action.check.addRule(
    new Rule({
        name : "Check any Action - Requires Noun",
        firstPriority : Rule.PRIORITY_HIGHEST,
        code : (rulebook : RulebookRunner<Action>) => {
            let action = <Action> rulebook.noun;
            if (action.getNoun(0) == undefined) {
                return false;
            }
        },
        conditions : runner => {
            return runner.noun.requiresNoun;
        }
    })
);

Action.check.addRule(
    new Rule({
        name : "Check any Action - Requires Visibility",
        code : (rulebook : RulebookRunner<Action>) => {
            let action = <Action> rulebook.noun;
            let actor = action.actor;
            if (!action.getNoun(0).isVisibleTo(actor)) {
                return false;
            }
        },
        conditions : runner => {
            return runner.noun.requiresVisibility;
        }
    })
);