let player;
let needCanvasUpdate = true;
let gameEnded = false;


// Don't change this
const TMT_VERSION = {
	tmtNum: "2.5.9.2",
	tmtName: "Dreams Really Do Come True"
}

function getResetGain(layer, useType = null) {
	let type = useType;
	if (!useType) {
		type = tmp[layer].type;
		if (layers[layer].getResetGain !== undefined) {
			return layers[layer].getResetGain();
		}
	}
	if (tmp[layer].type === "none") {
		return decimalZero;
	}
	if (tmp[layer].gainExp.eq(0)) {
		return decimalZero;
	}
	if (type === "static") {
		if ((!tmp[layer].canBuyMax) || tmp[layer].baseAmount.lt(tmp[layer].requires)) {
			return decimalOne;
		}
		let gain = tmp[layer].baseAmount.div(tmp[layer].requires).div(tmp[layer].gainMult).max(1).log(tmp[layer].base).times(tmp[layer].gainExp).pow(Decimal.pow(tmp[layer].exponent, -1));
		gain = gain.times(tmp[layer].directMult);
		return gain.floor().sub(player[layer].points).add(1).max(1);
	} else if (type === "normal") {
		if (tmp[layer].baseAmount.lt(tmp[layer].requires)) {
			return decimalZero;
		}
		let gain = tmp[layer].baseAmount.div(tmp[layer].requires).pow(tmp[layer].exponent).times(tmp[layer].gainMult).pow(tmp[layer].gainExp);
		if (gain.gte(tmp[layer].softcap)) {
			gain = gain.pow(tmp[layer].softcapPower).times(tmp[layer].softcap.pow(decimalOne.sub(tmp[layer].softcapPower)));
		}
		gain = gain.times(tmp[layer].directMult);
		return gain.floor().max(0);
	} else if (type === "custom") {
		return layers[layer].getResetGain();
	} else {
		return decimalZero;
	}
}

function getNextAt(layer, canMax = false, useType = null) {
	let type = useType;
	if (!useType) {
		type = tmp[layer].type;
		if (layers[layer].getNextAt !== undefined) {
			return layers[layer].getNextAt(canMax);
		}

	}
	if (tmp[layer].type === "none") {
		return new Decimal(Infinity);
	}

	if (tmp[layer].gainMult.lte(0)) {
		return new Decimal(Infinity);
	}
	if (tmp[layer].gainExp.lte(0)) {
		return new Decimal(Infinity);
	}

	if (type === "static") {
		if (!tmp[layer].canBuyMax) {
			canMax = false;
		}
		let amt = player[layer].points.plus((canMax && tmp[layer].baseAmount.gte(tmp[layer].nextAt)) ? tmp[layer].resetGain : 0).div(tmp[layer].directMult);
		let extraCost = Decimal.pow(tmp[layer].base, amt.pow(tmp[layer].exponent).div(tmp[layer].gainExp)).times(tmp[layer].gainMult);
		let cost = extraCost.times(tmp[layer].requires).max(tmp[layer].requires);
		if (tmp[layer].roundUpCost) {
			cost = cost.ceil();
		}
		return cost;
	} else if (type === "normal") {
		let next = tmp[layer].resetGain.add(1).div(tmp[layer].directMult);
		if (next.gte(tmp[layer].softcap)) {
			next = next.div(tmp[layer].softcap.pow(decimalOne.sub(tmp[layer].softcapPower))).pow(decimalOne.div(tmp[layer].softcapPower));
		}
		next = next.root(tmp[layer].gainExp).div(tmp[layer].gainMult).root(tmp[layer].exponent).times(tmp[layer].requires).max(tmp[layer].requires);
		if (tmp[layer].roundUpCost) {
			next = next.ceil();
		}
		return next;
	} else if (type === "custom") {
		return layers[layer].getNextAt(canMax);
	} else {
		return decimalZero;
	}
}

function softcap(value, cap, power = 0.5) {
	if (value.lte(cap)) {
		return value;
	} else {
		return value.pow(power).times(cap.pow(decimalOne.sub(power)));
	}
}

