setup.painReasons = {
	'':{
		deterioration: 1,
		label: 'Other',
		description: 'Other reasons'
	},
	'hangover':{
		deterioration: Infinity,
		label: 'Hangover',
		description: 'You have a hangover after drinking too much.'
	},
	'piercing':{
		deterioration: 0.5,
		label: 'Piercing',
		description: 'You got a piercing.'
	},
	'tattoo':{
		deterioration: 0.3,
		label: 'Tattoo',
		description: 'You got a tattoo.'
	},

}


class Pain{
	get OWNER(){return State.variables.pc}

	
    /**
	 * Bodypart -> reason of pain -> pain value.
	 * @example ```
	 * {
	 * 	head:{
	 * 		hangover: 10,
	 * 		hit: 20
	 * 	}
	 * }
	 * ```
	 *
	 * @type {{[key: string]:{[key: string]:number}}}
	 */
	_pain:{[key: string]:{[key: string]:number}} = {
	}

	get #painRaw(){
		let result = clone(this._pain);
		let painFromPlayerEffects = this.OWNER.painOfActiveEffects;
		for(const [bodypartId, reasonData] of Object.entries(painFromPlayerEffects)){
			result[bodypartId] ??= {};
			for(const [reasonId,painValue] of Object.entries(reasonData)){
				result[bodypartId][reasonId] ??= 0;
				result[bodypartId][reasonId] += painValue;
			}
		}

		return result;
	}

	get #painReduction():number{
		return this.OWNER.painReductionOfActiveEffects;
	}

	painByBodypart(bodypartId:string):number{
		return this.reasonsByBodypartSorted(bodypartId).
				map(
					([reasonId,reasonValue])=>reasonValue
				).
				reduce(
					(accumulator, currentValue, index)=>
					accumulator + currentValue * Math.pow(0.5,index),
					0
				);
	}

	painByBodypartAndReason(bodypart:string,reason:string):number{
		return (this.#painRaw[bodypart]?.[reason] ?? 0);
	}

	reasonsByBodypartSorted(bodypartId:string):Array<[string,number]>{
		return Object.entries(this.#painRaw[bodypartId] ?? {}).
				sort(
					([reasonIdA,reasonValueA],[reasonIdB,reasonValueB])=>
					(reasonValueB-reasonValueA)
				);
	}

	

	get painByBodyparts():{[key: string]:number}{
		var result = {};
		for (const bodypart of Object.keys(this.#painRaw)) {
			let painByBodypart = this.painByBodypart(bodypart);
			if(painByBodypart > 0)
				result[bodypart] = painByBodypart;
		}
		return result;
	}

	get painByBodypartsSorted():Array<[string,number]>{
		return Object.entries(this.painByBodyparts ?? {}).
				sort(
					([bodypartIdA,bodypartValueA],[bodypartIdB,bodypartValueB])=>
					(bodypartValueB-bodypartValueA)
				);
	}

    painDeteriorate(timeToAdd:number,factorsByBodypart:{[bodypartId:string]:number}={}):void{
		for (const [bodypartId,dataByBodypart] of Object.entries(this._pain))
		{
			for (const reasonId of Object.keys(dataByBodypart)){
				const deteriorationRateByReason = setup.painReasons[reasonId].deterioration;
				const deteriorationRate = deteriorationRateByReason * this.painDeteriorationRateBase;
				this.painInc(bodypartId,reasonId,deteriorationRate * -1 * timeToAdd * (factorsByBodypart[bodypartId] ?? 1));
			}
		}
    }

	
    /**
	 * How much pain deteriotes at a given bodypart per minute.
	 * @readonly
	 * @type {number}
	 */
	get painDeteriorationRateBase(){
        return 1/60;
    }

	get painOfActiveEffects():{[key: string]:number}{
		return this.OWNER.painOfActiveEffects;
	}

	/**
	 * 
	 * @param {string} bodypart The ID of the bodypart where the pain has to be increased.
	 * @param {number} v The amount by which to increase the pain.
	 */
	painInc(bodypartId:string,reason='',increaseValue:number):number{
		let currentValue = this.painByBodypartAndReason(bodypartId,reason)
		return this.painSet(bodypartId, reason, currentValue + increaseValue);
	}

	painSet(bodypartId:string,reason='',setValue:number){
		const bodypartData = setup.getBodypart(bodypartId);
		//We do this so that calling the same bodypart with different names, such as breasts and tits, yields the same result.
		const bodypartIdFromConst = bodypartData.id;
		this._pain[bodypartId] ??= {};
		const effectiveSetValue = Math.clamp(setValue,0,100);
		this._pain[bodypartId][reason] = effectiveSetValue;
		console.log('Pain Set',bodypartId,reason,setValue);
		return effectiveSetValue;
	}

	get painTotal(){
		return this.painByBodypartsSorted.
				map(
					([bodypartId,bodypartValue])=>bodypartValue
				).
				reduce(
					(accumulator, currentValue, index)=>
					accumulator + currentValue * Math.pow(0.5,index),
					0
				) - this.#painReduction;
	}

	constructor(pain={}){this._pain=pain}

	_init(data){
		Object.keys(data).forEach(function (pn) {
			this[pn] = clone(data[pn]);
		}, this);

		return this;
	}

	clone = function () {
		return (new Pain())._init(this);
	};

	toJSON = function () {
		var ownData = {};
		Object.keys(this).forEach(function (data) {
			if(typeof this[data] !== "function")
				ownData[data] = clone(this[data]);
		}, this);
		return JSON.reviveWrapper('(new setup.Pain())._init($ReviveData$)', ownData);
	};
}

setup.Pain = Pain;