--[[
	AddCompostToPF.lua
	
	Autor: 		Ifko[nator]
	Datum: 		18.05.2025
	Version: 	1.0
	
	Changelog:	v1.0 @ 18.05.2025 - initial implementation in FS 25
				v1.1 @ 08.07.2025 - Shad0wlife: Moved from Map to compostLoader
												Add HUD label correction
												Add custom NitrogenMap XML Loader
]]

AddCompostToPF = {};

AddCompostToPF.BASE_DIRECTORY = g_currentModDirectory;
AddCompostToPF.MOD_NAME = g_currentModName;
AddCompostToPF.modTablePF = "";

FS25_precisionFarming.FarmlandStatsDialog.MOD_DIR = AddCompostToPF.BASE_DIRECTORY;

function AddCompostToPF:finalizeTypes()
	for _, mod in pairs(g_modManager.mods) do
		if mod.title == "Precision Farming" and g_modIsLoaded[mod.modName] then	
			AddCompostToPF:registerCustomNitrogenMapSchema();
			
			AddCompostToPF.modTablePF = _G[mod.modName];

			AddCompostToPF.modTablePF.NitrogenMap.loadFromXML = Utils.appendedFunction(AddCompostToPF.modTablePF.NitrogenMap.loadFromXML, AddCompostToPF.loadCustomNitrogenMaps);

			AddCompostToPF.modTablePF.ExtendedSprayer.getCurrentSprayerMode = Utils.overwrittenFunction(AddCompostToPF.modTablePF.ExtendedSprayer.getCurrentSprayerMode, AddCompostToPF.getCurrentSprayerModePF);
			AddCompostToPF.modTablePF.ExtendedSprayer.onEndWorkAreaProcessing = Utils.appendedFunction(AddCompostToPF.modTablePF.ExtendedSprayer.onEndWorkAreaProcessing, AddCompostToPF.onEndWorkAreaProcessingPF);

			AddCompostToPF.modTablePF.FarmlandStatisticCounter.loadFromItemsXML = Utils.appendedFunction(AddCompostToPF.modTablePF.FarmlandStatisticCounter.loadFromItemsXML, AddCompostToPF.loadFromItemsXMLPF);
			AddCompostToPF.modTablePF.FarmlandStatisticCounter.saveToXMLFile = Utils.appendedFunction(AddCompostToPF.modTablePF.FarmlandStatisticCounter.saveToXMLFile, AddCompostToPF.saveToXMLFilePF);
			AddCompostToPF.modTablePF.FarmlandStatisticCounter.reset = Utils.appendedFunction(AddCompostToPF.modTablePF.FarmlandStatisticCounter.reset, AddCompostToPF.resetPF);
			AddCompostToPF.modTablePF.FarmlandStatisticCounter.onReadStream = Utils.appendedFunction(AddCompostToPF.modTablePF.FarmlandStatisticCounter.onReadStream, AddCompostToPF.onReadStreamPF);
			AddCompostToPF.modTablePF.FarmlandStatisticCounter.onWriteStream = Utils.appendedFunction(AddCompostToPF.modTablePF.FarmlandStatisticCounter.onWriteStream, AddCompostToPF.onWriteStreamPF);

			AddCompostToPF.modTablePF.FarmlandStatsDialog.setData = Utils.appendedFunction(AddCompostToPF.modTablePF.FarmlandStatsDialog.setData, AddCompostToPF.setDataPF);
			
			AddCompostToPF.modTablePF.ExtendedSprayerHUDExtension.new = Utils.overwrittenFunction(AddCompostToPF.modTablePF.ExtendedSprayerHUDExtension.new, AddCompostToPF.newHUDExtension);
			AddCompostToPF.modTablePF.ExtendedSprayerHUDExtension.draw = Utils.overwrittenFunction(AddCompostToPF.modTablePF.ExtendedSprayerHUDExtension.draw, AddCompostToPF.drawHUDExtension);
			break;
		end;
	end;
end;

TypeManager.finalizeTypes = Utils.prependedFunction(TypeManager.finalizeTypes, AddCompostToPF.finalizeTypes);


