var MATTIE = MATTIE || {};
MATTIE.multiplayer = MATTIE.multiplayer || {};
MATTIE.multiplayer.devTools = {};
MATTIE.multiplayer.enemyEmitter = {};
MATTIE.multiplayer.currentBattleEnemy = MATTIE.multiplayer.currentBattleEnemy || {};
MATTIE.multiplayer.combatEmitter = MATTIE.multiplayer.combatEmitter || {};
/** the max amount of time to spend in the sync state in ms */
MATTIE.multiplayer.combatEmitter.maxSyncTime = 1500;
MATTIE.multiplayer.combatEmitter.minSyncTime = 500;
MATTIE.BattleManagerStartTurn = BattleManager.startTurn;

MATTIE.multiplayer.combatEmitter.netExTurn = false;
MATTIE.multiplayer.ready = false;
MATTIE.multiplayer.waitingOnAllies = false;

/** log info with the proper conditionals */
function BattleLog(str) {
	if (MATTIE.multiplayer.devTools.inBattleLogger) console.info(str);
}

MATTIE.multiplayer.battleManagerInit = BattleManager.initMembers;
BattleManager.initMembers = function () {
	MATTIE.multiplayer.battleManagerInit.call(this);
	this._netActors = [];
};

BattleManager.getNetBattlers = function () {
	if (!this._netActors) this._netActors = [];
	return this._netActors;
};

BattleManager.startAfterReady = function () {
	MATTIE.BattleManagerStartTurn.call(this);
};

/** exit the ready state, returning to the beginning of the input phase
 * TODO: instead of returning to the start of the input phase return to the last input so that players can easily correct mistakes without having to...
 *
*/
BattleManager.unready = function () {
	BattleManager.startInput();
	this._phase = 'input';
	BattleLog('unready!');
	MATTIE.multiplayer.BattleController.emitUnreadyEvent();
};

/** enter the ready state */
BattleManager.ready = function () {
	this._phase = 'ready';

	BattleLog('ready!');
	MATTIE.multiplayer.BattleController.emitReadyEvent(JSON.stringify(this.getAllPlayerActions()));
};

/** enter the syncing state */
BattleManager.sync = function () {
	this._phase = 'syncing';
	BattleLog('syncing');
	setTimeout(() => {
		this.doneSyncing(); // enter done syncing state after 5 seconds
	}, MATTIE.multiplayer.combatEmitter.maxSyncTime);
};

/** enter the done syncing state */
BattleManager.doneSyncing = function () {
	if (this._phase == 'syncing') {
		this._phase = 'doneSyncing';
		BattleLog('donesyncing');
	}
};

/** check if a battler exists in the local party */
Game_Battler.prototype.battlerInParty = function () {
	return $gameParty.battleMembers().contains(this);
};

/** finds all currently inputted actions for the local party and returns an array of them */
BattleManager.getAllPlayerActions = function () {
	const arr = [];
	this.makeActionOrders();
	this._actionBattlers.forEach((battler) => {
		if (battler.isAlive()) {
			/** @type {Game_Action} */
			const action = battler.currentAction();
			if (action) { // only do stuff if the action exists
				if (action.item() != null) {
					action.setNetTarget(MATTIE.multiplayer.getCurrentNetController().peerId);
					action.preloadRng(action.makeTargets());
					if (MATTIE.multiplayer.pvp.inPVP) {
						const targetTroopId = $gameTroop.mapMemberIndexToTroopId(action._targetIndex);
						const targetActorId = MATTIE.multiplayer.pvp.PvpController.mapTroopToActor(targetTroopId);
						action.targetActorId = targetActorId;
						action.userBattlerIndex = battler.partyIndex();
					}
					arr.push(action);
				}
			}
		}
	});
	return (arr);
};

Game_Action.prototype.forceHit = function (obj) {
	if (typeof obj != 'undefined') {
		this._forcedHit = obj.res;
		this._preloadMissed = obj.miss;
		this._preloadEvade = obj.evade;
	} else {
		this._forcedHit = undefined;
		this._preloadMissed = undefined;
		this._preloadEvade = undefined;
	}
};

Game_Action.prototype.makeTargetResultsId = function (target, id = MATTIE.multiplayer.getCurrentNetController().peerId) {
	return `${target.name() + (this.subject().isActor() ? `${this.subject()._classId}-` : '')}-${this._targetIndex}-${this._item._itemId}-`;
};
/**
 *
 * @param {Game_Battler[]} targets
 */
