import { M5Attribute, M5AttributeCalculated, M5Attributes, M5CharacterCalculatedData, M5ItemMod, M5ModOperation, M5ModResult, M5ModSource, M5ModType, M5Stats, M5ModPair } from "../M5Base";

export default class M5ModAggregate {
	private attributes = new Map<string, Array<M5ModPair>>();
	private stats = new Map<string, Array<M5ModPair>>();
	private skills = new Map<string, Array<M5ModPair>>();

	constructor(public data: any, public calc: M5CharacterCalculatedData) {
		const characterString = (game as Game).i18n.localize("TYPES.Actor.character");

		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.ST, operation: M5ModOperation.SET, value: data.attributes.st.value }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.GS, operation: M5ModOperation.SET, value: data.attributes.gs.value }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.GW, operation: M5ModOperation.SET, value: data.attributes.gw.value }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.KO, operation: M5ModOperation.SET, value: data.attributes.ko.value }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.IN, operation: M5ModOperation.SET, value: data.attributes.in.value }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.ZT, operation: M5ModOperation.SET, value: data.attributes.zt.value }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.AU, operation: M5ModOperation.SET, value: data.attributes.au.value }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.PA, operation: M5ModOperation.SET, value: data.attributes.pa.value }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.WK, operation: M5ModOperation.SET, value: data.attributes.wk.value }, characterString);

		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.ST, operation: M5ModOperation.ADD_100, value: data.attributes.st.bonus }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.GS, operation: M5ModOperation.ADD_100, value: data.attributes.gs.bonus }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.GW, operation: M5ModOperation.ADD_100, value: data.attributes.gw.bonus }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.KO, operation: M5ModOperation.ADD_100, value: data.attributes.ko.bonus }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.IN, operation: M5ModOperation.ADD_100, value: data.attributes.in.bonus }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.ZT, operation: M5ModOperation.ADD_100, value: data.attributes.zt.bonus }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.AU, operation: M5ModOperation.ADD_100, value: data.attributes.au.bonus }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.PA, operation: M5ModOperation.ADD_100, value: data.attributes.pa.bonus }, characterString);
		this.push({ type: M5ModType.ATTRIBUTE, id: M5Attributes.WK, operation: M5ModOperation.ADD_100, value: data.attributes.wk.bonus }, characterString);

		this.push({ type: M5ModType.STAT, id: M5Stats.DEFENSE, operation: M5ModOperation.SET, value: calc.stats.defenseBonus.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.ATTACK, operation: M5ModOperation.SET, value: calc.stats.attackBonus.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.DAMAGE, operation: M5ModOperation.SET, value: calc.stats.damageBonus.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.MOVEMENT, operation: M5ModOperation.SET, value: calc.stats.movement.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.RESISTANCE_MIND, operation: M5ModOperation.SET, value: calc.stats.resistanceMind.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.RESISTANCE_BODY, operation: M5ModOperation.SET, value: calc.stats.resistanceBody.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.SPELL_CASTING, operation: M5ModOperation.SET, value: calc.stats.spellCasting.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.BRAWL, operation: M5ModOperation.SET, value: calc.stats.brawl.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.POISON_RESISTANCE, operation: M5ModOperation.SET, value: calc.stats.poisonResistance.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.LP, operation: M5ModOperation.SET, value: calc.stats.lp.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.AP, operation: M5ModOperation.SET, value: calc.stats.ap.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.PROTECTION_LP, operation: M5ModOperation.SET, value: calc.stats.lpProtection.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.PROTECTION_AP, operation: M5ModOperation.SET, value: calc.stats.apProtection.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.PERCEPTION, operation: M5ModOperation.SET, value: calc.stats.perception.value }, characterString);
		this.push({ type: M5ModType.STAT, id: M5Stats.DRINKING, operation: M5ModOperation.SET, value: calc.stats.drinking.value }, characterString);
	}

	push(mod: M5ItemMod, source: string) {
		if (!mod?.id || mod.id === "") return;

		let map: Map<string, Array<M5ModPair>> = null;
		if (mod.type === M5ModType.ATTRIBUTE) map = this.attributes;
		else if (mod.type === M5ModType.STAT) map = this.stats;
		else if (mod.type === M5ModType.SKILL) map = this.skills;

		if (map) {
			const pair: M5ModPair = {
				mod: mod,
				source: source,
			};

			if (map.has(mod.id)) map.get(mod.id).push(pair);
			else map.set(mod.id, [pair]);
		}
	}

	calculate() {
		const calc = this.calc;

		this.attributes.forEach((pairs, id) => {
			const res = M5ModAggregate.processPairs(pairs);
			calc.attributes[id] = {
				value: res.value,
				bonus: M5ModAggregate.attributeBonus(res.value),
				mods: res.mods,
			} as M5AttributeCalculated;
			//console.log("calc.attributes." + id, calc.attributes[id])
		});

		this.stats.forEach((pairs, id) => {
			const res = M5ModAggregate.processPairs(pairs);
			calc.stats[id] = res;
		});

		const ret = {};
		this.skills.forEach((pairs, id) => {
			ret[id] = pairs;
		});

		return ret;
	}

	static pairAsSource(pair: M5ModPair): M5ModSource {
		return {
			operation: pair.mod.operation,
			value: pair.mod.value,
			item: pair.source,
		};
	}

	static processPairs(arr: Array<M5ModPair>): M5ModResult {
		let ret: M5ModResult = {
			mods: [],
			value: 0,
		};

		let mods = arr.filter((pair) => pair.mod.operation === M5ModOperation.FIXED).sort((a, b) => b.mod.value - a.mod.value);
		let pair = mods.length === 0 ? null : mods[0];

		if (pair) {
			ret.mods.push(this.pairAsSource(pair));
			ret.value = pair.mod.value;
		} else {
			mods = arr.filter((pair) => pair.mod.operation === M5ModOperation.SET).sort((a, b) => b.mod.value - a.mod.value);
			if (mods.length !== 0) {
				ret.mods.push(this.pairAsSource(mods[0]));
				ret.value = mods[0].mod.value;
			}

			mods = arr.filter((pair) => pair.mod.operation === M5ModOperation.ADD_100);
			if (mods.length !== 0) {
				ret.mods = ret.mods.concat(mods.map(this.pairAsSource));
				const bonus = mods.map((p) => p.mod.value).reduce((a, b) => a + b, 0);
				ret.value = Math.min(100, Math.max(0, ret.value + bonus));
			}

			mods = arr.filter((pair) => pair.mod.operation === M5ModOperation.ADD);
			if (mods.length !== 0) {
				ret.mods = ret.mods.concat(mods.map(this.pairAsSource));
				const bonus = mods.map((p) => p.mod.value).reduce((a, b) => a + b, 0);
				ret.value = Math.max(0, ret.value + bonus);
			}

			mods = arr.filter((pair) => pair.mod.operation === M5ModOperation.SUBTRACT);
			if (mods.length !== 0) {
				ret.mods = ret.mods.concat(mods.map(this.pairAsSource));
				const bonus = mods.map((p) => p.mod.value).reduce((a, b) => a + b, 0);
				ret.value = ret.value - bonus;
			}

			mods = arr.filter((pair) => pair.mod.operation === M5ModOperation.MULTIPLY);
			if (mods.length !== 0) {
				ret.mods = ret.mods.concat(mods.map(this.pairAsSource));
				const bonus = mods.map((p) => p.mod.value).reduce((a, b) => a * b, 1);
				ret.value = Math.max(0, ret.value * bonus);
			}

			mods = arr.filter((pair) => pair.mod.operation === M5ModOperation.DIVISION);
			if (mods.length !== 0) {
				ret.mods = ret.mods.concat(mods.map(this.pairAsSource));
				const bonus = mods.map((p) => p.mod.value).reduce((a, b) => a * b, 1);
				ret.value = Math.max(0, Math.floor(ret.value / bonus));
			}
		}

		return ret;
	}

	static attributeMinMax(attribute: M5Attribute) {
		return Math.min(100, Math.max(0, attribute.value + attribute.bonus));
	}

	static attributeBonus(value: number) {
		if (value > 95) return 2;
		if (value > 80) return 1;
		if (value > 20) return 0;
		if (value > 5) return -1;
		return -2;
	}

	//static modToString(mod: M5ItemMod): string { }
}
