--[[
AnimatedIndoorPartsUtil

Utility for my AnimatedIndoorParts.lua

	@author:    Ifko[nator]
	@date:      11.03.2023
	@version:	1.0

	History:	v1.0 @11.03.2023 - initial implementation in FS 22
				--------------------------------------------------
]]

AnimatedIndoorPartsUtil = {};
AnimatedIndoorPartsUtil.currentModName = g_currentModName;

AnimatedIndoorPartsUtil.supportetDriveSymbolNames = {
	"FOUR_WHEEL", 
	"DIFF_LOCK_FRONT", 
	"DIFF_LOCK_BACK",
	"PTO_FRONT",
	"PTO_BACK",
	"HANDBRAKE",
	"TIPPING_ACTIVE",
	"TIPPING_INACTIVE",
	"TIPPING_SIDE_LEFT_1",
	"TIPPING_SIDE_RIGHT_1",
	"TIPPING_SIDE_BACK_1",
	"TIPPING_SIDE_GRAINDOOR_1",
	"TIPPING_SIDE_LEFT_2",
	"TIPPING_SIDE_RIGHT_2",
	"TIPPING_SIDE_BACK_2",
	"TIPPING_SIDE_GRAINDOOR_2",
	"TRAILER_1",
	"TRAILER_2",
	"TOOL_FRONT_LOWERED",
	"TOOL_FRONT_LIFTED",
	"TOOL_BACK_LOWERED",
	"TOOL_BACK_LIFTED",
	"CONTROL_LAMP",
	"GPS_ACTIVE",
	"GPS_STEERING_ACTIVE",
	"GPS_LANE_PLUS",
	"GPS_LANE_MINUS",
	"STEERAXLE_IS_LOCKED",
	"STEERAXLE_IS_UNLOCKED",
	"RIDGE_MARKER_LEFT",
	"RIDGE_MARKER_RIGHT",
	"RIDGE_MARKER_LEFT_UP",
	"RIDGE_MARKER_RIGHT_UP",
	"RIDGE_MARKER_LEFT_DOWN",
	"RIDGE_MARKER_RIGHT_DOWN",
	"TOOL_FRONT_IS_UNFOLDED",
	"TOOL_FRONT_IS_FOLDED",
	"TOOL_BACK_IS_UNFOLDED",
	"TOOL_BACK_IS_FOLDED",
	"TIPPING_ACTIVE_TRUCK",
	"ABS_TRUCK",
	"ABS_TRAILER",
	"TIPPING_ACTIVE_TRAILER_1",
	"TIPPING_ACTIVE_TRAILER_2",
	"TIPPING_INACTIVE_TRAILER_1",
	"TIPPING_INACTIVE_TRAILER_2",
	"WEARED_OUT_BRAKES",
	"WARNING_BRAKE_COMPRESSOR_FILL_LEVEL_TO_LOW",
	"WARNING_WORKSHOP_1",
	"WARNING_WORKSHOP_2",
	"STOP_IMMINENTLY",
	"MOTOR_NEED_MAINTRACE",
	"LOW_OIL_PRESSURE",
	"OIL_FILL_LEVEL_TO_LOW",
	"VEHICLE_SELECTION",
	"TOOL_FRONT_SELECTION_1",
	"TOOL_FRONT_SELECTION_2",
	"TOOL_BACK_SELECTION_1",
	"TOOL_BACK_SELECTION_2",
	"FRONTLOADER_SELECTION",
	"FRONTLOADER_TOOL_SELECTION",
	"TURN_LIGHT_TRAILER_ANY_1",
	"TURN_LIGHT_TRAILER_LEFT_1",
	"TURN_LIGHT_TRAILER_LEFT_2",
	"TURN_LIGHT_TRAILER_ANY_2",
	"TURN_LIGHT_TRAILER_RIGHT_1", 
	"TURN_LIGHT_TRAILER_RIGHT_2",
	"STRAW_CHOPPER",
	"OVERLOADING_ACTIVE",
	"GRAIN_TANK_UNFOLDED",
	"PIPE_UNFOLDED",
	"CARDINAL_DIRECTION_NORTH",
	"CARDINAL_DIRECTION_EAST",
	"CARDINAL_DIRECTION_SOUTH",
	"CARDINAL_DIRECTION_WEST",
	"CARDINAL_DIRECTION_NORTH_EAST",
	"CARDINAL_DIRECTION_NORTH_WEST",
	"CARDINAL_DIRECTION_SOUTH_EAST",
	"CARDINAL_DIRECTION_SOUTH_WEST",
	"NO_FIELD",
	"PS_MODE_AUTO",
	"PS_MODE_SEMI",
	"PS_MODE_MANUAL",
	"PS_LANE_LEFT",
	"PS_LANE_RIGHT",
	"PS_TRAM_LANE_LEFT",
	"PS_TRAM_LANE_RIGHT",
	"DISABLE_ON_MOTOR_OFF",
	"ADDITIONALLY_SIGN",
	"WORK_MODE_BROAD",
	"WORK_MODE_LEFT_SWATH",
	"WORK_MODE_RIGHT_SWATH",
	"WORK_MODE_SWATH",
	"BALE_WRAPPER_ACTIVE",
	"BALE_DROP_STATE_MANUAL",
	"BALE_DROP_STATE_AUTOMATIC",
	"BALE_DROP_STATE_COLLECT",
	"LIFT_AXLE_STATE_DOWN",
	"LIFT_AXLE_STATE_UP"
};