Game_Action.prototype.preloadRng = function (targets) {
	BattleManager.targetResults = {};
	this.targetResults = {};
	targets.forEach((target) => {
		this._preloadMissed = (Math.random() >= this.itemHit(target));
		this._preloadEvade = (!this._preloadMissed && Math.random() < this.itemEva(target));
		const crit = (Math.random() < this.itemCri(target));
		this.targetResults[this.makeTargetResultsId(target)] = {};
		this.targetResults[this.makeTargetResultsId(target)].crit = crit;
		this.targetResults[this.makeTargetResultsId(target)].evade = this._preloadEvade;
		this.targetResults[this.makeTargetResultsId(target)].miss = this._preloadMissed;
		this.targetResults[this.makeTargetResultsId(target)].res = (!this._preloadMissed && !this._preloadEvade);
		this.targetResults[this.makeTargetResultsId(target)].dmg = this.makeDamageValue(target, crit);

		if (MATTIE.multiplayer.pvp.inPVP) {
			if (this.targetResults[this.makeTargetResultsId(target)].dmg >= target.hp) this.killingBlow = true;
			this.targetName = target.name();
			// update dmg vals for pvp

			const originalTarget = target;
			const originalTargetName = target.name();
			const baseDmg = this.targetResults[this.makeTargetResultsId(target)].dmg;
			atkScaler = 0.05;

			// limbs only do 40% dmg
			this.targetResults[this.makeTargetResultsId(target)].dmg = Math.ceil(MATTIE.util.clamp(baseDmg * atkScaler * 0.4, 6, 89));

			if (originalTargetName.toLowerCase().includes('torso')) {
				this.targetResults[this.makeTargetResultsId(target)].dmg = Math.ceil(MATTIE.util.clamp(baseDmg * atkScaler, 6, 89));
				// battler.enemy().params[2] = MATTIE.util.clamp(actor.atk * atkScaler, 4, 100);// min damage 4
			} else if (originalTargetName.toLowerCase().includes('head')) {
				// battler.enemy().params[2] = MATTIE.util.clamp(actor.atk * atkScaler * 3, 4, 100);// 1.5x dmg to head
				// head does 3x dmg
				this.targetResults[this.makeTargetResultsId(target)].dmg = Math.ceil(MATTIE.util.clamp(baseDmg * atkScaler * 3, 6, 99));
			}
		}
	});
	BattleManager.targetResults = Object.assign(BattleManager.targetResults || {}, this.targetResults);
};

Game_Action.prototype.loadRng = function (results, id) {
	this._id = id;
	this.targetResults = Object.assign(this.targetResults || {}, results);
};

Game_Action.prototype.getTargetResults = function () {
	return this.targetResults || BattleManager.targetResults;
};

MATTIE.multiplayer.Game_Action_Apply = Game_Action.prototype.apply;
Game_Action.prototype.apply = function (target) {
	MATTIE.multiplayer.Game_Action_Apply.call(this, target);
};
MATTIE.multiplayer.Game_Action_testApply = Game_Action.prototype.testApply;
Game_Action.prototype.testApply = function (target) {
	const targets = this.getTargetResults();
	if (targets) {
		if (Object.keys(targets).includes(this.makeTargetResultsId(target))) {
			if (target.result().forceHit) target.result().forceHit(targets[this.makeTargetResultsId(target)]);
		} else if (target) if (target.result()) if (target.result().forceHit) target.result().forceHit(undefined);
	}
	return MATTIE.multiplayer.Game_Action_testApply.call(this, target);
};

/** @description the base make damage value function for a game action */
MATTIE.multiplayer.Game_ActionmakeDamageValue = Game_Action.prototype.makeDamageValue;
Game_Action.prototype.makeDamageValue = function (target, critical) {
	const targets = this.getTargetResults();
	if (targets) {
		if (Object.keys(targets).includes(this.makeTargetResultsId(target))) {
			if (target.result().forceHit) {
				if (targets[this.makeTargetResultsId(target)].crit) critical = targets[this.makeTargetResultsId(target)].crit;
				if (targets[this.makeTargetResultsId(target)].dmg) return targets[this.makeTargetResultsId(target)].dmg;
			}
		}
	}
	return MATTIE.multiplayer.Game_ActionmakeDamageValue.call(this, target, critical);
};

/** @description extend the is hit function to include if it is being forced to hit */
MATTIE.multiplayer.Game_ActionResultisHit = Game_ActionResult.prototype.isHit;
Game_ActionResult.prototype.isHit = function () {
	if (typeof this._forcedHit != 'undefined') {
		this.missed = this._preloadMissed;
		this.evaded = this._preloadEvade;
		return this._forcedHit && !this.missed && !this.evaded;
	}
	return (MATTIE.multiplayer.Game_ActionResultisHit.call(this));
};
/** @description set a action result as forced hit */
Game_ActionResult.prototype.forceHit = function (obj) {
	if (typeof obj != 'undefined') {
		this._forcedHit = obj.res;
		this._preloadMissed = obj.miss;
		this._preloadEvade = obj.evade;
	} else {
		this._forcedHit = undefined;
		this._preloadMissed = undefined;
		this._preloadEvade = undefined;
	}
};