// Return true if the layer should be highlighted. By default checks for upgrades only.
function shouldNotify(layer) {
	for (let id in tmp[layer].upgrades) {
		if (isPlainObject(layers[layer].upgrades[id])) {
			if (canAffordUpgrade(layer, id) && !hasUpgrade(layer, id) && tmp[layer].upgrades[id].unlocked) {
				return true;
			}
		}
	}
	if (player[layer].activeChallenge && canCompleteChallenge(layer, player[layer].activeChallenge)) {
		return true;
	}

	if (tmp[layer].shouldNotify)
		return true

	if (isPlainObject(tmp[layer].tabFormat)) {
		for (let subtab in tmp[layer].tabFormat) {
			if (subtabShouldNotify(layer, "mainTabs", subtab)) {
				tmp[layer].trueGlowColor = tmp[layer].tabFormat[subtab].glowColor
				return true;
			}
		}
	}

	for (let family in tmp[layer].microtabs) {
		for (let subtab in tmp[layer].microtabs[family]) {
			if (subtabShouldNotify(layer, family, subtab)) {
				tmp[layer].trueGlowColor = tmp[layer].microtabs[family][subtab].glowColor
				return true;
			}
		}
	}

	return false;

}

function canReset(layer) {
	if (layers[layer].canReset !== undefined) {
		return run(layers[layer].canReset, layers[layer]);
	} else if (tmp[layer].type === "normal") {
		return tmp[layer].baseAmount.gte(tmp[layer].requires);
	} else if (tmp[layer].type === "static") {
		return tmp[layer].baseAmount.gte(tmp[layer].nextAt);
	} else {
		return false;
	}
}

function rowReset(row, layer) {
	for (let lr in ROW_LAYERS[row]) {
		if (layers[lr].doReset) {

			player[lr].activeChallenge = null; // Exit challenges on any row reset on an equal or higher row
			run(layers[lr].doReset, layers[lr], layer);
		} else if (tmp[layer].row > tmp[lr].row && row !== "side" && !isNaN(row)) {
			layerDataReset(lr);
		}
	}
}

function layerDataReset(layer, keep = []) {
	let storedData = {unlocked: player[layer].unlocked, forceTooltip: player[layer].forceTooltip, noRespecConfirm: player[layer].noRespecConfirm} // Always keep these

	for (let thing in keep) {
		if (player[layer][keep[thing]] !== undefined) {
			storedData[keep[thing]] = player[layer][keep[thing]];
		}
	}
	Vue.set(player[layer], "buyables", getStartBuyables(layer));
	Vue.set(player[layer], "clickables", getStartClickables(layer));
	Vue.set(player[layer], "challenges", getStartChallenges(layer));

	layOver(player[layer], getStartLayerData(layer));
	player[layer].upgrades = [];
	player[layer].milestones = [];
	player[layer].achievements = [];
	player[layer].challenges = getStartChallenges(layer);
	resetBuyables(layer);

	if (layers[layer].clickables && !player[layer].clickables) {
		player[layer].clickables = getStartClickables(layer);
	}
	for (let thing in storedData) {
		player[layer][thing] = storedData[thing];
	}
}

function resetBuyables(layer) {
	if (layers[layer].buyables) {
		player[layer].buyables = getStartBuyables(layer);
	}
	player[layer].spentOnBuyables = decimalZero;
}


function addPoints(layer, gain) {
	if (isFunction(layers[layer].onAddPoints)) {
		layers[layer].onAddPoints(gain);
	}
	player[layer].points = player[layer].points.add(gain).max(0);
	if (player[layer].best) {
		player[layer].best = player[layer].best.max(player[layer].points);
	}
	if (player[layer].total) {
		player[layer].total = player[layer].total.add(gain);
	}
}

function generatePoints(layer, diff) {
	addPoints(layer, tmp[layer].resetGain.times(diff));
}

let prevOnReset;