AnimatedIndoorPartsUtil.workModes = {
	"BROAD",
	"LEFT_SWATH",
	"RIGHT_SWATH",
	"SWATH"
};

AnimatedIndoorPartsUtil.supportedAnimationNames = {
	"TOOL_FRONT",
	"TOOL_BACK",
	"PTO_FRONT",
	"PTO_BACK"
};

AnimatedIndoorPartsUtil.supportetScaleDirections = {
	"X",
	"Y",
	"Z",
	"ALL"
};

AnimatedIndoorPartsUtil.unSupportetFillTypes = {
	"BALETWINE",
	"BALENET",
	"MOLASSES",
	"JOHNDEERE_BALETWINE",
	"CLAAS_BALETWINE"
};

AnimatedIndoorPartsUtil.radioChannels = {
	"off",
	"tropicalisimaLatinoFM",
	"theQueensRockPlaceFM",
	"laMusiqueFM",
	"dasEWerkFM",
	"countryRoadsFM",
	"classicalRadioNetworkFM",
	"internet"
};

AnimatedIndoorPartsUtil.supportetDashboardFunctions = {
	"textRendering",
	"fillLevelPercentTotal",
	"fillLevelTotal",
	"fillLevelPercentTrailer1",
	"fillLevelTrailer1",
	"fillLevelPercentTrailer2",
	"fillLevelTrailer2",
	"currentFilledTrailerFillLevelPercent",
	"currentFilledTrailerFillLevel",
	"fillLevelSowingMachine",
	"fillLevelPercentSowingMachine",
	"lowFuelFillLevel",
	"currentBaleCount",
	"totalBaleCount",
	"currentWrappedBaleCount",
	"totalWrappedBaleCount",
	"currentAngle",
	"seedFillLevelPercent",
	"seedFillLevel",
	"fertilizerFillLevelPercent",
	"fertilizerFillLevel",
	"fillWeightTrailer1",
	"fillWeightTrailer2",
	"fillLevelCombine",
	"fillLevelPercentCombine",
	"fillWeightCombine",
	"gpsWorkingWidth",
	"gpsCurrentLane",
	"currentFieldNumber",
	"currentFuelUsage",
	"currentMotorTemperature",
	"psCurrentLane",
	"psMaxLanes",
	"psWorkingWidth",
	"psTramlineDistance",
	"currentTirePressure",
	"tragetTirePressure",
	"cruiseControlSpeed",
	"currentBaleSize",
	"currentCoords",
	"currentZoomFactor"
};

AnimatedIndoorPartsUtil.inputs = {
	"INDOOR_CAMERA_ZOOM_BUTTON",
	"INDOOR_CAMERA_RESET_BUTTON",
	"SWITCH_SCREEN_UP_BUTTON",
	"SWITCH_SCREEN_DOWN_BUTTON"
};

function AnimatedIndoorPartsUtil.getDebugPriority(xmlFile, path)
	return Utils.getNoNil(getXMLInt(xmlFile, path), 0);
end;

function AnimatedIndoorPartsUtil.getSpecByName(self, specName, currentModName)
    local spec = self["spec_" .. Utils.getNoNil(currentModName, AnimatedIndoorPartsUtil.currentModName) .. "." .. specName];

	if spec ~= nil then
        return spec;
    end;

    return self["spec_" .. specName];
end;

function AnimatedIndoorPartsUtil.printError(errorMessage, isWarning, isInfo, luaName)
	local prefix = "::ERROR:: ";
	
	if isWarning then
		prefix = "::WARNING:: ";
	elseif isInfo then
		prefix = "::INFO:: ";
	end;
	
	print(prefix .. "from the " .. luaName .. ".lua: " .. tostring(errorMessage));
end;

