/// <reference path="../Thing.ts" />
interface BodypartValueResult {
    value : number;
    weight : number;
}

class Bodypart extends Thing {
    public static WEIGHT_LOWEST = 1;
    public static WEIGHT_LOW = 3;
    public static WEIGHT_MEDIUM = 5;
    public static WEIGHT_HIGH = 7;
    public static WEIGHT_HIGHEST = 9;

    public static SLUTTINESS_LOWEST_SAINTLY = 0;
    public static SLUTTINESS_LOW_PRUDE = 25;
    public static SLUTTINESS_MEDIUM_AVERAGE = 50;
    public static SLUTTINESS_HIGH_SLUT = 75;
    public static SLUTTINESS_HIGHEST_WHORE = 100;

    public static GENDER_LOWEST_MANLIEST = 0;
    public static GENDER_LOW_MANLY = 25;
    public static GENDER_MEDIUM_ANDROGYNE = 50;
    public static GENDER_HIGH_FEMININE = 75;
    public static GENDER_HIGHEST_FEMININEST = 100;

    // The higher this number, the more the bodypart will be worth for health.
    // For instance, HEAD should be worth more than FINGERNAIL.
    public sorenessWeight : number = 1;

    // 0 to 10
    // says how fucked up the bodypart is
    public soreness : number = 0;

    // How much soreness is healed every turn
    public sorenessPerTurn : number = 0.05;

    // Last turn soreness got updated
    public lastSorenessUpdate : number = 0;

    public constructor (options? : ThingOptions) {
        super(options);
        this.addGetAlterations((bp : Bodypart) => {
            return {
                Soreness : bp.soreness,
                GenderValue : bp.genderValue,
                Sluttiness : bp.getSluttiness()
            }
        });

        this.addSetAlterations((bp : Bodypart, changes) => {
            this.soreness = changes.Soreness;
            this.genderValue = changes.GenderValue;
            this.sluttiness = changes.Sluttiness;
        });
    }

    public updateSoreness () {
        let cTurn = WorldState.getCurrentTurn();
        if (cTurn > this.lastSorenessUpdate) {
            if (this.soreness > 0) {
                this.soreness -= (this.sorenessPerTurn * (cTurn - this.lastSorenessUpdate));

                if (this.soreness < 0) {
                    this.soreness = 0;
                }
            }
            this.lastSorenessUpdate = cTurn;
        }
    }

    public changeSoreness (soreness : number) {
        this.updateSoreness();
        this.soreness += soreness;
        if (this.soreness < 0) {
            this.soreness = 0;
        }
    }

    public getSoreness () {
        this.updateSoreness();
        return this.soreness;
    }


    public getWeightedSoreness() {
        return this.getSoreness() * this.sorenessWeight;
    }

    public getSorenessWeight () {
        return this.sorenessWeight;
    }

    // These are the slots the bodypart is visible on
    // Should use Humanoid.SLOT_* !
    public slots : Array<number> = [];
    public visibleSlots : Array<number> = [];

    // GenderValue is how masculine/feminine this bodypart is.
    // At 0 = the most masculine possible, at 100 = the most feminine eveter
    // 50 would be completely androgynous
    public genderValue : number = Bodypart.GENDER_MEDIUM_ANDROGYNE;

    // Like with health, the weight makes this bodypart matter more for deciding gender.
    // Gender presentation is not about prettiness, so, for instance, having huge breasts will
    // strongly push you to the "is a woman" side, even if you're manly as fuck everywhere else.
    // If you're too manly everywhere else, though, you'll probably end up closer on the spectrum to male
    // or at least androgynous, which might result in not passing.
    public genderWeight : number = 1;

    /**
     * Sluttiness goes from 0 to 100.
     * A bodypart's sluttiness is dependent on it being seen.
     * @type {number}
     */
    public sluttiness : number = 10;

    public sluttinessWeight : number = Bodypart.WEIGHT_LOWEST;

    public getGenderWeight () {
        if (this.slots.length == 0) {
            return 0;
        }
        return this.genderWeight * (this.visibleSlots.length / this.slots.length);
    }

    public getGenderValue () {
        return this.genderValue;
    }

    public getWeightedGenderValue () {
        return this.getGenderValue() * this.getGenderWeight();
    }

    public getSluttiness() {
        return this.sluttiness;
    }

    public getSluttinessWeight () {
        if (this.slots.length == 0) {
            return 0;
        }
        return this.sluttinessWeight * (this.visibleSlots.length / this.slots.length);
    }

    public getWeightedSluttinessValue () {
        return this.getSluttiness() * this.getSluttinessWeight();
    }

    public updateVisibility () {
        this.visibleSlots = this.slots.slice(0);
        let parent = Thing.PartRelation.getLeft(this);
        if (parent != undefined) {
            let clothing = <Array<Clothing>>Thing.WearRelation.getRight(parent);
            for (let i = 0; i < clothing.length; i++) {
                let covering = clothing[i].getCoveringSlots();
                for (let k = 0; k < covering.length; k++) {
                    let idx = this.visibleSlots.indexOf(covering[k]);
                    if (idx >= 0) {
                        this.visibleSlots.splice(idx, 1);
                    }
                }

                if (this.visibleSlots.length == 0) break;
            }
        }
    }

    public isUncovered () {
        this.updateVisibility();
        return this.visibleSlots.length == this.slots.length && this.slots.length > 0;
    }

    public updateStatus () {
        this.updateVisibility();
    }

    public static getSoreness (thing : Thing) {
        let bodyparts = Thing.PartRelation.getRightType(thing, Bodypart);
        let soreness = 0;
        bodyparts.forEach((bodypart : Bodypart) => {
            soreness += bodypart.getWeightedSoreness();
        });
        return soreness;
    }

    public static getGenderValueOn (thing : Thing) : BodypartValueResult {
        let weight = 0;
        let value = 0;

        let bodyparts = Thing.PartRelation.getRightType(thing, Bodypart);
        bodyparts.forEach((bodypart : Bodypart) => {
            weight += bodypart.getGenderWeight();
            value += bodypart.getWeightedGenderValue();
        });

        return {
            weight : weight,
            value : value
        };
    }

    public static getSluttinessValueOn (thing : Thing) : BodypartValueResult {
        let weight = 0;
        let value = 0;

        let bodyparts = Thing.PartRelation.getRightType(thing, Bodypart);
        bodyparts.forEach((bodypart : Bodypart) => {
            weight += bodypart.getSluttinessWeight();
            value += bodypart.getWeightedSluttinessValue();
        });

        return {
            weight : weight,
            value : value
        };
    }

    /**
     * Changes the bodypart so that it matches the desired genderValue.
     * This needs to be implemented in all classes inheriting from Bodypart, as not all bodyparts have a simple "genderValue" to assign.
     * @param {number} genderValue
     */
    public arrangeGenderValue (genderValue : number) {
        this.genderValue = genderValue;
        (<Humanoid> this.getPartOne()).invalidateCaches();
    }

    /**
     * Attempts to increase Femininity by 5 * amount. Will read Current Gender Value and try to reassign it.
     * @param {number} amount
     */
    public increaseFemininity (amount : number) {
        let currentGV = this.getGenderValue();
        this.arrangeGenderValue(currentGV + (5 * amount));
    }


    /**
     * Attempts to increase Masculinity by 5 * amount. Will read Current Gender Value and try to reassign it.
     * @param {number} amount
     */
    public increaseMasculinity (amount : number) {
        let currentGV = this.getGenderValue();
        this.arrangeGenderValue(currentGV - (5 * amount));
    }
}