/// <reference path="moodlet.ts"/>

class Mood{

	/* Mood */

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

	get mood(){
		const moodletsEffect = this.moodletsActiveEffect;
		return Math.clamp(50 + moodletsEffect,0,100);
	}
	set mood(v){
		console.warn('pc.mood(set) is deprecated, use moodletApplyById instead',State.variables.here,State.variables.callStack);
		const desiredEffect = v - this.mood;
		const time = State.variables.time;
		const effectId = 'DEPR_'+(time.now.getTime() / 60000)+'-'+Object.keys(this._moodlets).length;
		this._moodlets[effectId] = {
			effect: desiredEffect,
			expiration: time.nowWithMinutesOffset(60),
			title: 'Misc Effect'
		};
	}

	_moodlets:{[key: string]: object} = {};

	private moodlet(moodletId:string):ActiveMoodlet{
		return ActiveMoodlet.create(moodletId,this._moodlets[moodletId] ?? {});
	}


	/**
	 * All moodlets that are currently stored as active. Use moodletsActive() to get only the ones which haven't expired yet.
	 * @private
	 * @readonly
	 * @type {{[key: string]: ActiveMoodlet}}
	 */
	private get moodlets():{[key: string]: ActiveMoodlet} {
		let result = {};
		for (const [moodletId, moodletDynamicData] of Object.entries(this._moodlets)) {
			result[moodletId] = ActiveMoodlet.create(moodletId,moodletDynamicData);
		}
		let specialMoodlets = this.OWNER.moodletsSpecial;
		
		return Object.assign({},specialMoodlets,result);
	}

	
	/**
	 * The moodlets that are currently active.
	 * @readonly
	 * @type {{[key: string]: ActiveMoodlet}}
	 */
	get moodletsActive():{[key: string]: ActiveMoodlet}{
		return Object.fromEntries(
			Object.entries(this.moodlets).
				filter(
					([moodletId,moodlet])=>moodlet.isActive
				)
		);
		
		
	}

	get moodletsActiveByGroup(){
		let result = {};
		for (const [moodletId,moodletData] of Object.entries(this.moodletsActive)) {
			const moodletGroup = moodletData.groupId ?? 'none';
			result[moodletGroup] ??= {};
			result[moodletGroup][moodletId] = moodletData;
		}
		return result;
	}

	
	/**
	 * Returns the combined effect of all moodlets that are in effect.
	 * @readonly
	 * @type {number}
	 */
	get moodletsActiveEffect():number{
		return Object.values(this.moodletsActiveByGroupAccumulationApplied).reduce(
			(accumulator, currentValue) => accumulator + Object.values(currentValue).reduce(
				(innerAccumulator,innerCurrentValue) => innerAccumulator + innerCurrentValue.effect
			,0)
		,0) as number;
	}

	get moodletsActiveByGroupAccumulationApplied(){

		let activeEffectsByGroup = this.moodletsActiveByGroup;
		for (const [moodletGroupId,moodletGroup] of Object.entries(activeEffectsByGroup)) {
			if(!Object.keys(moodletGroup).length) continue;
			const groupData = MoodletGroup.get(moodletGroupId);

			
			let moodletsSortedByEffect = Object.entries(moodletGroup).map(([moodletId,moodletData])=>([moodletId,moodletData.effect ?? 0])).sort((a,b)=>b[1]-a[1]);
			switch(groupData.accumulationMode){
				case moodletAccumulationMode.ADD:
					break;
				case moodletAccumulationMode.HIGHEST:
					for(let moodletIndex = 0;moodletIndex < moodletsSortedByEffect.length; moodletIndex++){
						if(moodletIndex)
							activeEffectsByGroup[moodletGroupId][moodletsSortedByEffect[moodletIndex][0]].effect = 0;
					}
					break;
				case moodletAccumulationMode.LOWEST:
					moodletsSortedByEffect.reverse();
					for(let moodletIndex = 0;moodletIndex < moodletsSortedByEffect.length; moodletIndex++){
						if(moodletIndex)
							activeEffectsByGroup[moodletGroupId][moodletsSortedByEffect[moodletIndex][0]].effect = 0;
					}
					break;
				case moodletAccumulationMode.DIMINISHING_REVERSE:
					moodletsSortedByEffect.reverse();
				case moodletAccumulationMode.DIMINISHING:
					const diminishFactor = groupData.diminishFactor ?? 0.5;
					for(let moodletIndex = 0;moodletIndex < moodletsSortedByEffect.length; moodletIndex++){
						activeEffectsByGroup[moodletGroupId][moodletsSortedByEffect[moodletIndex][0]].effect *= Math.pow(diminishFactor,moodletIndex);
					}
					break;
				
			}
			
		}
		return activeEffectsByGroup;
	}

	
	/**
	 * Removes inactive moodlets from _moodlets
	 * @date 11/19/2023 - 11:54:52 AM
	 */
	_moodletsClean(){
		this._moodlets = Object.fromEntries(
			Object.entries(this._moodlets).
				filter(
					([moodletId,moodletData])=>ActiveMoodlet.create(moodletId,moodletData).isActive
				)
		);

	}

	moodletApplyById(moodletId:string,minutes:number=0){
		const moodletData = Moodlet.get(moodletId);
		const time = State.variables.time;
		switch(moodletData.timeMode){
			case moodletTimeMode.Continuous:
				this.moodletIncTime(moodletId,minutes);
				break;
			case moodletTimeMode.Lasting:
				this.moodletIncTime(moodletId,525600000);
				break;
			case moodletTimeMode.Resetting:
				let expirationTimeResetting = time.nowWithMinutesOffset(moodletData.duration);
				this.moodletUpdate(moodletId,{expiration: expirationTimeResetting});
				break;
		}
	}

	moodletDeactivateById(moodletId){
		delete this._moodlets[moodletId];
	}

	moodletIncTime(moodletId:string,minutes:number,skipIncludedMoodlets=false){
		const moodletData:ActiveMoodlet = this.moodlet(moodletId);
		const time = State.variables.time;
		let expiration:Date;

		if(moodletData.timeConversion)
			minutes *= moodletData.timeConversion;

		//if(!moodletData.expiration || !time.isFuture(moodletData.expiration)){
		if(!moodletData.isActive){
			expiration = time.nowWithMinutesOffset(minutes);		
		}else{
			expiration = new Date(moodletData.expiration.getTime());
			expiration.setUTCMinutes(expiration.getUTCMinutes() + minutes);
		}

		if(moodletData.maxDuration){
			let maxExpiration = time.nowWithMinutesOffset(moodletData.maxDuration);
			expiration = new Date(Math.min(expiration.getTime(),maxExpiration.getTime()));
		}

		this.moodletUpdate(moodletId,{expiration: expiration});

		if(!skipIncludedMoodlets && moodletData.includes){
			for(const includeId of moodletData.includes)
				this.moodletIncTime(includeId,minutes,true);
		}
	}

	moodletUpdate(moodletId,updateObject){
		this._moodlets[moodletId] = Object.assign({},this._moodlets[moodletId],updateObject);
	}


    constructor(moodlets={}){this._moodlets=moodlets;}

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

		return this;
	}

	clone = function () {
		return (new Mood())._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.Mood())._init($ReviveData$)', ownData);
	};
}

setup.Mood = Mood;