AdjustableMirrors = {}

function AdjustableMirrors.prerequisitesPresent(specializations)
    return true
end

local function clamp(value, minValue, maxValue)
    value = value or 0
    minValue = minValue or value
    maxValue = maxValue or value

    if minValue > maxValue then
        minValue, maxValue = maxValue, minValue
    end

    if value < minValue then
        return minValue
    elseif value > maxValue then
        return maxValue
    end

    return value
end

local function smoothTo(current, target, speed, dt)
    current = current or 0
    target = target or current
    speed = speed or 4
    dt = dt or 0

    local factor = math.min(1, speed * dt / 1000)
    return current + ((target - current) * factor)
end

local function getXmlFloatValue(xmlFile, key, defaultValue)
    if xmlFile ~= nil and xmlFile.getFloat ~= nil then
        return xmlFile:getFloat(key, defaultValue)
    end

    local value = getXMLFloat(xmlFile, key)
    return value ~= nil and value or defaultValue
end

local function getXmlStringValue(xmlFile, key, defaultValue)
    if xmlFile ~= nil and xmlFile.getString ~= nil then
        return xmlFile:getString(key, defaultValue)
    end

    local value = getXMLString(xmlFile, key)
    return value ~= nil and value or defaultValue
end

local function hasXmlPropertyValue(xmlFile, key)
    if xmlFile ~= nil and xmlFile.hasProperty ~= nil then
        return xmlFile:hasProperty(key)
    end

    return hasXMLProperty(xmlFile, key)
end

local function applyMirrorRotation(mirror)
    mirror.rotX = clamp(
        mirror.rotX,
        (mirror.defaultRotX or 0) + (mirror.minRotX or math.rad(-10)),
        (mirror.defaultRotX or 0) + (mirror.maxRotX or math.rad(10))
    )

    mirror.rotY = clamp(
        mirror.rotY,
        (mirror.defaultRotY or 0) + (mirror.minRotY or math.rad(-10)),
        (mirror.defaultRotY or 0) + (mirror.maxRotY or math.rad(10))
    )

    mirror.rotZ = clamp(
        mirror.rotZ,
        (mirror.defaultRotZ or 0) + (mirror.minRotZ or math.rad(-10)),
        (mirror.defaultRotZ or 0) + (mirror.maxRotZ or math.rad(10))
    )

    setRotation(
        mirror.node,
        mirror.rotX or 0,
        mirror.rotY or 0,
        mirror.rotZ or 0
    )
end

local function resetMirror(mirror)
    mirror.targetRotX = mirror.defaultRotX
    mirror.targetRotY = mirror.defaultRotY
    mirror.targetRotZ = mirror.defaultRotZ
end

local function sendMirrorEvent(vehicle)
    if AdjustableMirrorsEvent ~= nil then
        AdjustableMirrorsEvent.sendEvent(vehicle)
    end
end

function AdjustableMirrors.setMirrorOffset(vehicle, name, rotX, rotY, rotZ, noEventSend)
    local spec = vehicle.spec_adjustableMirrors
    if spec == nil then
        return
    end

    local mirror = spec.mirrors[name]
    if mirror == nil then
        return
    end

    mirror.targetRotX = rotX or mirror.targetRotX or mirror.rotX
    mirror.targetRotY = rotY or mirror.targetRotY or mirror.rotY
    mirror.targetRotZ = rotZ or mirror.targetRotZ or mirror.rotZ

    if noEventSend ~= true then
        sendMirrorEvent(vehicle)
    end
end

