enum ContentGroupMatch {
    NO_MATCH,
    PARTIAL_MATCH,
    PERFECT_MATCH
}

interface ContentGroupMatchResult {
    type : ContentGroupMatch;
    unmatched : Array<ContentUnit>;
}

class ContentGroup {
    private units : Array<ContentUnit> = [];
    private matching : Array<ContentUnit>;

    constructor (...units : Array<ContentUnit>) {
        units.forEach(unit => {
            this.addUnit(unit);
        })
    }

    public addUnit (unit : ContentUnit) {
        this.units.push(unit);
        return this;
    }

    public reset () {
        this.matching = this.units.slice();
    }
	
	public isMatching () {
		return this.matching.length;
	}

    public setMatching (matching : Array<ContentUnit>) {
        this.matching = matching;
    }

    public isMatch (cg : ContentGroup) : ContentGroupMatchResult {
        let unmatched = cg.matching.slice();
        let matching = this.units.slice();

        for (let i = matching.length - 1; i >= 0; i--) {
            for (let k = unmatched.length - 1; k >= 0; k--) {
                if (matching[i].isMatch(unmatched[k])) {
                    unmatched.splice(k, 1);
                    matching.splice(i, 1);
                    break;
                }
            }
        }

        return {
            type : matching.length > 0 ? ContentGroupMatch.NO_MATCH :
                    unmatched.length == 0 ? ContentGroupMatch.PERFECT_MATCH :
                        ContentGroupMatch.PARTIAL_MATCH,
            unmatched : unmatched
        }
    }

    public getScore () {
        let score = 0;
        this.units.forEach(unit => {
            score += unit.getScore();
        });

        return Math.floor((score / this.units.length) * 10) + this.units.length; // If we keep precision too high, random won't work.
    }

    public matchAgainst (a : Array<ContentGroup>) : Array<number> {
        let matches = [];
        this.reset();
        for (let i = 0; i < a.length; i++) {
            let match = a[i].isMatch(this);
            if (match.type != ContentGroupMatch.NO_MATCH) {
                matches.push(i);
                this.setMatching(match.unmatched);
            }
            if (!this.isMatching()) {
                return matches;
            }
        }
        return undefined;
    }
}