--
-- ExtendedWearable
--
-- @author:		JoPi
-- @date:		16/05/21
-- @version:	v1.0
-- @history:	v1.0 - initial version
--
-- free for non-commercial usage

ExtendedWearable = {};
ExtendedWearable.MOD_NAME = g_currentModName;

function ExtendedWearable.prerequisitesPresent(specializations)
	return SpecializationUtil.hasSpecialization(Wearable, specializations);
end;

function ExtendedWearable.registerFunctions(vehicleType)
	SpecializationUtil.registerFunction(vehicleType, "addRustAmount", ExtendedWearable.addRustAmount);
	SpecializationUtil.registerFunction(vehicleType, "addNodeRustAmount", ExtendedWearable.addNodeRustAmount);
	SpecializationUtil.registerFunction(vehicleType, "updateNodeRustAmount", ExtendedWearable.updateNodeRustAmount);
	SpecializationUtil.registerFunction(vehicleType, "setNodeRustAmount", ExtendedWearable.setNodeRustAmount);
	SpecializationUtil.registerFunction(vehicleType, "getNodeRustAmount", ExtendedWearable.getNodeRustAmount);
	SpecializationUtil.registerFunction(vehicleType, "getRustMultiplier", ExtendedWearable.getRustMultiplier);
	SpecializationUtil.registerFunction(vehicleType, "getCanReapplyWear", ExtendedWearable.getCanReapplyWear);
	SpecializationUtil.registerFunction(vehicleType, "getDoReapplyWear", ExtendedWearable.getDoReapplyWear);
	SpecializationUtil.registerFunction(vehicleType, "getWearReapplicationMultiplier", ExtendedWearable.getWearReapplicationMultiplier);
end;

function ExtendedWearable.registerOverwrittenFunctions(vehicleType)
	SpecializationUtil.registerOverwrittenFunction(vehicleType, ExtendedWearable.getIsSeasonsActive() and "setNodeScratchAmount" or "setNodeWearAmount", ExtendedWearable.setNodeWearAmount);
	SpecializationUtil.registerOverwrittenFunction(vehicleType, "addToLocalWearableNode", ExtendedWearable.addToLocalWearableNode);
end;

function ExtendedWearable.registerEventListeners(vehicleType)
	SpecializationUtil.registerEventListener(vehicleType, "onLoad", ExtendedWearable);
	SpecializationUtil.registerEventListener(vehicleType, "onPostLoad", ExtendedWearable);
	SpecializationUtil.registerEventListener(vehicleType, "onReadStream", ExtendedWearable);
	SpecializationUtil.registerEventListener(vehicleType, "onWriteStream", ExtendedWearable);
	SpecializationUtil.registerEventListener(vehicleType, "onReadUpdateStream", ExtendedWearable);
	SpecializationUtil.registerEventListener(vehicleType, "onWriteUpdateStream", ExtendedWearable);
	SpecializationUtil.registerEventListener(vehicleType, "onUpdateTick", ExtendedWearable);
end;

function ExtendedWearable:onLoad(savegame)
	local spec = ExtendedWearable.getSpecTable(self, "extendedWearable");
	spec.rustDuration = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.extendedWearable#rustDuration"), Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.wearable#wearDuration"), 600) * 1.5) * 60 * 1000;
	if spec.rustDuration ~= 0 then
		spec.rustDuration = 1 / spec.rustDuration;
	end;
	spec.wearReapplicationMultiplier = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.extendedWearable#wearReapplicationMultiplier"), 50);
	spec.rainMultiplier = Utils.getNoNil(getXMLFloat(self.xmlFile, "vehicle.extendedWearable#rainMultiplier"), 3);
	spec.canReapplyWear = Utils.getNoNil(getXMLBool(self.xmlFile, "vehicle.extendedWearable#canReapplyWear"), self.getDoGroundManipulation ~= nil);
	spec.dirtyFlag = self:getNextDirtyFlag();
end;