function AnimatedIndoorPartsUtil.printDebug(debugMessage, priority, addString, luaName)
	if priority >= 1 then
		local prefix = "";
		
		if addString then
			prefix = "::DEBUG:: from the " .. luaName .. ".lua: ";
		end;
		
		setFileLogPrefixTimestamp(false);
		print(prefix .. tostring(debugMessage));
		setFileLogPrefixTimestamp(true);
	end;
end;

function AnimatedIndoorPartsUtil.getIsSupportetValue(supportetValues, supportetValue)
	for supportetValueNumber = 1, #supportetValues do
		if supportetValues[supportetValueNumber] == supportetValue then 
			return true;
		end;
	end;
	
	return false;
end;

function AnimatedIndoorPartsUtil.getSupportetValues(supportetValues)
	local supportetValuesString = "";

	for supportetValueNumber, supportetValue in pairs(supportetValues) do
		if supportetValuesString ~= "" then 
			if supportetValueNumber < #supportetValues then
				supportetValue = "', '" .. supportetValue;
			else
				supportetValue = "' or '" .. supportetValue;
			end;
		end;
		
		supportetValuesString = supportetValuesString .. supportetValue;
	end;
	
	return supportetValuesString;
end;

function AnimatedIndoorPartsUtil.renderText(posX, posY, textSize, string, priority)
	if priority >= 1 then
		renderText(posX, posY, getCorrectTextSize(textSize), tostring(string));
	end;
end;

function AnimatedIndoorPartsUtil.getCurrentFilledTrailer(vehicle, dischargeNode, specPipe)
	local currentFilledTrailer = nil;
	local minDistance = math.huge;
	local checkNode = Utils.getNoNil(dischargeNode.node, vehicle.components[1].node);

	for object, _ in pairs(specPipe.objectsInTriggers) do
		local outputFillType = vehicle:getFillUnitLastValidFillType(dischargeNode.fillUnitIndex);
		
		for fillUnitIndex, fillUnit in ipairs(object.spec_fillUnit.fillUnits) do
			local allowedToFillByPipe = object:getFillUnitSupportsToolType(fillUnitIndex, ToolType.DISCHARGEABLE);
			local supportsFillType = object:getFillUnitSupportsFillType(fillUnitIndex, outputFillType) or outputFillType == FillType.UNKNOWN;
			
			if allowedToFillByPipe and supportsFillType then
				local targetPoint = object:getFillUnitAutoAimTargetNode(fillUnitIndex);
				local exactFillRootNode = object:getFillUnitExactFillRootNode(fillUnitIndex);
				
				if targetPoint == nil then
					targetPoint = exactFillRootNode;
				end;
			
				if targetPoint ~= nil then
					local distance = calcDistanceFrom(checkNode, targetPoint);
				
					if distance < minDistance then
						currentFilledTrailer = object;

						break;
					end;
				end;
			end;
		end;
	end;

	return currentFilledTrailer;
end;

function AnimatedIndoorPartsUtil.getL10nText(text)
	if text:sub(1, 6) == "$l10n_" then
		text = g_i18n:getText(text:sub(7), AnimatedIndoorPartsUtil.currentModName);
	elseif g_i18n:hasText(text, AnimatedIndoorPartsUtil.currentModName) then
		text = g_i18n:getText(text, AnimatedIndoorPartsUtil.currentModName);
	end;

	return text;
end;

function AnimatedIndoorPartsUtil.getJointMoveAlpha(jointDesc, object, self, dt)
	local moveAlpha = -1;
	local upperAlpha, lowerAlpha = self:calculateAttacherJointMoveUpperLowerAlpha(jointDesc, object);

	if upperAlpha ~= nil and lowerAlpha ~= nil then 
		--moveAlpha = (lowerAlpha / upperAlpha) * 100;
	end;

	local objectAtttacherJoint = object.spec_attachable.attacherJoint;
	local targetRot = MathUtil.lerp(objectAtttacherJoint.upperRotationOffset, objectAtttacherJoint.lowerRotationOffset, jointDesc.moveAlpha);
	local curRot = MathUtil.lerp(jointDesc.upperRotationOffset, jointDesc.lowerRotationOffset, jointDesc.moveAlpha);

	if targetRot ~= nil and curRot ~= nil then 
		--moveAlpha = (curRot / targetRot) * 100;
	end;

	--moveAlpha = Utils.getMovedLimitedValue(jointDesc.moveAlpha, jointDesc.lowerAlpha, jointDesc.upperAlpha, jointDesc.moveTime, dt, not jointDesc.moveDown); --MathUtil.clamp(jointDesc.moveAlpha, jointDesc.lowerAlpha, jointDesc.upperAlpha);

	local x, _, _ = getRotation(jointDesc.bottomArm.rotationNode);

	return math.ceil(math.deg(x)), math.ceil(math.deg(objectAtttacherJoint.upperDistanceToGround)), math.ceil(math.deg(objectAtttacherJoint.lowerDistanceToGround));