local function loadMirrorsFromSavegame(vehicle, savegame)
    local spec = vehicle.spec_adjustableMirrors

    if spec == nil or savegame == nil or savegame.xmlFile == nil then
        return
    end

    local xmlFile = savegame.xmlFile

    local possibleKeys = {
        savegame.key .. ".FS25_AVM_7R.adjustableMirrors",
        savegame.key .. ".adjustableMirrors",
        savegame.key
    }

    local key = nil

    for _, testKey in ipairs(possibleKeys) do
        if xmlFile:hasProperty(testKey .. ".mirror(0)") then
            key = testKey
            break
        end
    end

    if key == nil then
        return
    end

    local i = 0
    while true do
        local mirrorKey = string.format("%s.mirror(%d)", key, i)

        if not xmlFile:hasProperty(mirrorKey) then
            break
        end

        local name = xmlFile:getString(mirrorKey .. "#name")
        local mirror = spec.mirrors[name]

        if mirror ~= nil then
            mirror.rotX = xmlFile:getFloat(mirrorKey .. "#rotX", mirror.defaultRotX)
            mirror.rotY = xmlFile:getFloat(mirrorKey .. "#rotY", mirror.defaultRotY)
            mirror.rotZ = xmlFile:getFloat(mirrorKey .. "#rotZ", mirror.defaultRotZ)

            mirror.targetRotX = mirror.rotX
            mirror.targetRotY = mirror.rotY
            mirror.targetRotZ = mirror.rotZ

            applyMirrorRotation(mirror)
        end

        i = i + 1
    end
end

function AdjustableMirrors.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onLoad", AdjustableMirrors)
    SpecializationUtil.registerEventListener(vehicleType, "onUpdate", AdjustableMirrors)
    SpecializationUtil.registerEventListener(vehicleType, "saveToXMLFile", AdjustableMirrors)
end

function AdjustableMirrors:onLoad(savegame)
    local spec = {}
    self.spec_adjustableMirrors = spec

    spec.speed = getXmlFloatValue(self.xmlFile, "vehicle.adjustableMirrors#speed", 0.45)
    spec.smoothSpeed = getXmlFloatValue(self.xmlFile, "vehicle.adjustableMirrors#smoothSpeed", 4)
    spec.mirrors = {}
    spec.buttons = {}
    spec.buttonsInitialized = false

    local i = 0
    while true do
        local key = string.format("vehicle.adjustableMirrors.mirror(%d)", i)

        if not hasXmlPropertyValue(self.xmlFile, key) then
            break
        end

        local mirror = {}

        mirror.name = getXmlStringValue(self.xmlFile, key .. "#name")
        local nodeMapping = getXmlStringValue(self.xmlFile, key .. "#node")

        if mirror.name == nil then
            mirror.name = nodeMapping or ("mirror" .. tostring(i))
        end

        mirror.node = nil

        if nodeMapping ~= nil and self.i3dMappings ~= nil then
            local mappedNode = self.i3dMappings[nodeMapping]

            if type(mappedNode) == "number" then
                mirror.node = mappedNode
            elseif type(mappedNode) == "table" then
                mirror.node = mappedNode.node or mappedNode.nodeId or mappedNode.id or mappedNode[1]
            end
        end

        mirror.minRotX = math.rad(getXmlFloatValue(self.xmlFile, key .. "#minRotX", -8))
        mirror.maxRotX = math.rad(getXmlFloatValue(self.xmlFile, key .. "#maxRotX", 8))

        mirror.minRotY = math.rad(getXmlFloatValue(self.xmlFile, key .. "#minRotY", -10))
        mirror.maxRotY = math.rad(getXmlFloatValue(self.xmlFile, key .. "#maxRotY", 10))

        mirror.minRotZ = math.rad(getXmlFloatValue(self.xmlFile, key .. "#minRotZ", -10))
        mirror.maxRotZ = math.rad(getXmlFloatValue(self.xmlFile, key .. "#maxRotZ", 10))

        if mirror.node ~= nil and type(mirror.node) == "number" and mirror.node ~= 0 then
            mirror.defaultRotX, mirror.defaultRotY, mirror.defaultRotZ = getRotation(mirror.node)

            mirror.rotX = mirror.defaultRotX
            mirror.rotY = mirror.defaultRotY
            mirror.rotZ = mirror.defaultRotZ

            mirror.targetRotX = mirror.rotX
            mirror.targetRotY = mirror.rotY
            mirror.targetRotZ = mirror.rotZ

            spec.mirrors[mirror.name] = mirror
        else
            print("AdjustableMirrors WARNING: invalid node '" .. tostring(nodeMapping) .. "'")
        end

        i = i + 1
    end

    local j = 0
    while true do
        local key = string.format("vehicle.adjustableMirrors.button(%d)", j)

        if not hasXmlPropertyValue(self.xmlFile, key) then
            break
        end

        local button = {}
        button.animation = getXmlStringValue(self.xmlFile, key .. "#animation")
        button.mirror = getXmlStringValue(self.xmlFile, key .. "#mirror")
        button.direction = getXmlStringValue(self.xmlFile, key .. "#direction")
        button.step = getXmlFloatValue(self.xmlFile, key .. "#step", 0.2)
        button.lastAnimTime = 0

        if button.animation ~= nil and button.mirror ~= nil and button.direction ~= nil then
            table.insert(spec.buttons, button)
        end

        j = j + 1
    end

    loadMirrorsFromSavegame(self, savegame)