function ExtendedWearable:onPostLoad(savegame)
	if savegame ~= nil then
		local spec = ExtendedWearable.getSpecTable(self, "wearable");
		if spec.wearableNodes ~= nil then
			for i, nodeData in ipairs(spec.wearableNodes) do
				if ExtendedWearable.getIsSeasonsActive() then
					nodeData.scratchAmount = Utils.getNoNil(nodeData.scratchAmount, 1);
				end;
				local key = ("%s.%s.extendedWearable.rustNode(%d)"):format(savegame.key, ExtendedWearable.MOD_NAME, i - 1);
				self:setNodeRustAmount(nodeData, Utils.getNoNil(getXMLFloat(savegame.xmlFile, key .. "#amount01"), 0), Utils.getNoNil(getXMLFloat(savegame.xmlFile, key .. "#amount02"), 0), true);
			end;
		end;
	end;
end;

function ExtendedWearable:saveToXMLFile(xmlFile, key, usedModNames)
	local spec = ExtendedWearable.getSpecTable(self, "wearable");
	if spec.wearableNodes ~= nil then
		for i, nodeData in ipairs(spec.wearableNodes) do
			local key = ("%s.rustNode(%d)"):format(key, i - 1);
			local rustAmount01, rustAmount02 = self:getNodeRustAmount(nodeData);
			setXMLFloat(xmlFile, key .. "#amount01", rustAmount01);
			setXMLFloat(xmlFile, key .. "#amount02", rustAmount02);
		end;
	end;
end;

function ExtendedWearable:onReadStream(streamId, connection)
	local spec = ExtendedWearable.getSpecTable(self, "wearable");
	if spec.wearableNodes ~= nil then
		for _,nodeData in ipairs(spec.wearableNodes) do
			local rustAmount01 = 0;
			local rustAmount02 = 0;
			if streamReadBool(streamId) then
				rustAmount01 = streamReadUIntN(streamId, Wearable.SEND_NUM_BITS) / Wearable.SEND_MAX_VALUE;
				if self:getCanReapplyWear() then
					rustAmount02 = streamReadUIntN(streamId, Wearable.SEND_NUM_BITS) / Wearable.SEND_MAX_VALUE;
				end;
			end;
			self:setNodeRustAmount(nodeData, rustAmount01, rustAmount02, true);
		end;
	end;
end;

function ExtendedWearable:onWriteStream(streamId, connection)
	local spec = ExtendedWearable.getSpecTable(self, "wearable");
	if spec.wearableNodes ~= nil then
		for _,nodeData in ipairs(spec.wearableNodes) do
			if streamWriteBool(streamId, Utils.getNoNil(nodeData.scratchAmount, self:getNodeWearAmount(nodeData)) > 0) then
				local rustAmount01, rustAmount02 = self:getNodeRustAmount(nodeData);
				streamWriteUIntN(streamId, math.floor(rustAmount01 * Wearable.SEND_MAX_VALUE + 0.5), Wearable.SEND_NUM_BITS);
				if self:getCanReapplyWear() then
					streamWriteUIntN(streamId, math.floor(rustAmount02 * Wearable.SEND_MAX_VALUE + 0.5), Wearable.SEND_NUM_BITS);
				end;
			end;
		end;
	end;
end;

function ExtendedWearable:onReadUpdateStream(streamId, timestamp, connection)
	if connection:getIsServer() then
		local spec = ExtendedWearable.getSpecTable(self, "wearable");
		if spec.wearableNodes ~= nil and streamReadBool(streamId) then
			for _,nodeData in ipairs(spec.wearableNodes) do
				local rustAmount01 = 0;
				local rustAmount02 = 0;
				if streamReadBool(streamId) then
					rustAmount01 = streamReadUIntN(streamId, Wearable.SEND_NUM_BITS) / Wearable.SEND_MAX_VALUE;
					if self:getCanReapplyWear() then
						rustAmount02 = streamReadUIntN(streamId, Wearable.SEND_NUM_BITS) / Wearable.SEND_MAX_VALUE;
					end;
				end;
				self:setNodeRustAmount(nodeData, rustAmount01, rustAmount02, true);
			end;
		end;
	end;