--Ignore other parameters after self, as they are only used for the "vanilla" xml loading
function AddCompostToPF:loadCustomNitrogenMaps()
	local xmlName = "precisionFarmingTweaks.xml";
	local xmlDir = AddCompostToPF.BASE_DIRECTORY;
	local xmlPath = xmlDir .. xmlName;
	
	if AddCompostToPF.tweaksLoaded then
		return;
	end;
	
	local xmlFile = XMLFile.loadIfExists("precisionFarmingTweaks", xmlPath , AddCompostToPF.pfTweaksSchema);
	
	xmlFile:iterate("precisionFarmingTweaks.nitrogenMap.applicationRates.applicationRate", 
		function(index, key)
			local fillTypeName = xmlFile:getValue(key .. "#fillType");
			local applicationRate = {
				fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeName),
				autoAdjustToFruit = xmlFile:getValue(key .. "#autoAdjustToFruit"),
				regularRate = xmlFile:getValue(key .. "#regularRate"),
				ratesBySoilType = {}
			};
			
			xmlFile:iterate(key .. ".soil", 
				function(index, key)
					local rateBySoilEntry = {
						soilTypeIndex = xmlFile:getValue(key .. "#soilTypeIndex"),
						rate = xmlFile:getValue(key .. "#rate") / self.amountPerState
					};
					table.insert(applicationRate.ratesBySoilType, rateBySoilEntry);
				end
			);
			
			print(string.format("Inserting ApplicationRate for %s (%d) with %d", fillTypeName, applicationRate.fillTypeIndex, applicationRate.regularRate))
			table.insert(self.applicationRates, applicationRate);
		end
	);
	
	xmlFile:iterate("precisionFarmingTweaks.nitrogenMap.fertilizerUsage.nAmount", 
		function(index, key)
			local fillTypeName = xmlFile:getValue(key .. "#fillType");
			local nAmountEntry = {
				fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(fillTypeName),
				amount = xmlFile:getValue(key .. "#amount")
			};
			
			table.insert(self.fertilizerUsage.nAmounts, nAmountEntry);
		end
	);
	
	xmlFile:delete();
	AddCompostToPF.tweaksLoaded = true;
	
	--[[
	local function debugApplicationRates()
		print("NitrogenMap Application Rates:")
		for index, applicationRate in pairs(self.applicationRates) do
			print(
				string.format(
					"%d: -> %d (%s), %s, regularRate:%d", 
					index, 
					applicationRate.fillTypeIndex, 
					g_fillTypeManager:getFillTypeNameByIndex(applicationRate.fillTypeIndex),
					tostring(applicationRate.autoAdjustToFruit),
					applicationRate.regularRate
				)
			)
			print("{")
			for index, soilEntry in pairs(applicationRate.ratesBySoilType) do
				print(
					string.format(
						"%d -> idx:%d, rate:%d",
						index,
						soilEntry.soilTypeIndex,
						soilEntry.rate
					)
				)
			end
			print("}")
		end
	end
	
	debugApplicationRates()
	]]
end

function AddCompostToPF:registerCustomNitrogenMapSchema()
	local basePath = "precisionFarmingTweaks";
    self.pfTweaksSchema = XMLSchema.new(basePath);

    local nitrogenMapEntry = ".nitrogenMap";
	
    local applicationRateEntry = ".applicationRates.applicationRate(?)";
	local soilEntry = ".soil(?)";
	
	local fertilizerUsageEntry = ".fertilizerUsage";
	local nitrogenAmountEntry = ".nAmount(?)";
	
	
	--Usage: xmlSchema:register(xmlPath, description, defaultValue, requiredOnRead)
	self.pfTweaksSchema:register(XMLValueType.STRING, basePath .. nitrogenMapEntry .. applicationRateEntry .. "#fillType", "FillType of Fertilizer", nil, true);
	self.pfTweaksSchema:register(XMLValueType.BOOL, basePath .. nitrogenMapEntry .. applicationRateEntry .. "#autoAdjustToFruit", "Auto-adjust to fruit", false, true);
	self.pfTweaksSchema:register(XMLValueType.FLOAT, basePath .. nitrogenMapEntry .. applicationRateEntry .. "#regularRate", "Regular application rate", 60, true);
    
	self.pfTweaksSchema:register(XMLValueType.INT, basePath .. nitrogenMapEntry .. applicationRateEntry .. soilEntry .. "#soilTypeIndex", "Soil Type ID", nil, true);
	self.pfTweaksSchema:register(XMLValueType.INT, basePath .. nitrogenMapEntry .. applicationRateEntry .. soilEntry .. "#rate", "Soil Type specific application rate", 60, true);

	self.pfTweaksSchema:register(XMLValueType.STRING, basePath .. nitrogenMapEntry .. fertilizerUsageEntry .. nitrogenAmountEntry .. "#fillType", "FillType of Fertilizer", nil, true);
	self.pfTweaksSchema:register(XMLValueType.FLOAT, basePath .. nitrogenMapEntry .. fertilizerUsageEntry .. nitrogenAmountEntry .. "#amount", "Amount of nitrogen per fertilizer", 0.007, true);
end

