class DialogueTree {
    public id : string;
    private nodes : {[id : string] : DialogueNode} = {};
    public startNode : DialogueNode = undefined;
    private repeatChoices : boolean = true;
    private lastPrintedChoice : Array<HTMLElement>;

    public constructor (id : string) {
        this.id = id;
    }

    public addNode (node : DialogueNode) {
        this.nodes[node.id] = node;
        if (node.type == NodeType.Node) {
            this.nodes[node.name] = node; // This is a label!
        }
    }

    public addStartNode (node : DialogueNode) {
        this.addNode(node);
        this.startNode = node;
    }

    public getNode (id : string) {
        return this.nodes[id];
    }

    public getNext (node : DialogueNode) {
        let next = node.getNext();
        if (next != undefined) {
            return this.getNode(next);
        }
    }

    public setRepeatChoices (doIt : boolean) {
        this.repeatChoices = doIt;
    }

    public async execute (startId? : string) {

        console.debug(Rulebook.getIndentation() + "[DialogueTree] Running " + this.id);
        Rulebook.increaseIndentation(this);

        let node : DialogueNode;
        if (startId == undefined) {
            node = this.startNode;
        } else {
            node = this.getNode(startId);
        }
        if (node == undefined) {
            Elements.CurrentTurnHandler.printAsError("Unable to start dialogue " + this.id + ": A starting node could not be found.");
            return;
        }

        let previousNode : DialogueNode;
        while (node != undefined) {
            let nextNode = await this.processNode(node, previousNode);
            previousNode = node;
            node = nextNode;
        }
        Rulebook.decreaseIndentation();
    }

    public async processNode (node : DialogueNode, previousNode : DialogueNode) {
        console.debug(Rulebook.getIndentation() + "[" + node.type + "] " + node.id);
        Rulebook.increaseIndentation(node);
        let doChoices = node.hasChoices();

        if (node.type == NodeType.Tree) {
            await (<DialogueNodeTree> node).tree().execute();
        } else if (node.type == NodeType.Text) {
            let say = (<DialogueText> node).getSay();
            Elements.CurrentTurnHandler.printAsContent(say);
        } else if (node.type == NodeType.Set) {
            let runningSet = (<DialogueSet> node).run();
            if (runningSet instanceof Promise) {
                await runningSet;
            }
        }

        if (doChoices) {
            // Next node is decided by the choices
            let branchingDialogue = new BranchingDialogue();
            let choices = node.choices;
            let options = [];
            for (let i = 0; i < choices.length; i++) {
                let choice = <DialogueChoice> this.getNode(choices[i]);
                let branchingOption = new BranchingOption(choice.getSay(), choice.isAvailable());
                options.push(branchingOption);
                branchingDialogue.addOptions(branchingOption);
            }
            let chosenOption : BranchingOption = await branchingDialogue.getChosenOption();
            let chosenNodeId = choices[options.indexOf(chosenOption)];
            let chosenNode = <DialogueChoice> this.getNode(chosenNodeId);

            if (this.repeatChoices) {
                let say = new Say(new SayBold(" > ", chosenNode.getSay()));
                this.lastPrintedChoice = await Elements.CurrentTurnHandler.getSayElementsAsContent(say);
                await Elements.CurrentTurnHandler.print(...this.lastPrintedChoice);
            }

            console.debug(Rulebook.getIndentation() + "[Choice] Picked " + chosenNodeId);
            console.debug(Rulebook.getIndentation() + "[Choice] Going to " + chosenNode.getNext());
            Rulebook.increaseIndentation(chosenNodeId);
            Rulebook.decreaseIndentation();

            Rulebook.decreaseIndentation();
            return this.getNext(chosenNode);
        } else {
            Rulebook.decreaseIndentation();
            return this.getNext(node);
        }
    }

    public unprintLastChoice () {
        Elements.CurrentTurnHandler.unprint(...this.lastPrintedChoice);
    }
}