---
-- ExtendedSprayerHUDExtension
-- Custom HUD drawing extension for precision farming sprayers
--
-- Displays the pH / Nitrogen actual and target values
--
-- @author Stefan Maurus
-- @date   23/09/20
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.

ExtendedSprayerHUDExtension = {}

ExtendedSprayerHUDExtension.MOD_NAME = g_currentModName
ExtendedSprayerHUDExtension.MOD_DIR = g_currentModDirectory
ExtendedSprayerHUDExtension.GUI_ELEMENTS = ExtendedSprayerHUDExtension.MOD_DIR .. "gui/ui_elements.png"

local ExtendedSprayerHUDExtension_mt = Class(ExtendedSprayerHUDExtension, VehicleHUDExtension)

-- Register this extension to be displayed for extended sprayers:
VehicleHUDExtension.registerHUDExtension(ExtendedSprayer, ExtendedSprayerHUDExtension)

---Create a new instance of ExtendedSprayerHUDExtension.
-- @param table vehicle Vehicle which has the specialization required by a sub-class
-- @param float uiScale Current UI scale
-- @param table uiTextColor HUD text drawing color as an RGBA array
-- @param float uiTextSize HUD text size
-- @includeCode
function ExtendedSprayerHUDExtension.new(vehicle, uiScale, uiTextColor, uiTextSize)
    local self = VehicleHUDExtension.new(ExtendedSprayerHUDExtension_mt, vehicle, uiScale, uiTextColor, uiTextSize)
    self.extendedSprayer = vehicle.spec_extendedSprayer

    self.validVehicle = self:getIsVehicleValid(vehicle)
    self.realVehicle = self.validVehicle and vehicle or nil

    self.pHGradientUVs = getNormalizedUVs(ExtendedSprayerHUDExtension.UV.PH_GRADIENT)
    self.pHGradientUVsInactive = getNormalizedUVs(ExtendedSprayerHUDExtension.UV.PH_GRADIENT)
    self.nGradientUVs = getNormalizedUVs(ExtendedSprayerHUDExtension.UV.N_GRADIENT)
    self.nGradientUVsInactive = getNormalizedUVs(ExtendedSprayerHUDExtension.UV.N_GRADIENT)
    self.colorBlindGradientUVs = getNormalizedUVs(ExtendedSprayerHUDExtension.UV.COLOR_BLIND_GRADIENT)
    self.colorBlindGradientUVsInactive = getNormalizedUVs(ExtendedSprayerHUDExtension.UV.COLOR_BLIND_GRADIENT)

    self.pixelSizeX, self.pixelSizeY = getNormalizedScreenValues(1 * uiScale, 1 * uiScale)

    local width, height = getNormalizedScreenValues(370 * uiScale, 8 * uiScale)
    self.gradient = Overlay:new(ExtendedSprayerHUDExtension.GUI_ELEMENTS, 0, 0, width, height)
    self.gradient:setUVs(self.pHGradientUVs)
    self.gradient:setColor(1, 1, 1, 1)
    self:addComponentForCleanup(self.gradient)

    self.gradientInactive = Overlay:new(ExtendedSprayerHUDExtension.GUI_ELEMENTS, 0, 0, width, height)
    self.gradientInactive:setUVs(self.pHGradientUVs)
    self.gradientInactive:setColor(0.4, 0.4, 0.4, 1)
    self:addComponentForCleanup(self.gradientInactive)

    width, height = getNormalizedScreenValues(3 * uiScale, 20 * uiScale)
    self.actualBar = Overlay:new(g_baseUIFilename, 0, 0, width, height)
    self.actualBar:setUVs(g_colorBgUVs)
    self.actualBar:setColor(unpack(ExtendedSprayerHUDExtension.COLOR.ACTUAL_BAR))
    self:addComponentForCleanup(self.actualBar)

    width, height = getNormalizedScreenValues(2.5 * uiScale, 5 * uiScale)
    self.targetBar = Overlay:new(ExtendedSprayerHUDExtension.GUI_ELEMENTS, 0, 0, width, height)
    self.targetBar:setUVs(getNormalizedUVs(ExtendedSprayerHUDExtension.UV.TARGET_BAR))
    self:addComponentForCleanup(self.targetBar)

    width, height = getNormalizedScreenValues(23 * uiScale, 29 * uiScale)
    self.targetFlag = Overlay:new(ExtendedSprayerHUDExtension.GUI_ELEMENTS, 0, 0, width, height)
    self.targetFlag:setUVs(getNormalizedUVs(ExtendedSprayerHUDExtension.UV.TARGET_FLAG))
    self:addComponentForCleanup(self.targetFlag)

    width, height = getNormalizedScreenValues(3 * uiScale, 14 * uiScale)
    self.setValueBar = Overlay:new(g_baseUIFilename, 0, 0, width, height)
    self.setValueBar:setUVs(g_colorBgUVs)
    self.setValueBar:setColor(unpack(ExtendedSprayerHUDExtension.COLOR.SET_VALUE_BAR_GOOD))
    self:addComponentForCleanup(self.setValueBar)

    width, height = getNormalizedScreenValues(430 * uiScale, 1 * uiScale)
    self.footerSeparationBar = Overlay:new(g_baseUIFilename, 0, 0, width, height)
    self.footerSeparationBar:setUVs(g_colorBgUVs)
    self.footerSeparationBar:setColor(unpack(ExtendedSprayerHUDExtension.COLOR.SEPARATOR_BAR))
    self:addComponentForCleanup(self.footerSeparationBar)

    local _, displayHeight = getNormalizedScreenValues(0, 130 * uiScale)
    self.displayHeight = displayHeight

    local _, additionalTextHeightOffset = getNormalizedScreenValues(0, 30 * uiScale)
    self.additionalTextHeightOffset = additionalTextHeightOffset

    local _, invalidHeightOffset = getNormalizedScreenValues(0, 60 * uiScale)
    self.invalidHeightOffset = invalidHeightOffset

    local _, noSetBarHeightOffset = getNormalizedScreenValues(0, 20 * uiScale)
    self.noSetBarHeightOffset = noSetBarHeightOffset

    self.additionalDisplayHeight = 0

    _, self.textHeightHeadline = getNormalizedScreenValues(0, 20 * uiScale)
    _, self.textHeight = getNormalizedScreenValues(0, 15 * uiScale)
    self.uiTextColor = uiTextColor

    self.gradientPosX, self.gradientPosY = getNormalizedScreenValues(0, -40 * uiScale)

    _, self.footerOffset = getNormalizedScreenValues(0, 44 * uiScale)

    self.actualPos = 0.34
    self.targetPos = 0.8

    self.actualValue = 0
    self.actualValueStr = "%.3f"

    self.setValue = 0
    self.targetValue = 0

    self.hasValidValues = false

    self.soilMap = g_precisionFarming.soilMap
    self.pHMap = g_precisionFarming.pHMap
    self.nitrogenMap = g_precisionFarming.nitrogenMap

    self.texts = {}
    self.texts.headline_ph_lime = g_i18n:getText("hudExtension_headline_ph_lime", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.headline_n_solidFertilizer = g_i18n:getText("hudExtension_headline_n_solidFertilizer", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.headline_n_liquidFertilizer = g_i18n:getText("hudExtension_headline_n_liquidFertilizer", ExtendedSprayerHUDExtension.MOD_NAME)
	self.texts.headline_n_anhydrous = g_i18n:getText("hudExtension_headline_n_anhydrous", ExtendedSprayerHUDExtension.MOD_NAME)
	self.texts.headline_n_compost = g_i18n:getText("hudExtension_headline_n_compost", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.headline_n_slurryTanker = g_i18n:getText("hudExtension_headline_n_slurryTanker", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.headline_n_manureSpreader = g_i18n:getText("hudExtension_headline_n_manureSpreader", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.actualValue = g_i18n:getText("hudExtension_actualValue", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.newValue = g_i18n:getText("hudExtension_newValue", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.targetReached = g_i18n:getText("hudExtension_targetReached", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.applicationRate = g_i18n:getText("hudExtension_applicationRate", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.soilType = g_i18n:getText("hudExtension_soilType", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.unknown = g_i18n:getText("hudExtension_unknown", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.automaticShort = g_i18n:getText("hudExtension_automaticShort", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.description_limeAuto = g_i18n:getText("hudExtension_description_limeAuto", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.description_limeManual = g_i18n:getText("hudExtension_description_limeManual", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.description_slurryAuto = g_i18n:getText("hudExtension_description_slurryAuto", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.description_manureAuto = g_i18n:getText("hudExtension_description_manureAuto", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.description_fertilizerAutoFruit = g_i18n:getText("hudExtension_description_fertilizerAutoFruit", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.description_fertilizerAutoNoFruit = g_i18n:getText("hudExtension_description_fertilizerAutoNoFruit", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.description_fertilizerManualFruit = g_i18n:getText("hudExtension_description_fertilizerManualFruit", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.description_fertilizerManualNoFruit = g_i18n:getText("hudExtension_description_fertilizerManualNoFruit", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.description_noFertilizerRequired = g_i18n:getText("hudExtension_description_noFertilizerRequired", ExtendedSprayerHUDExtension.MOD_NAME)
    self.texts.invalidValues = g_i18n:getText("hudExtension_invalidValues", ExtendedSprayerHUDExtension.MOD_NAME)

    self.actualValueStr = self.texts.unknown

    self.isColorBlindMode = g_gameSettings:getValue(GameSettings.SETTING.USE_COLORBLIND_MODE) or false

    g_messageCenter:subscribe(MessageType.SETTING_CHANGED[GameSettings.SETTING.USE_COLORBLIND_MODE], self.setColorBlindMode, self)

    return self
end

---Delete this instance and clean up resources.
-- @includeCode
function ExtendedSprayerHUDExtension:delete()
    ExtendedSprayerHUDExtension:superClass().delete(self)
    g_messageCenter:unsubscribeAll(self)
end

---Determine if the HUD extension should be drawn.
-- @includeCode
function ExtendedSprayerHUDExtension:setColorBlindMode(isActive)
    if isActive ~= self.isColorBlindMode then
        self.isColorBlindMode = isActive

        self.pHMap:setMinimapRequiresUpdate(true)
        self.nitrogenMap:setMinimapRequiresUpdate(true)
    end
end

---Determine if the HUD extension should be drawn.
-- @includeCode
function ExtendedSprayerHUDExtension:canDraw()
    local vehicle = self.vehicle
    local spec = vehicle.spec_extendedSprayer

    -- only way to update the valid vehicle state to block rendering if not valid since we don't have a update function
    if spec.attachStateChanged then
        spec.attachStateChanged = false

        local foundVehicle = false
        local rootVehicle = vehicle:getRootVehicle()
        local vehicleList = {}
        rootVehicle:getChildVehicles(vehicleList)
        for i=1, #vehicleList do
            local subVehicle = vehicleList[i]
            if self:getIsVehicleValid(subVehicle) then
                self.validVehicle = true
                self.realVehicle = subVehicle
                foundVehicle = true
            end
        end

        if not foundVehicle then
            self.validVehicle = false
            self.realVehicle = nil
        end
    end

    if not self.validVehicle or self.realVehicle == nil then
        return false
    end

    if not self.vehicle:getIsActiveForInput(true, true) then
        return false
    end

    if self.validVehicle then
        local sourceVehicle, sourceFillUnitIndex = self:getFillTypeSourceVehicle(self.vehicle)
        if sourceVehicle:getFillUnitFillLevel(sourceFillUnitIndex) <= 0 then
            return false
        end

        spec = self.realVehicle.spec_extendedSprayer
        if not spec.isLiming and not spec.isFertilizing then
            return false
        end
    end

    return true
end

---
-- @includeCode
function ExtendedSprayerHUDExtension:getFillTypeSourceVehicle(sprayer)
    -- check the valid sprayer if he has a fill type source to consume from, otherwise hide the display
    if sprayer:getFillUnitFillLevel(sprayer:getSprayerFillUnitIndex()) <= 0 then
        local spec = sprayer.spec_sprayer
        for _, supportedSprayType in ipairs(spec.supportedSprayTypes) do
            for _, src in ipairs(spec.fillTypeSources[supportedSprayType]) do
                local vehicle = src.vehicle
                if vehicle:getFillUnitFillType(src.fillUnitIndex) == supportedSprayType and vehicle:getFillUnitFillLevel(src.fillUnitIndex) > 0 then
                    return vehicle, src.fillUnitIndex
                end
            end
        end
    end

    return sprayer, sprayer:getSprayerFillUnitIndex()
end

---
-- @includeCode
function ExtendedSprayerHUDExtension:getIsVehicleValid(vehicle)
    if not SpecializationUtil.hasSpecialization(ExtendedSprayer, vehicle.specializations) then
        return false
    end

    if not SpecializationUtil.hasSpecialization(WorkArea, vehicle.specializations) then
        return false
    end

    if #vehicle.spec_workArea.workAreas == 0 then
        return false
    end

    if SpecializationUtil.hasSpecialization(ManureBarrel, vehicle.specializations) and vehicle.spec_manureBarrel.attachedTool ~= nil then
        return false
    end

    return true
end

---Get this HUD extension's display height.
-- @return float Display height in screen space
-- @includeCode
function ExtendedSprayerHUDExtension:getDisplayHeight()
    return self:canDraw() and (self.displayHeight or 0) + (self.additionalDisplayHeight or 0)
end

local function formatDecimalNumber(n)
    if math.floor(n) == n then
        return string.format("%.1f", n)
    else
        return string.format("%s", n)
    end
end

local function renderDoubleText(x, y, textSize, text)
    setTextColor(0, 0, 0, 1)
    renderText(x, y-0.0015, textSize, text)
    setTextColor(1, 1, 1, 1)
    renderText(x, y, textSize, text)
end

---Draw mixing ratio information for a mixing wagon when it is active.
-- @param float leftPosX Left input help panel column start position
-- @param float rightPosX Right input help panel column start position
-- @param float posY Current input help panel drawing vertical offset
-- @return float Modified input help panel drawing vertical offset
-- @includeCode
function ExtendedSprayerHUDExtension:draw(leftPosX, rightPosX, posY)
    if not self:canDraw() then
        return
    end

    local vehicle = self.realVehicle
    local spec = vehicle.spec_extendedSprayer

    local headline = self.texts.headline_ph_lime

    local applicationRate = 0
    local applicationRateReal = 0
    local applicationRateStr = "%.2f t/ha"

    local changeBarText = ""

    local minValue = 0
    local maxValue = 0

    self.hasValidValues = false

    local soilTypeName = ""

    if spec.lastTouchedSoilType ~= 0 and self.soilMap ~= nil then
        local soilType = self.soilMap:getSoilTypeByIndex(spec.lastTouchedSoilType)
        if soilType ~= nil then
            soilTypeName = soilType.name
        end
    end

    local hasLimeLoaded = false
    local fillTypeDesc
    local sourceVehicle, fillUnitIndex = self:getFillTypeSourceVehicle(self.vehicle)
    local sprayFillType = sourceVehicle:getFillUnitFillType(fillUnitIndex)
    fillTypeDesc = g_fillTypeManager:getFillTypeByIndex(sprayFillType)
    local massPerLiter = (fillTypeDesc.massPerLiter / FillTypeManager.MASS_SCALE)
    if sprayFillType == FillType.LIME then
        hasLimeLoaded = true
    end

    local descriptionText = ""
    local stepResolution

    local enableZeroTargetFlag = false

    if hasLimeLoaded then
        self.gradientInactive:setUVs(self.isColorBlindMode and self.colorBlindGradientUVsInactive or self.pHGradientUVsInactive)
        self.gradient:setUVs(self.isColorBlindMode and self.colorBlindGradientUVs or self.pHGradientUVs)

        local pHChanged = 0
        applicationRate = spec.lastLitersPerHectar * massPerLiter
        if not spec.sprayAmountAutoMode then
            local requiredLitersPerHa = self.pHMap:getLimeUsageByStateChange(spec.sprayAmountManual)
            pHChanged = self.pHMap:getPhValueFromChangedStates(spec.sprayAmountManual)
            applicationRate = requiredLitersPerHa * massPerLiter

            if pHChanged > 0 then
                changeBarText = string.format("pH +%s", formatDecimalNumber(pHChanged))
            end
        end

        local pHActualInt = spec.phActualBuffer:get()
        local pHTargetInt = spec.phTargetBuffer:get()
        local pHActual = self.pHMap:getPhValueFromInternalValue(pHActualInt)
        local pHTarget = self.pHMap:getPhValueFromInternalValue(pHTargetInt)
        if pHActualInt ~= 0 and pHTargetInt ~= 0 then
            self.actualValue = pHActual
            self.setValue = pHActual + pHChanged
            self.targetValue = pHTarget

            if spec.sprayAmountAutoMode then
                pHChanged = self.targetValue - self.actualValue
                if pHChanged > 0 then
                    changeBarText = string.format("pH +%s", formatDecimalNumber(pHChanged))
                end

                self.setValue = self.targetValue
            end

            self.actualValueStr = "pH %.3f"
            if soilTypeName ~= "" then
                if spec.sprayAmountAutoMode then
                    descriptionText = string.format(self.texts.description_limeAuto, soilTypeName, pHTarget)
                else
                    descriptionText = string.format(self.texts.description_limeManual, soilTypeName, pHTarget)
                end
            end

            self.hasValidValues = true
        end

        if self.pHMap ~= nil then
            minValue, maxValue = self.pHMap:getMinMaxValue()
        end

        stepResolution = spec.pHMap:getPhValueFromChangedStates(1)
    else
        self.gradientInactive:setUVs(self.isColorBlindMode and self.colorBlindGradientUVsInactive or self.nGradientUVsInactive)
        self.gradient:setUVs(self.isColorBlindMode and self.colorBlindGradientUVs or self.nGradientUVs)

        local litersPerHectar = spec.lastLitersPerHectar
        local nitrogenChanged = 0
        if not spec.sprayAmountAutoMode then
            litersPerHectar = self.nitrogenMap:getFertilizerUsageByStateChange(spec.sprayAmountManual, sprayFillType)
            nitrogenChanged = self.nitrogenMap:getNitrogenFromChangedStates(spec.sprayAmountManual)

            if nitrogenChanged > 0 then
                changeBarText = string.format("+%dkg N/ha", nitrogenChanged)
            end
        end

        if spec.isSolidFertilizerSprayer then
            headline = self.texts.headline_n_solidFertilizer
            applicationRateStr = "%d kg/ha"
            applicationRate = litersPerHectar * massPerLiter * 1000
        elseif spec.isLiquidFertilizerSprayer then
            headline = self.texts.headline_n_liquidFertilizer
            applicationRateStr = "%d l/ha"
            applicationRate = litersPerHectar
        elseif spec.isSlurryTanker then
            headline = self.texts.headline_n_slurryTanker
            applicationRateStr = "%d m³/ha"
            applicationRate = litersPerHectar / 1000

            if spec.sprayAmountAutoMode and soilTypeName ~= "" then
                descriptionText = string.format(self.texts.description_slurryAuto, soilTypeName)
            end
        elseif spec.isManureSpreader then
            headline = self.texts.headline_n_manureSpreader
            applicationRateStr = "%.1f t/ha"
            applicationRate = litersPerHectar * massPerLiter

            if spec.sprayAmountAutoMode and soilTypeName ~= "" then
                descriptionText = string.format(self.texts.description_manureAuto, soilTypeName)
            end
        end

        local nActualInt = spec.nActualBuffer:get()
        local nTargetInt = spec.nTargetBuffer:get()
        local nActual = self.nitrogenMap:getNitrogenValueFromInternalValue(nActualInt)
        local nTarget = self.nitrogenMap:getNitrogenValueFromInternalValue(nTargetInt)
        if nActualInt > 0 and nTargetInt > 0 then
            self.actualValue = nActual
            self.setValue = nActual + nitrogenChanged
            self.targetValue = nTarget

            if spec.sprayAmountAutoMode then
                nitrogenChanged = self.targetValue - self.actualValue
                if nitrogenChanged > 0 then
                    changeBarText = string.format("+%dkg N/ha", nitrogenChanged)
                end

                self.setValue = self.targetValue
            end

            self.actualValueStr = "%dkg N/ha"

            local fillType = g_fillTypeManager:getFillTypeByIndex(g_fruitTypeManager:getFillTypeIndexByFruitTypeIndex(spec.nApplyAutoModeFruitType))
            if fillType ~= nil then
                if fillType ~= FillType.UNKNOWN and soilTypeName ~= "" then
                    if nTarget > 0 then
                        if spec.sprayAmountAutoMode then
                            descriptionText = string.format(self.texts.description_fertilizerAutoFruit, fillType.title, soilTypeName)
                        else
                            descriptionText = string.format(self.texts.description_fertilizerManualFruit, fillType.title, soilTypeName)
                        end
                    else
                        descriptionText = self.texts.description_noFertilizerRequired
                        enableZeroTargetFlag = true
                    end
                end
            end

            if descriptionText == "" and soilTypeName ~= "" then
                if spec.sprayAmountAutoMode then
                    descriptionText = string.format(self.texts.description_fertilizerAutoNoFruit, soilTypeName)
                else
                    descriptionText = string.format(self.texts.description_fertilizerManualNoFruit, soilTypeName)
                end
            end

            self.hasValidValues = true
        end

        if self.nitrogenMap ~= nil then
            minValue, maxValue = self.nitrogenMap:getMinMaxValue()

            local nAmount = self.nitrogenMap:getNitrogenAmountFromFillType(sprayFillType)
            applicationRateStr = applicationRateStr .. string.format(" (%s%%%%N)", MathUtil.round(nAmount * 100, 1))

            stepResolution = self.nitrogenMap:getNitrogenFromChangedStates(1)
        end
    end

    if spec.sprayAmountAutoMode then
        applicationRateStr = applicationRateStr .. string.format(" (%s)", self.texts.automaticShort)
        soilTypeName = ""
    end

    self.actualPos = math.min((self.actualValue - minValue) / (maxValue - minValue), 1)
    self.setValuePos = math.min((self.setValue - minValue) / (maxValue - minValue), 1)
    self.targetPos = math.min((self.targetValue - minValue) / (maxValue - minValue), 1)

    local centerX = leftPosX + (rightPosX - leftPosX) * 0.5

    setTextColor(unpack(self.uiTextColor))
    setTextBold(true)
    setTextAlignment(RenderText.ALIGN_CENTER)
    renderDoubleText(centerX, posY + self.textHeightHeadline * 0.5, self.textHeightHeadline, headline)
    setTextBold(false)

    -- gradient
    local gradientPosX = centerX - self.gradientInactive.width * 0.5 + self.gradientPosX
    local gradientPosY = posY + self.gradientPosY
    if not self.hasValidValues then
        gradientPosY = gradientPosY + (self.actualBar.height - self.gradientInactive.height) + self.textHeight
    end

    self.gradientInactive:setPosition(gradientPosX, gradientPosY)
    self.gradientInactive:render()

    local gradientVisibilePos = 0
    if self.hasValidValues then
        gradientVisibilePos = self.actualPos
    end

    self.gradient:setPosition(gradientPosX, gradientPosY)
    self.gradient:setDimension(gradientVisibilePos * self.gradientInactive.width)
    self.gradient.uvs[5] = self.gradientInactive.uvs[1] + (self.gradientInactive.uvs[5] - self.gradientInactive.uvs[1]) * gradientVisibilePos
    self.gradient.uvs[7] = self.gradientInactive.uvs[3] + (self.gradientInactive.uvs[7] - self.gradientInactive.uvs[3]) * gradientVisibilePos
    self.gradient:setUVs(self.gradient.uvs)
    self.gradient:render()

    local labelMin
    local labelMax
    if hasLimeLoaded then
        labelMin = string.format("pH\n%s", minValue)
        labelMax = string.format("pH\n%s", maxValue)
    else
        labelMin = string.format("%s\nkg/ha", minValue)
        labelMax = string.format("%s\nkg/ha", maxValue)
    end

    local widthDiff = ((rightPosX - leftPosX) - self.gradientInactive.width) * 0.425
    renderDoubleText(gradientPosX - widthDiff, gradientPosY + self.gradientInactive.height * 0.85, self.gradientInactive.height * 1.3, labelMin)
    renderDoubleText(gradientPosX + self.gradientInactive.width + widthDiff, gradientPosY + self.gradientInactive.height * 0.85, self.gradientInactive.height * 1.3, labelMax)

    local additionalChangeLineHeight = 0

    -- actual
    local changeBarRendered = false
    if self.hasValidValues then
        -- target
        local targetBarX, targetBarY
        local showFlag = self.targetPos ~= 0 or enableZeroTargetFlag
        if showFlag then
            targetBarX = gradientPosX + self.gradientInactive.width * self.targetPos - self.targetBar.width * 0.5
            targetBarY = gradientPosY
            self.targetBar:setPosition(targetBarX, targetBarY)
            self.targetBar:render()

            self.targetFlag:setPosition(targetBarX, targetBarY + self.targetBar.height)
            self.targetFlag:render()
        end

        local actualBarText
        local actualBarTextOffset = self.actualBar.height + self.textHeight * 1.1
        local actualBarSkipFlagCollisionCheck = false
        if self.actualPos ~= self.targetPos then
            actualBarText = string.format(self.texts.actualValue, string.format(self.actualValueStr, self.actualValue))
        elseif spec.sprayAmountAutoMode or self.targetPos == self.setValuePos then
            if self.targetPos ~= 0 then
                actualBarText = string.format(self.texts.targetReached, string.format(self.actualValueStr, self.actualValue))
                actualBarTextOffset = -self.textHeight * 0.7
                actualBarSkipFlagCollisionCheck = true
                changeBarRendered = true
            end
        end

        if actualBarText ~= nil then
            local actualBarX = gradientPosX + self.gradientInactive.width * self.actualPos - self.actualBar.width * 0.5
            local actualBarY = gradientPosY + (self.gradientInactive.height - self.actualBar.height) * 0.5

            self.actualBar:setPosition(actualBarX, actualBarY)
            self.actualBar:render()

            local actualTextWidth = getTextWidth(self.textHeight * 0.7, actualBarText)
            actualBarX = math.max(math.min(actualBarX, rightPosX - actualTextWidth * 0.5), leftPosX + actualTextWidth * 0.5)

            if not actualBarSkipFlagCollisionCheck and showFlag then
                local rightTextBorder = actualBarX + actualTextWidth * 0.5
                if rightTextBorder > targetBarX and rightTextBorder < targetBarX + self.targetFlag.width * 0.5 then
                    actualBarX = targetBarX - actualTextWidth * 0.5 - self.pixelSizeX
                end

                local leftTextBorder = actualBarX - actualTextWidth * 0.5
                if (leftTextBorder > targetBarX and leftTextBorder < targetBarX + self.targetFlag.width * 0.5)
                or (targetBarX > leftTextBorder and targetBarX < rightTextBorder) then
                    actualBarX = targetBarX + self.targetFlag.width + self.pixelSizeX + actualTextWidth * 0.5
                end
            end

            renderDoubleText(actualBarX, actualBarY + actualBarTextOffset, self.textHeight * 0.7, actualBarText)
        end

        if self.setValuePos > self.actualPos then
            local goodColor = ExtendedSprayerHUDExtension.COLOR.SET_VALUE_BAR_GOOD
            local badColor = ExtendedSprayerHUDExtension.COLOR.SET_VALUE_BAR_BAD
            local difference = math.min((math.abs(self.setValue - self.targetValue) / stepResolution) / 3, 1)
            local differenceInv = 1 - difference
            local r, g, b, a = difference * badColor[1] + differenceInv * goodColor[1],
                            difference * badColor[2] + differenceInv * goodColor[2],
                            difference * badColor[3] + differenceInv * goodColor[3],
                            1
            local setValueBarX = gradientPosX + self.gradientInactive.width * self.actualPos
            local setValueBarY = gradientPosY - self.gradientInactive.height - self.setValueBar.height
            self.setValueBar:setPosition(setValueBarX, setValueBarY)
            self.setValueBar:setDimension(self.gradientInactive.width * (math.min(self.setValuePos, 1) - self.actualPos))
            self.setValueBar:setColor(r, g, b, a)
            self.setValueBar:render()

            local setBarTextX = setValueBarX + self.setValueBar.width * 0.5
            local setBarTextY = setValueBarY + self.setValueBar.height * 0.2
            local setTextWidth = getTextWidth(self.setValueBar.height * 0.9, changeBarText)
            if setTextWidth > self.setValueBar.width * 0.95 then
                setBarTextY = setValueBarY - self.setValueBar.height
                additionalChangeLineHeight = self.setValueBar.height
            end
            renderDoubleText(setBarTextX, setBarTextY, self.setValueBar.height * 0.9, changeBarText)

            changeBarRendered = true
        end
    else
        descriptionText = self.texts.invalidValues
    end

    local bottomPosY = posY - self.displayHeight - self.additionalDisplayHeight

    if descriptionText ~= "" and self.additionalDisplayHeight ~= 0 then
        setTextAlignment(RenderText.ALIGN_CENTER)
        local textSize = self.textHeight * 0.85
        while getTextWidth(textSize, descriptionText) > self.footerSeparationBar.width do
            textSize = textSize * 0.98
        end
        renderDoubleText(centerX, bottomPosY + self.footerOffset + self.additionalTextHeightOffset - textSize * 0.25, textSize, descriptionText)
    end

    self.footerSeparationBar:setPosition(centerX - self.footerSeparationBar.width * 0.5, bottomPosY + self.footerOffset + self.textHeight * 1.2)
    self.footerSeparationBar:render()

    -- footer
    setTextAlignment(RenderText.ALIGN_LEFT)
    renderDoubleText(leftPosX, bottomPosY + self.footerOffset, self.textHeight, self.texts.applicationRate .. " " .. string.format(applicationRateStr, applicationRate, applicationRateReal))

    if soilTypeName ~= "" then
        setTextAlignment(RenderText.ALIGN_RIGHT)
        renderDoubleText(rightPosX, bottomPosY + self.footerOffset, self.textHeight, string.format(self.texts.soilType, soilTypeName))
    end

    -- do that at the end so we give the ui some time to increase the window height and then render the text above
    self.additionalDisplayHeight = additionalChangeLineHeight
    if descriptionText ~= "" then
        self.additionalDisplayHeight = self.additionalDisplayHeight + self.additionalTextHeightOffset
    end
    if not self.hasValidValues then
        self.additionalDisplayHeight = self.additionalDisplayHeight - self.invalidHeightOffset
    elseif not changeBarRendered then
        self.additionalDisplayHeight = self.additionalDisplayHeight - self.noSetBarHeightOffset
    end

    return posY
end

ExtendedSprayerHUDExtension.COLOR = {
    TARGET_BAR = {1, 1, 1, 1},
    ACTUAL_BAR = {0.6010, 0.0100, 0.0100, 1},
    SEPARATOR_BAR = {1, 1, 1, 0.3},
    SET_VALUE_BAR_GOOD = {0.0100, 0.4100, 0.0100, 1},
    SET_VALUE_BAR_BAD = {0.6100, 0.0100, 0.0100, 1},
}

ExtendedSprayerHUDExtension.UV = {
    PH_GRADIENT = {194, 11, 252, 12},
    N_GRADIENT = {194, 27, 252, 12},
    COLOR_BLIND_GRADIENT = {194, 43, 252, 12},
    TARGET_FLAG = {405, 70, 36, 52},
    TARGET_BAR = {405, 98, 4, 2}
}