Game_Action.prototype.setNetTarget = function (peerId) {
	this._netTarget = peerId;
};
MATTIE.multiplayer.gameActonSubject = Game_Action.prototype.subject;
Game_Action.prototype.subject = function () {
	if (this._netTarget) {
		if (this._netTarget != MATTIE.multiplayer.getCurrentNetController().peerId) {
			return MATTIE.multiplayer.getCurrentNetController().netPlayers[this._netTarget].$netActors.dataActor(this._subjectActorId);
		}
	}
	if (this._subjectActorId > 0) {
		return $gameActors.actor(this._subjectActorId);
	}
	return $gameTroop.members()[this._subjectEnemyIndex];
};

/** start the combat round */
BattleManager.startTurn = function () {
	if ($gameTroop.totalCombatants() == 1) { // if solo, start next phase
		MATTIE.BattleManagerStartTurn.call(this);
	} else { // if the player is fighting with allies enter "ready" state
		this.ready();
	}
};
BattleManager.getNextSubject = function () {
	if ($gameTroop.turnCount() <= 0) return null;
	this._performedBattlers = this._performedBattlers || [];
	this.makeActionOrders();
	for (;;) {
		var battlerArray = [];
		for (var i = 0; i < this._actionBattlers.length; ++i) {
			var obj = this._actionBattlers[i];
			if (!this._performedBattlers.contains(obj)) battlerArray.push(obj);
		}
		this._actionBattlers = battlerArray;
		var battler = this._actionBattlers.shift();
		if (!battler) return null;
		if (battler.isAlive()) {
			this._performedBattlers.push(battler);
			return battler;
		}
	}
};
MATTIE.multiplayer.battlemanageronStart = BattleManager.startBattle;
BattleManager.startBattle = function () {
	MATTIE.multiplayer.combatEmitter.netExTurn = false;
	MATTIE.multiplayer.ready = false;
	MATTIE.multiplayer.waitingOnAllies = false;
	MATTIE.multiplayer.battlemanageronStart.call(this);
	this._netActors = [];
};

BattleManager.addNetActionBattler = function (battler, isExtraTurn) {
	if (!this._netActionBattlers) this._netActionBattlers = [];
	const netActionBattler = {};
	netActionBattler.battler = battler;
	netActionBattler.isExtraTurn = isExtraTurn;
	this._netActionBattlers.push(netActionBattler);
};

BattleManager.clearNetActionBuffer = function () {
	this._netActionBattlers = [];
};

/**
   * @description this function handles where enemies and local player attack in combat
   * @returns int
   */
Game_Battler.prototype.partyIndex = function () {
	const indexof = $gameParty.battleMembers().indexOf(this);
	return indexof != -1 ? indexof : MATTIE.multiplayer.config.scaling.enemyBattleIndex(this);
};

MATTIE.multiplayer.multiCombat.makeActionOrders = BattleManager.makeActionOrders;
BattleManager.makeActionOrders = function () {
	if (!this._netActionBattlers) this._netActionBattlers = [];
	MATTIE.multiplayer.multiCombat.makeActionOrders.call(this);
	const currentNetBattlers = this._netActionBattlers.filter((netBattler) => {
		if (Galv.EXTURN.active || MATTIE.multiplayer.combatEmitter.netExTurn) {
			return netBattler.isExtraTurn;
		}
		return true;
	}).map((netBattler) => netBattler.battler);
	let battlers = [];
	if (MATTIE.multiplayer.combatEmitter.netExTurn) {
		battlers = currentNetBattlers;
	} else {
		battlers = this._actionBattlers.concat(currentNetBattlers);
	}

	battlers.forEach((battler) => {
		battler.makeSpeed();
	});
	battlers.sort((a, b) => {
		let val = 0;
		val = a.partyIndex() - b.partyIndex(); // sort my index in order of decreasing
		if (val === 0) { // if index is same sort by if the battler is a player
			val = (a instanceof Game_Actor ? 1 : 0) - (b instanceof Game_Actor ? 1 : 0);
		}
		if (val === 0) { // if both palyers sort by speed
			val = b.speed() - a.speed();
		}

		if (val === 0) { // finnally sort by id
			if (a.isActor() && b.isActor()) {
				val = (b.peerId || b.netID).localeCompare((a.peerId || a.netID));
			}
		}

		return val;
	});

	this._actionBattlers = battlers;
};