function AddCompostToPF:getCurrentSprayerModePF(superFunc)
	local sourceVehicle, fillUnitIndex = AddCompostToPF.modTablePF.ExtendedSprayer.getFillTypeSourceVehicle(self);
	local lastValidFillType = sourceVehicle:getFillUnitLastValidFillType(fillUnitIndex);

	if lastValidFillType == FillType.LIME then
		return true, false;
	elseif  self:getIsAIActive() or lastValidFillType == FillType.FERTILIZER or (lastValidFillType == FillType.LIQUIDFERTILIZER or (lastValidFillType == FillType.MANURE or (lastValidFillType == FillType.LIQUIDMANURE or lastValidFillType == FillType.DIGESTATE))) or (FillType.COMPOST ~= nil and lastValidFillType == FillType.COMPOST) then
		return false, true;
	else
		return false, false;
	end;
end;

function AddCompostToPF:onEndWorkAreaProcessingPF(dt, hasProcessed)
	local specSprayer = self.spec_sprayer;

	if self.isServer and specSprayer.workAreaParameters.isActive then
		local sprayerVehicle = specSprayer.workAreaParameters.sprayVehicle;
		local usageAmount = specSprayer.workAreaParameters.usage;
		local sprayFillType = specSprayer.workAreaParameters.sprayFillType;

		if (sprayerVehicle ~= nil or self:getIsAIActive()) and self:getIsTurnedOn() then
			local lastRegularUsage = self[AddCompostToPF.modTablePF.ExtendedSprayer.SPEC_TABLE_NAME].lastRegularUsage;
			local statistics, _, fieldId = self:getPFStatisticInfo();

			if statistics ~= nil and fieldId ~= nil then
				if FillType.COMPOST ~= nil and sprayFillType == FillType.COMPOST then
					statistics:updateStatistic(fieldId, "usedCompost", usageAmount);
					statistics:updateStatistic(fieldId, "usedCompostRegular", lastRegularUsage);

					return;
				end;
			end;
		end;
	end;
end;

function AddCompostToPF:loadFromItemsXMLPF(xmlFile, key)
    if FillType.COMPOST ~= nil then
        self.usedCompost = xmlFile:getFloat(key .. "#usedCompost", self.usedCompost) or 0;
        self.usedCompostRegular = xmlFile:getFloat(key .. "#usedCompostRegular", self.usedCompostRegular) or 0;
    end;
end;

function AddCompostToPF:saveToXMLFilePF(xmlFile, key, usedModNames)
    if FillType.COMPOST ~= nil then
        local usedCompost = self.usedCompost ~= nil and self.usedCompost or 0;
        local usedCompostRegular = self.usedCompostRegular ~= nil and self.usedCompostRegular or 0;

        xmlFile:setFloat(key .. "#usedCompost", usedCompost);
        xmlFile:setFloat(key .. "#usedCompostRegular", usedCompostRegular);
    end;
end;

function AddCompostToPF:resetPF()
    if FillType.COMPOST ~= nil then
        self.usedCompost = 0;
        self.usedCompostRegular = 0;
    end;
end;

function AddCompostToPF:onReadStreamPF(streamId, connection)
    if FillType.COMPOST ~= nil then
        self.usedCompost = streamReadFloat32(streamId);
        self.usedCompostRegular = streamReadFloat32(streamId);
    end;
end;

function AddCompostToPF:onWriteStreamPF(streamId, connection)
    if FillType.COMPOST ~= nil then
        streamWriteFloat32(streamId, self.usedCompost);
        streamWriteFloat32(streamId, self.usedCompostRegular);
    end;
end;