end;

function ExtendedWearable:onWriteUpdateStream(streamId, connection, dirtyMask)
	if not connection:getIsServer() then
		local spec = ExtendedWearable.getSpecTable(self, "wearable");
		if spec.wearableNodes ~= nil and streamWriteBool(streamId, bitAND(dirtyMask, ExtendedWearable.getSpecTable(self, "extendedWearable").dirtyFlag) ~= 0) then
			for _,nodeData in ipairs(spec.wearableNodes) do
				if streamWriteBool(streamId, Utils.getNoNil(nodeData.scratchAmount, self:getNodeWearAmount(nodeData)) > 0) then
					local rustAmount01, rustAmount02 = self:getNodeRustAmount(nodeData);
					streamWriteUIntN(streamId, math.floor(rustAmount01 * Wearable.SEND_MAX_VALUE + 0.5), Wearable.SEND_NUM_BITS);
					if self:getCanReapplyWear() then
						streamWriteUIntN(streamId, math.floor(rustAmount02 * Wearable.SEND_MAX_VALUE + 0.5), Wearable.SEND_NUM_BITS);
					end;
				end;
			end;
		end;
	end;
end;

function ExtendedWearable:onUpdateTick(dt, isActive, isActiveForInput, isSelected)
	if self.isServer and self:getUsageCausesWear() then
		local spec = ExtendedWearable.getSpecTable(self, "wearable");
		if spec.wearableNodes ~= nil then
			for _,nodeData in ipairs(spec.wearableNodes) do
				self:addNodeRustAmount(nodeData, nodeData.rustUpdateFunc(self, nodeData, dt));
				local wearAmount = Utils.getNoNil(nodeData.scratchAmount, self:getNodeWearAmount(nodeData));
				if wearAmount > 0 then
					local rustAmount01, rustAmount02 = self:getNodeRustAmount(nodeData);
					if rustAmount01 < wearAmount or rustAmount02 < rustAmount01 then
						self:raiseActive();
					end;
				end;
			end;
		end;
	end;
end;

function ExtendedWearable:addRustAmount(amount01, amount02, force)
	local spec = ExtendedWearable.getSpecTable(self, "wearable");
	if spec.wearableNodes ~= nil then
		for _,nodeData in ipairs(spec.wearableNodes) do
			self:addNodeRustAmount(nodeData, amount01, amount02, force);
		end;
	end;
end;

function ExtendedWearable:addNodeRustAmount(nodeData, amount01, amount02, force)
	amount01 = Utils.getNoNil(amount01, 0);
	amount02 = Utils.getNoNil(amount02, 0);
	if Utils.getNoNil(nodeData.scratchAmount, self:getNodeWearAmount(nodeData)) > 0 and (amount01 ~= 0 or self:getCanReapplyWear() and amount02 ~= 0) or force then
		local rustAmount01, rustAmount02 = self:getNodeRustAmount(nodeData);
		self:setNodeRustAmount(nodeData, rustAmount01 + amount01, rustAmount02 + amount02, force);
	end;
end;

function ExtendedWearable:updateNodeRustAmount(nodeData, dt)
	local changedAmount01 = 0;
	local changedAmount02 = 0;
	if Wearable.getIntervalMultiplier() ~= 0 and Utils.getNoNil(nodeData.scratchAmount, self:getNodeWearAmount(nodeData)) > 0 then
		changedAmount01 = dt * ExtendedWearable.getSpecTable(self, "extendedWearable").rustDuration * self:getRustMultiplier() * Wearable.getIntervalMultiplier();
		if self:getCanReapplyWear() then
			changedAmount02 = self:getDoReapplyWear() and -dt * ExtendedWearable.getSpecTable(self, "wearable").wearDuration * self:getWearReapplicationMultiplier() * Wearable.getIntervalMultiplier() or changedAmount01;
		end;
	end;
	return changedAmount01, changedAmount02;
end;