end

function AdjustableMirrors:onUpdate(dt)
    local spec = self.spec_adjustableMirrors
    if spec == nil then
        return
    end

    if not spec.buttonsInitialized then
        for _, button in ipairs(spec.buttons) do
            local animTime = 0

            if self.getAnimationTime ~= nil then
                animTime = self:getAnimationTime(button.animation) or 0
            end

            button.lastAnimTime = animTime
        end

        spec.buttonsInitialized = true
        return
    end

    for _, button in ipairs(spec.buttons) do
        local animTime = 0

        if self.getAnimationTime ~= nil then
            animTime = self:getAnimationTime(button.animation) or 0
        end

        local diff = math.abs(animTime - (button.lastAnimTime or animTime))
        local isClick = diff > 0.01

        button.lastAnimTime = animTime

        if isClick then
            if button.direction == "reset" then
                if button.mirror == "all" then
                    for _, mirror in pairs(spec.mirrors) do
                        resetMirror(mirror)
                    end
                else
                    local mirror = spec.mirrors[button.mirror]
                    if mirror ~= nil then
                        resetMirror(mirror)
                    end
                end

                sendMirrorEvent(self)
            else
                local mirror = spec.mirrors[button.mirror]

                if mirror ~= nil then
                    local stepAmount = math.rad(button.step or 0.2)

                    mirror.targetRotX = mirror.targetRotX or mirror.rotX
                    mirror.targetRotY = mirror.targetRotY or mirror.rotY
                    mirror.targetRotZ = mirror.targetRotZ or mirror.rotZ

                    if button.direction == "up" then
                        mirror.targetRotX = mirror.targetRotX + stepAmount
                    elseif button.direction == "down" then
                        mirror.targetRotX = mirror.targetRotX - stepAmount
                    elseif button.direction == "left" then
                        mirror.targetRotY = mirror.targetRotY - stepAmount
                    elseif button.direction == "right" then
                        mirror.targetRotY = mirror.targetRotY + stepAmount
                    end

                    sendMirrorEvent(self)
                end
            end
        end
    end

    for _, mirror in pairs(spec.mirrors) do
        mirror.rotX = smoothTo(mirror.rotX, mirror.targetRotX or mirror.rotX, spec.smoothSpeed, dt)
        mirror.rotY = smoothTo(mirror.rotY, mirror.targetRotY or mirror.rotY, spec.smoothSpeed, dt)
        mirror.rotZ = smoothTo(mirror.rotZ, mirror.targetRotZ or mirror.rotZ, spec.smoothSpeed, dt)

        applyMirrorRotation(mirror)
    end
end

function AdjustableMirrors:saveToXMLFile(xmlFile, key, usedModNames)
    local spec = self.spec_adjustableMirrors
    if spec == nil then
        return
    end

    local i = 0

    for name, mirror in pairs(spec.mirrors) do
        local mirrorKey = string.format("%s.mirror(%d)", key, i)

        xmlFile:setString(mirrorKey .. "#name", name)
        xmlFile:setFloat(mirrorKey .. "#rotX", mirror.targetRotX or mirror.rotX or mirror.defaultRotX or 0)
        xmlFile:setFloat(mirrorKey .. "#rotY", mirror.targetRotY or mirror.rotY or mirror.defaultRotY or 0)
        xmlFile:setFloat(mirrorKey .. "#rotZ", mirror.targetRotZ or mirror.rotZ or mirror.defaultRotZ or 0)

        i = i + 1
    end
end