Game_Battler.prototype.setCurrentAction = function (action) {
	this.forceAction(action._item._itemId, action._targetIndex, action.forcedTargets);
	this._actions[this._actions.length - 1]._netTarget = action._netTarget;
	this._actions[this._actions.length - 1].loadRng(action.targetResults);
	this._actions[this._actions.length - 1].cb = action.cb;

	console.log(action.forcedTargets);
	if (action._item._dataClass === 'item') this._actions[this._actions.length - 1].setItem(action._item._itemId);
};
MATTIE.forceAction = Game_Battler.prototype.forceAction;
Game_Battler.prototype.forceAction = function (skillId, targetIndex, forcedTargets = []) {
	if (forcedTargets.length > 0) this.forcedTargets = forcedTargets;
	MATTIE.forceAction.call(this, skillId, targetIndex);
	if (forcedTargets.length > 0) this._actions[this._actions.length - 1].forcedTargets = forcedTargets;
};

MATTIE.maketargets = Game_Action.prototype.makeTargets;
/** override make targets to return forced target if we need */
Game_Action.prototype.makeTargets = function () {
	if (this.forcedTargets) { // net player targeting someone
		return this.forcedTargets;
	}

	if (this.netPartyId !== MATTIE.multiplayer.getCurrentNetController().peerId) { // host targeting net player
		const net = MATTIE.multiplayer.getCurrentNetController().netPlayers[this.netPartyId];
		let netParty = [];
		if (net) {
			netParty = net.battleMembers();
			return netParty;
		}
	}

	return MATTIE.maketargets.call(this);
};

Game_Action.prototype.setNetPartyId = function (id) {
	this.netPartyId = id;
};

/** check that all combatants on this event are ready */
BattleManager.checkAllPlayersReady = function () {
	return $gameTroop.allReady();
};

/** check that all combatants on this event are ready */
BattleManager.checkAllExtraTurn = function () {
	return $gameTroop.allExTurn();
};

/** check that all combatants on this event are ready */
BattleManager.checkSomeExtraTurn = function () {
	return $gameTroop.someExTurn();
};

/** check that all combatants on this event are ready */
BattleManager.allExTurnReady = function () {
	return $gameTroop.allExTurnReady();
};

// override YANFly's update function to have the ready state aswell
BattleManager.update = function () {
	if (!this.isBusy() && !this.updateEvent()) {
		switch (this._phase) {
		case 'start':
			this.startInput();
			break;

		case 'input':
			if (!$gameParty.canInput()) { // if the game party cannot input then they are ready
				this.ready();
			}
			break;
		case 'ready':
			// allow 'un-reading' to go back to previous state.
			// and if all players are ready proceed to turn state.

			MATTIE.multiplayer.waitingOnAllies = true;
			if (!MATTIE.multiplayer.combatEmitter.netExTurn) {
				if (this.checkSomeExtraTurn() && !Galv.EXTURN.active) { // if someone else has an extra turn but local does not
					if (this.allExTurnReady() && this.checkAllPlayersReady()) {
						// if atleast one player has an extra turn and all players with extra turn are ready.
						MATTIE.multiplayer.combatEmitter.netExTurn = true;
						this.startAfterReady();
					}
				} else if (this.checkAllPlayersReady()) { // otherwise normal check all ready
					this.startAfterReady();
					setTimeout(() => {
						MATTIE.multiplayer.BattleController.emitUnreadyEvent(); // unready once the round is well and started
					}, 1000);
				}
			}

			break;
		case 'turn':
			MATTIE.multiplayer.waitingOnAllies = false;
			this.updateTurn();
			break;
		case 'action':
			this.updateAction();
			break;
		case 'phaseChange':
			this.updatePhase();
			break;
		case 'actionList':
			this.updateActionList();
			break;
		case 'actionTargetList':
			this.updateActionTargetList();
			break;
		case 'turnEnd':
			if (!MATTIE.multiplayer.combatEmitter.netExTurn) { // if a net player is taking an extra turn, dont unready
				MATTIE.multiplayer.BattleController.emitUnreadyEvent();
				MATTIE.multiplayer.BattleController.emitTurnEndEvent();
			}
			BattleManager.sync();
			break;
		case 'syncing':

			break;
		case 'doneSyncing':
			if (MATTIE.multiplayer.combatEmitter.netExTurn) {
				MATTIE.multiplayer.combatEmitter.netExTurn = false; // turn of net extra turn
				this.ready(); // re enter the ready state
			} else {
				this.updateTurnEnd();
			}

			break;
		case 'battleEnd':
			MATTIE.multiplayer.BattleController.emitTurnEndEvent();
			this.updateBattleEnd();
			break;
		}
	}
};

MATTIE.multiplayer.combatEmitter.startAction = BattleManager.startAction;
BattleManager.startAction = function () {
	var action = this._subject.currentAction();
	const isNet = this._subject.isNetActor;
	MATTIE.multiplayer.BattleController.onSkillExecution(action, this._subject, isNet);
};