function AddCompostToPF:setDataPF(farmlandId, fieldNumber, fieldArea, statistic)
    local function updatePercentageText(textElement, currentValue, referenceValue, monetaryChange, invertColors)
        local percentageChange = 0;

        if currentValue == referenceValue then
            textElement:setText("");

            return percentageChange;
        end;

        local isNegative = false;
        local displayText = "";

        if referenceValue == 0 then
            if math.floor(monetaryChange or 0) > 0 then
                displayText = string.format("(+%s)", g_i18n:formatMoney(monetaryChange));
                isNegative = true;
                percentageChange = 100;
            else
                displayText = "";
            end;
        else
            local calculatedPercentage = -100 * (1 - currentValue / referenceValue);

            percentageChange = math.floor(calculatedPercentage);

            if percentageChange ~= 0 then
                local formatString = percentageChange <= 0 and "(%d%%)" or "(+%d%%)";

                displayText = string.format(formatString, percentageChange);
                isNegative = percentageChange > 0;
            end;
        end;

        if invertColors then
            isNegative = not isNegative;
        end;

        textElement:applyProfile(isNegative and "farmlandStatsDialogChangeNeg" or "farmlandStatsDialogChangePos", true);
        textElement:setText(displayText);

        return percentageChange;
    end;

    local function updateFarmlandStatistics(statistics, statIndex, key, subKey, referenceKey, fillType, showWeight, showCost, roundValues, useDecimal, unitSuffix, priceOverride, weightOverride, invertColors)
        if unitSuffix == nil then
            unitSuffix = " l";

        elseif unitSuffix:len() > 0 then
            unitSuffix = " " .. unitSuffix;
        end;

        local currentValue = statistics:getValue(key, subKey);

        if referenceKey == nil then
            referenceKey = currentValue;
        elseif type(referenceKey) == "string" then
            referenceKey = statistics:getValue(key, referenceKey);
        elseif type(referenceKey) ~= "number" then
            referenceKey = currentValue;
        end;

        local price = priceOverride or AddCompostToPF.modTablePF.g_precisionFarming.farmlandStatistics:getFillLevelPrice(currentValue, fillType);
        local weight = weightOverride or AddCompostToPF.modTablePF.g_precisionFarming.farmlandStatistics:getFillLevelWeight(currentValue, fillType);

        if showWeight then
            local formatString = "%d" .. unitSuffix;

            if useDecimal then
                formatString = "%.1f" .. unitSuffix;
            else
                currentValue = MathUtil.round(currentValue);
            end;

            formatString = formatString .. " | %.1f t";

            self.statAmountText[statIndex]:setText(formatString:format(currentValue, weight));
        else
            self.statAmountText[statIndex]:setText("");
        end;

        if showCost == nil or showCost then
            self.statCostText[statIndex]:setText(g_i18n:formatMoney(price));
        else
            self.statCostText[statIndex]:setText("");
        end;

        local percentageChange = updatePercentageText(self.statPercentageText[statIndex], currentValue, referenceKey, price, invertColors);
        local adjustedPercentage = showCost == false and 0 or percentageChange;
        local roundedPrice = math.floor(price);
        local maxPrice = math.max(roundedPrice);
        local adjustedPrice = price * (-adjustedPercentage * 0.01 + 1);
        local finalPrice = math.floor(adjustedPrice);

        return maxPrice, math.max(finalPrice, 0);
    end;

    if FillType.COMPOST ~= nil then
        updateFarmlandStatistics(statistic, 14, self.showTotal, "usedCompost", "usedCompostRegular", FillType.COMPOST, true, false, false, false);
    end;
end;

--[[
DE:
Funktionssignatur sieht seltsam aus, liegt aber dran, dass overwrittenFunction den ersten Parameter immer als "self" annimmt, 
und auch als ersten Parameter (vor der superFunc) weitergibt.
ExtendedSprayerHUDExtension.new hat aber keinen "self" parameter, sondern der erste Parameter ist "vehicle".
EN:
Function signature looks weird, but that's because overwrittenFunction always expects the first parameter to be "self",
and passes it as the first parameter to the new function (even before superFunc).
ExtendedSprayerHUDExtension.new does NOT have a "self" parameter, but instead the first parameter is "vehicle".
]]
function AddCompostToPF.newHUDExtension(vehicle, superFunc, customMt)
	local HUDExtension = superFunc(vehicle, customMt);
	local texts = HUDExtension.texts;
	
	texts['headline_n_manureSpreader_ORIGINAL'] = HUDExtension.texts.headline_n_manureSpreader;
	texts['headline_n_manureSpreader_COMPOST'] = g_i18n:getText("hudExtensionSprayer_headline_n_compostSpreader", AddCompostToPF.MOD_NAME);
	
	return HUDExtension;
end;

function AddCompostToPF:drawHUDExtension(superFunc, inputHelpDisplay, posX, posY)
	local spec = self.extendedSprayer;
	
	if spec == nil then
		return posY;
	end;
	
	local sourceVehicle, sourceFillUnitIndex = AddCompostToPF.modTablePF.ExtendedSprayer.getFillTypeSourceVehicle(self.vehicle);
	local sprayFillType = sourceVehicle:getFillUnitFillType(sourceFillUnitIndex);
	
	if spec.isManureSpreader then
		if sprayFillType == FillType.COMPOST and sourceVehicle:getFillUnitFillLevel(sourceFillUnitIndex) > 0 then
			if not spec.compostMode then
				spec.compostMode = true;
				self.texts.headline_n_manureSpreader = self.texts.headline_n_manureSpreader_COMPOST;
			end;
		else
			if spec.compostMode or self.texts.headline_n_manureSpreader ~= self.texts.headline_n_manureSpreader_ORIGINAL then
				spec.compostMode = false;
				self.texts.headline_n_manureSpreader = self.texts.headline_n_manureSpreader_ORIGINAL;
			end;
		end;
	end;
	
	return superFunc(self, inputHelpDisplay, posX, posY);
end;