end;

function AnimatedIndoorPartsUtil.getModNameBySpecialization(specialization)
	local modName = "";

	if specialization == nil then
		return "";
	end;
	
	for _, mod in pairs(g_modManager.mods) do
		if _G[tostring(mod.modName)][specialization] ~= nil then		
			if g_modIsLoaded[tostring(mod.modName)] then	
				modName = mod.modName;

				break;
			end;
		end;
	end;

	return modName;
end;

function AnimatedIndoorPartsUtil.getModNameByTitle(germanTitle, englishTitle)
	local modName = "";

	if englishTitle == nil then
		englishTitle = germanTitle;
	end;

	if germanTitle == nil then
		return "";
	end;
	
	for _, mod in pairs(g_modManager.mods) do
		if mod.title == germanTitle or mod.title == englishTitle then		
			if g_modIsLoaded[tostring(mod.modName)] then	
				modName = mod.modName;

				break;
			end;
		end;
	end;

	return modName;
end;

function AnimatedIndoorPartsUtil.getSchemaColor(isSelected)
	local color = {0, 0.25, 0};

	if isSelected then
		color = {0, 0.05, 0};
	end;

	return color;
end;

function AnimatedIndoorPartsUtil.getHasTurnLights(xmlFile)
	local hasTurnLights = false;

	for _, lightType in pairs({"low", "high"}) do
		for _, direction in pairs({"Left", "Right"}) do
			if xmlFile:hasProperty("vehicle.lights.realLights." .. lightType .. ".turnLight" .. direction) then
				hasTurnLights = true;

				break;
			end;
		end;
	end;

	return hasTurnLights;
end;

function AnimatedIndoorPartsUtil.formatNumber(number, precision, forcePrecision)
	precision = precision or 0;

	if precision == 0 then
		if number == nil then
			printCallstack();
		end;

		number = math.ceil(number);
	end;

	local baseString = tostring(MathUtil.round(number, precision));
	local prefix, num, decimal = string.match(baseString, "^([^%d]*%d)(%d*)[.]?(%d*)");
	local currencyString = prefix .. num:reverse():gsub("(%d%d%d)", "%1" .. AnimatedIndoorPartsUtil.getL10nText("unit_digitGroupingSymbol")):reverse();
	local decimalSeparator = Utils.getNoNil(AnimatedIndoorPartsUtil.getL10nText("unit_decimalSymbol"), ".")

	if precision > 0 then
		local prec = decimal:len();

		if prec > 0 and (decimal ~= string.rep("0", prec) or forcePrecision) then
			currencyString = currencyString .. decimalSeparator .. decimal:sub(1, precision);
		end;
	end;

	return currencyString;
end;

function AnimatedIndoorPartsUtil.setBarScale(bar, percentage, scaleDirection)
	if scaleDirection == "X" then
		setScale(bar, percentage, 1, 1);
	elseif scaleDirection == "Y" then
		setScale(bar, 1, percentage, 1);
	elseif scaleDirection == "Z" then
		setScale(bar, 1, 1, percentage);
	elseif scaleDirection == "ALL" then
		setScale(bar, percentage, percentage, percentage);
	end;
end;

function AnimatedIndoorPartsUtil.getAllowInsertScreen(vehicle, specAnimatedIndoorParts, screenKey)
	local allowInsertScreen = true;
	local configName = Utils.getNoNil(getXMLString(specAnimatedIndoorParts.xmlFile, screenKey .. "#configName"), "");
	local activeConfigs = Utils.getNoNil(getXMLString(specAnimatedIndoorParts.xmlFile, screenKey .. "#activeConfigs"), "");
			
    if configName ~= "" and activeConfig ~= "" then
		allowInsertScreen = false;
				
		local storeItem = g_storeManager:getItemByXMLFilename(vehicle.configFileName);
			
    	if storeItem ~= nil and storeItem.configurations ~= nil and storeItem.configurations[configName] ~= nil then
			local activeConfigs = string.split(activeConfigs, ", ");
            local configurations = storeItem.configurations[configName];
            local config = configurations[vehicle.configurations[configName]];
				
            for _, activeConfig in pairs(activeConfigs) do
				activeConfig = AnimatedIndoorPartsUtil.getL10nText(activeConfig);
						
				if config.name == activeConfig then	
					allowInsertScreen = true;

					break;
				end;
            end;
        end;
    end;

	return allowInsertScreen;
end;