function ExtendedWearable:setNodeRustAmount(nodeData, rustAmount01, rustAmount02, force)
	nodeData.rustAmount01 = MathUtil.clamp(Utils.getNoNil(rustAmount01, nodeData.rustAmount01), 0, Utils.getNoNil(nodeData.scratchAmount, self:getNodeWearAmount(nodeData)));
	nodeData.rustAmount02 = MathUtil.clamp(Utils.getNoNil(rustAmount02, nodeData.rustAmount02), self:getCanReapplyWear() and 0 or nodeData.rustAmount01, nodeData.rustAmount01);
	if math.abs(nodeData.rustAmount01Sent - nodeData.rustAmount01) > Wearable.SEND_THRESHOLD or math.abs(nodeData.rustAmount02Sent - nodeData.rustAmount02) > Wearable.SEND_THRESHOLD or force then
		for _,node in pairs(nodeData.nodes) do
			local x, y, _, _ = getShaderParameter(node, "RDT");
			setShaderParameter(node, "RDT", x, y, nodeData.rustAmount01, 1 - nodeData.rustAmount02, false);
		end;
		if self.isServer then
			self:raiseDirtyFlags(ExtendedWearable.getSpecTable(self, "extendedWearable").dirtyFlag);
			nodeData.rustAmount01Sent = nodeData.rustAmount01;
			nodeData.rustAmount02Sent = nodeData.rustAmount02;
		end;
	end;
end;

function ExtendedWearable:getNodeRustAmount(nodeData)
	return nodeData.rustAmount01, nodeData.rustAmount02;
end;

function ExtendedWearable:getRustMultiplier()
	if g_currentMission.environment.weather:getRainFallScale() > 0.1 and g_currentMission.environment.weather:getTimeSinceLastRain() < 20 then
		local isAffectedByRain = true;
		if ExtendedWearable.getIsSeasonsActive() and g_seasons ~= nil then
			local wx,_,wz = getWorldTranslation(self.rootNode);
			local posX = wx - self.sizeWidth / 2 + self.widthOffset;
			local posZ = wz - self.sizeLength / 2 + self.lengthOffset;
			isAffectedByRain = g_seasons.mask:getDensityAt(posX, posZ, posX + self.sizeWidth, posZ, posX, posZ + self.sizeLength) == 0;
		end;
		if isAffectedByRain then
			return ExtendedWearable.getSpecTable(self, "extendedWearable").rainMultiplier;
		end;
	end;
	return 1;
end;

function ExtendedWearable:getCanReapplyWear()
	return ExtendedWearable.getSpecTable(self, "extendedWearable").canReapplyWear;
end;

function ExtendedWearable:getDoReapplyWear()
	return self.getDoGroundManipulation ~= nil and self:getDoGroundManipulation();
end;

function ExtendedWearable:getWearReapplicationMultiplier()
	return self:getWearMultiplier() * ExtendedWearable.getSpecTable(self, "extendedWearable").wearReapplicationMultiplier;
end;

function ExtendedWearable:setNodeWearAmount(superFunc, nodeData, wearAmount, ...)
	superFunc(self, nodeData, wearAmount, ...);
	self:setNodeRustAmount(nodeData, nil, nil, ...);
end;

function ExtendedWearable:addToLocalWearableNode(superFunc, node, updateFunc, customIndex, extraParams, ...)
	extraParams = Utils.getNoNil(extraParams, {});
	extraParams.rustUpdateFunc = ExtendedWearable.updateNodeRustAmount;
	extraParams.rustAmount01 = 0;
	extraParams.rustAmount01Sent = 0;
	extraParams.rustAmount02 = 0;
	extraParams.rustAmount02Sent = 0;
	superFunc(self, node, updateFunc, customIndex, extraParams, ...);
end;

function ExtendedWearable.getSpecTable(vehicle, specName)
	return Utils.getNoNil(vehicle[("spec_%s.%s"):format(ExtendedWearable.MOD_NAME, specName)], vehicle[("spec_%s"):format(specName)]);
end;

function ExtendedWearable.getIsSeasonsActive()
	return g_modIsLoaded["FS19_RM_Seasons"];
end;