function doReset(layer, force = false) {
	if (tmp[layer].type === "none") {
		return;
	}
	let row = tmp[layer].row;
	if (!force) {
		if (tmp[layer].baseAmount.lt(tmp[layer].requires)) {
			return;
		}
		let gain = tmp[layer].resetGain;
		if (tmp[layer].type === "static") {
			if (tmp[layer].baseAmount.lt(tmp[layer].nextAt)) {
				return;
			}
			gain = (tmp[layer].canBuyMax ? gain : 1);
		}
		if (tmp[layer].type === "custom") {
			if (!tmp[layer].canReset) {
				return;
			}
		}

		if (layers[layer].onPrestige) {
			run(layers[layer].onPrestige, layers[layer], gain);
		}

		addPoints(layer, gain);
		updateMilestones(layer);
		updateAchievements(layer);

		if (!player[layer].unlocked) {
			player[layer].unlocked = true;
			needCanvasUpdate = true;

			if (tmp[layer].increaseUnlockOrder) {
				let lrs = tmp[layer].increaseUnlockOrder;
				for (let lr in lrs) {
					if (!player[lrs[lr]].unlocked) {
						player[lrs[lr]].unlockOrder++;
					}
				}
			}
		}

		tmp[layer].baseAmount = decimalZero; // quick fix
	}

	if (tmp[layer].resetsNothing) {
		return;
	}


	for (let layerResetting in layers) {
		if (row >= layers[layerResetting].row && (!force || layerResetting !== layer)) {
			completeChallenge(layerResetting);
		}
	}

	prevOnReset = {...player}; //Deep Copy
	player.points = (row === 0 ? decimalZero : getStartPoints());

	for (let x = row; x >= 0; x--) {
		rowReset(x, layer);
	}
	rowReset("side", layer);
	prevOnReset = undefined;

	player[layer].resetTime = 0;

	updateTemp();
	updateTemp();
}

function startChallenge(layer, x) {
	let enter = false;
	if (!player[layer].unlocked) {
		return;
	}
	if (player[layer].activeChallenge === x) {
		completeChallenge(layer, x);
		player[layer].activeChallenge = null;
	} else {
		enter = true;
	}
	doReset(layer, true);
	if (enter) {
		player[layer].activeChallenge = x;
		run(layers[layer].challenges[x].onEnter, layers[layer].challenges[x]);
	}

	updateChallengeTemp(layer);
}

function canCompleteChallenge(layer, x) {
	if (x !== player[layer].activeChallenge) {
		return;
	}
	let challenge = tmp[layer].challenges[x];
	if (challenge.canComplete !== undefined) {
		return challenge.canComplete;
	}

	if (challenge.currencyInternalName) {
		let name = challenge.currencyInternalName;
		if (challenge.currencyLocation) {
			return !(challenge.currencyLocation[name].lt(challenge.goal));
		} else if (challenge.currencyLayer) {
			let lr = challenge.currencyLayer;
			return !(player[lr][name].lt(challenge.goal));
		} else {
			return !(player[name].lt(challenge.goal));
		}
	} else {
		return !(player.points.lt(challenge.goal));
	}

}

function completeChallenge(layer) {
	let x = player[layer].activeChallenge;
	if (!x) {
		return;
	}

	let completions = canCompleteChallenge(layer, x);
	if (!completions) {
		player[layer].activeChallenge = null;
		run(layers[layer].challenges[x].onExit, layers[layer].challenges[x]);
		return;
	}
	if (player[layer].challenges[x] < tmp[layer].challenges[x].completionLimit) {
		needCanvasUpdate = true;
		player[layer].challenges[x] += completions;
		player[layer].challenges[x] = Math.min(player[layer].challenges[x], tmp[layer].challenges[x].completionLimit);
		if (layers[layer].challenges[x].onComplete) {
			run(layers[layer].challenges[x].onComplete, layers[layer].challenges[x]);
		}
	}
	player[layer].activeChallenge = null;
	run(layers[layer].challenges[x].onExit, layers[layer].challenges[x]);
	updateChallengeTemp(layer);
}

VERSION.withoutName = "v" + VERSION.num + (VERSION.pre ? " Pre-Release " + VERSION.pre : VERSION.pre ? " Beta " + VERSION.beta : "");
VERSION.withName = VERSION.withoutName + (VERSION.name ? ": " + VERSION.name : "");


function autobuyUpgrades(layer) {
	if (!tmp[layer].upgrades) {
		return;
	}
	for (let id in tmp[layer].upgrades) {
		if (isPlainObject(tmp[layer].upgrades[id]) && (layers[layer].upgrades[id].canAfford === undefined || layers[layer].upgrades[id].canAfford() === true)) {
			buyUpg(layer, id);
		}
	}
}

