/// <reference path="Rulebook.ts" />
/// <reference path="Rule.ts" />
/// <reference path="Things/Person.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 aggressivenessRating = 1;
    public actingSubmissively = false;
    public requiresTurn = true;
    public requiresNoun = true;
    public requiresVisibility = true; // First noun must be visible and in the same room
    public allowedStances = [PersonStance.ALLFOURS, PersonStance.STANDING];

    public finalSay : Say;
    public finalSayOnEnd = true;


    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) {
            if (this.requiresTurn) {
                /**
                 * Add to the list of actions performed in turn.
                 * Notice the position of this thing: If the action failed, but an attempt was made, it still gets added to the board.
                 * The importance of ths is, if you do something to annoy an NPC and you get stopped midway, they still get to react.
                 */
                TurnSequence.addAction(this);
            }
            return;
        } else if(result instanceof Action) {
            console.debug(Rulebook.getIndentation() + "[ACTION] Instead of...");
            let originalNouns = this.nouns;
            await result.execute();
            this.say.add(result.say);
            this.nouns = result.nouns;
            // Reset to initial state
            this.nouns = originalNouns;
            return;
        }

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

        /**
         * Add to the list of actions performed in turn.
         * Notice the position of this thing: if an action was replaced by another, it will instead add that action.
         */
        TurnSequence.addAction(this);

        if (this.finalSay != undefined) {
            if (this.finalSayOnEnd) {
                this.say.add(...this.finalSay.sequence);
            } else {
                this.say = new Say(...this.finalSay.sequence, ...this.say.sequence);
            }
        }

        return this.say;
    }

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

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

    // TODO: The if shouldn't be necessary right? I don't wanna touch this right now.
    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 - Stance",
        firstPriority : Rule.PRIORITY_HIGHEST,
        code : (rulebook : RulebookRunner<Action>) => {
            let action = <Action> rulebook.noun;
            let actor = action.actor;
            if (actor instanceof Person) {
                if (action.allowedStances.indexOf(actor.stance) == -1) {
                    if (action.actor == WorldState.player) {
                        action.say = new Say("You can't do that while ", PersonStanceNames[actor.stance], ", you need to be ", PersonStanceNames[action.allowedStances[0]], ".");
                    }
                    action.stop();
                    return false;
                }
            }
        }
    })
);

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;
        }
    })
);

Action.carry.addRule(
    new Rule({
        name : "Check any Action - Angery",
        firstPriority : Rule.PRIORITY_LOWEST,
        priority: Rule.PRIORITY_LOWEST,
        code : async (rulebook : RulebookRunner<Action>) => {
            let action = <Action> rulebook.noun;
            let target = action.getNoun(0);
            let tai = (<Person> target).AI;
            await tai.getPoked(action);
        },
        conditions : (rulebook : RulebookRunner<Action>) => {
            let action = <Action> rulebook.noun;
            return action.actingAgressively && action.getNoun(0) instanceof Person;
        }
    })
);

Action.carry.addRule(
    new Rule({
        name : "Carry any Action - Interrupt through Aggressive",
        firstPriority : Rule.PRIORITY_LOWEST,
        priority: Rule.PRIORITY_LOWEST,
        code : async (rulebook : RulebookRunner<Action>) => {
            await Elements.CurrentTurnHandler.printAsContent(new Say("Interrupted."));
            TurnSequence.repeatedAction = undefined;
        },
        conditions : (rulebook : RulebookRunner<Action>) => {
            let action = <Action> rulebook.noun;
            return action.actingAgressively && WorldState.isPlayer(action.getNoun(0));
        }
    })
);