/**
 * Represents the smallest part of a content. Is a wrapper for what is being compared.
 * Holds an array of things. Only returns positive comparisons when all of them match, unless both sides only left optional nouns.
 */
class ContentAtom {
    protected nouns : Array<any> = [];

    public constructor (...nouns : Array<any>) {
        this.addNoun(...nouns);
    }

    public addNoun (...nouns : Array<any>) {
        this.nouns.push(...nouns);
        return this;
    }

    public setNouns (...nouns : Array<any>) {
        this.nouns = [];
        this.addNoun(...nouns);
        return this;
    }

    public getNouns () {
        return [...this.nouns];
    }

    public getAtomPriority () {
        let weight = 0;
        this.nouns.forEach(value => {
            weight += ContentAtom.weightNoun(value);
        });
        return weight;
    }

    /**
     * Comparisons are always made assuming we're comparing the SPECIFIC side to the VAGUE side.
     * @param other
     */
    public compareAgainst (other : ContentAtom) {
        let specificNouns = [...this.nouns];
        let vagueNouns = [...other.nouns];

        for (let i = specificNouns.length - 1; i >= 0; i--) {
            let specificNoun = specificNouns[i];
            for (let k = vagueNouns.length - 1; k >= 0; k--) {
                let vagueNoun = vagueNouns[k];
                if (ContentAtom.compareNoun(specificNoun, vagueNoun)) {
                    specificNouns.splice(i, 1);
                    vagueNouns.splice(k, 1);
                    break;
                }
            }
        }

        if (specificNouns.length == 0 && vagueNouns.length == 0) {
            return true;
        } else {
            // If any mandatory nouns were left unfilled, return false
            for (let i = 0; i < specificNouns.length; i++) {
                if (!(specificNouns[i] instanceof ContentNoun) || (<ContentNoun> specificNouns[i]).getType() == ContentNounType.MANDATORY) {
                    return false;
                }
            }

            // If any non-adaptive nouns were left on the vague side, return false
            // Adaptive nouns means the content will handle it not existing for us
            for (let i = 0; i < vagueNouns.length; i++) {
                if (!(vagueNouns[i] instanceof ContentNoun) || (<ContentNoun> vagueNouns[i]).getType() != ContentNounType.FULLY_ADAPTIVE) {
                    return false;
                }
            }

            // All tests passed, must be okay
            return true;
        }
    }

    public static compareNoun (specificNoun : any, vagueNoun : any) {
        if (specificNoun instanceof ContentNoun) {
            return specificNoun.compareAgainst(vagueNoun);
        } else if (typeof vagueNoun == "function") {
            // specific must inherit vague or be vague
            return specificNoun == vagueNoun || specificNoun instanceof vagueNoun ||
                   (typeof specificNoun == "function" && (<any> specificNoun).prototype instanceof vagueNoun)
        } else if (vagueNoun instanceof Thing) {
            // specific must be vague
            return specificNoun == vagueNoun;
        }

        // Last strict check, probably returns false every time, unless we're dealing with numbers?
        return vagueNoun === specificNoun;
    }

    public static weightNoun (noun : any) : number {
        if (noun instanceof ContentNoun) {
            // This is a collection of multiple nouns! Add the weights?
            let weight = 0;
            noun.getNouns().forEach(value => {
                weight += ContentAtom.weightNoun(value);
            });
            return weight;
        } else if (noun instanceof ContentNounSimple) {
            // It's just a marker
            return 1;
        } else {
            // Normal noun.
            if (typeof noun == "function") {
                if (<any>noun.prototype instanceof Thing) {
                    let specificity = 2; // Vague Thing
                    let parentClass = Object.getPrototypeOf(noun);
                    while (parentClass != Thing) {
                        specificity += 0.1;
                        parentClass = Object.getPrototypeOf(parentClass);
                    }
                    return specificity;
                } else {
                    return 2.5; // It's not a "Thing", so it's probably a weird class, which is kind of specific
                }
            } else if (noun instanceof Thing) {
                return 4; // Specific thing
            } else {
                return 0.5; // The fuck is this shit, probably a number or something.
            }
        }
    }
}