function gameLoop(diff) {
	if (isEndgame() || gameEnded) {
		gameEnded = 1;
	}

	if (isNaN(diff)) {
		diff = 0;
	}
	if (gameEnded && !player.keepGoing) {
		diff = 0;
		player.tab = "gameEnded";
		clearParticles();
	}

	if (maxTickLength) {
		let limit = maxTickLength();
		if (diff > limit) {
			diff = limit;
		}
	}
	addTime(diff);
	const modifiedDiff = diff * ritualEffect("speed").toNumber();
	player.points = player.points.add(tmp.pointGen.times(modifiedDiff)).max(0);

	for (let x = 0; x <= maxRow; x++) {
		for (let item in TREE_LAYERS[x]) {
			let layer = TREE_LAYERS[x][item];
			player[layer].resetTime += diff;
			if (tmp[layer].passiveGeneration) {
				generatePoints(layer, modifiedDiff * tmp[layer].passiveGeneration);
			}
			if (layers[layer].update) {
				layers[layer].update(modifiedDiff);
			}
		}
	}

	for (let row in OTHER_LAYERS) {
		for (let item in OTHER_LAYERS[row]) {
			let layer = OTHER_LAYERS[row][item];
			player[layer].resetTime += diff;
			if (tmp[layer].passiveGeneration) {
				generatePoints(layer, modifiedDiff * tmp[layer].passiveGeneration);
			}
			if (layers[layer].update) {
				layers[layer].update(modifiedDiff);
			}
		}
	}

	for (let x = maxRow; x >= 0; x--) {
		for (let item in TREE_LAYERS[x]) {
			let layer = TREE_LAYERS[x][item];
			if (tmp[layer].autoPrestige && tmp[layer].canReset) {
				doReset(layer);
			}
			if (layers[layer].automate) {
				layers[layer].automate();
			}
			if (tmp[layer].autoUpgrade) {
				autobuyUpgrades(layer);
			}
		}
	}

	for (let row in OTHER_LAYERS) {
		for (let item in OTHER_LAYERS[row]) {
			let layer = OTHER_LAYERS[row][item];
			if (tmp[layer].autoPrestige && tmp[layer].canReset) {
				doReset(layer);
			}
			if (layers[layer].automate) {
				layers[layer].automate();
			}
			player[layer].best = player[layer].best.max(player[layer].points);
			if (tmp[layer].autoUpgrade) {
				autobuyUpgrades(layer);
			}
		}
	}

	for (let layer in layers) {
		if (layers[layer].milestones) {
			updateMilestones(layer);
		}
		if (layers[layer].achievements) {
			updateAchievements(layer);
		}
	}

}

function hardReset(override = false) {
	if (!(override || confirm("Are you sure you want to do this? You will lose all your progress!"))) {
		return;
	}
	player = null;
	save();
	window.location.reload();
}

let ticking = false;
let lastTenTicks = [];

const interval = setInterval(function () {
	if (player === undefined || tmp === undefined) {
		return;
	}
	if (ticking) {
		return;
	}
	if (gameEnded && !player.keepGoing) {
		return;
	}
	ticking = true;
	let now = Date.now();
	let diff = (now - player.time) / 1e3;
	let trueDiff = diff;
	if (player.offTime !== undefined) {
		if (player.offTime.remain > modInfo.offlineLimit * 3600) {
			player.offTime.remain = modInfo.offlineLimit * 3600;
		}
		if (player.offTime.remain > 0) {
			let offlineDiff = Math.max(player.offTime.remain / 10, diff);
			player.offTime.remain -= offlineDiff;
			diff += offlineDiff;
		}
		if (!player.offlineProd || player.offTime.remain <= 0) {
			player.offTime = undefined;
		}
	}
	if (player.devSpeed != null) {
		diff *= player.devSpeed;
	}
	player.time = now;
	if (needCanvasUpdate) {
		resizeCanvas();
		needCanvasUpdate = false;
	}
	tmp.scrolled = document.getElementById("treeTab")?.scrollTop > 30;
	updateTemp();
	updateOomps(diff);
	updateWidth()
	updateTabFormats()
	gameLoop(diff);
	fixNaNs();
	adjustPopupTime(trueDiff);
	updateParticles(trueDiff);
	lastTenTicks.push(trueDiff);
	if (lastTenTicks.length > 10) {
		lastTenTicks = lastTenTicks.slice(1);
	}
	Vue.set(app, "lastTenTicks", lastTenTicks);
	ticking = false;
}, 50);

setInterval(function () {
	needCanvasUpdate = true;
}, 500);