--
-- SoilSampler
--
-- @author  Stefan Maurus
-- @date  26/06/2020
--
-- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved.

---Specialization for soil samplers
-- @category Specializations
SoilSampler = {}

SoilSampler.MOD_NAME = g_currentModName

SoilSampler.SEND_NUM_BITS = 9 -- max 512 samples

SoilSampler.SAMPLING_OCTAGON = {{0.33, 0, -1, -0.33, 0, -1, -0.33, 0, -0.33},
                                {1, 0, 0.33, 1, 0, -0.33, 0.33, 0, -0.33},
                                {-0.33, 0, 1, 0.33, 0, 1, 0.33, 0, 0.33},
                                {-1, 0, -0.33, -1, 0, 0.33, -0.33, 0, 0.33},
                                {-0.33, 0, 1, -0.33, 0, -0.33, 0.33, 0, 0.33},
                                {1, 0, 0.33, -0.33, 0, 0.33, 0.33, 0, -0.33}}

---Checks if all prerequisite specializations are loaded
-- @param table specializations specializations
-- @return boolean hasPrerequisite true if all prerequisite specializations are loaded
-- @includeCode
function SoilSampler.prerequisitesPresent(specializations)
    return SpecializationUtil.hasSpecialization(PrecisionFarmingStatistic, specializations)
end

function SoilSampler.registerFunctions(vehicleType)
    SpecializationUtil.registerFunction(vehicleType, "startSoilSampling", SoilSampler.startSoilSampling)
    SpecializationUtil.registerFunction(vehicleType, "setNumCollectedSoilSamples", SoilSampler.setNumCollectedSoilSamples)
    SpecializationUtil.registerFunction(vehicleType, "getNormalizedSampleIndex", SoilSampler.getNormalizedSampleIndex)
    SpecializationUtil.registerFunction(vehicleType, "getCanStartSoilSampling", SoilSampler.getCanStartSoilSampling)
    SpecializationUtil.registerFunction(vehicleType, "processSoilSampling", SoilSampler.processSoilSampling)
    SpecializationUtil.registerFunction(vehicleType, "sendTakenSoilSamples", SoilSampler.sendTakenSoilSamples)
end

function SoilSampler.registerOverwrittenFunctions(vehicleType)
    SpecializationUtil.registerOverwrittenFunction(vehicleType, "doCheckSpeedLimit", SoilSampler.doCheckSpeedLimit)
    SpecializationUtil.registerOverwrittenFunction(vehicleType, "getIsFoldAllowed", SoilSampler.getIsFoldAllowed)
end

function SoilSampler.registerEventListeners(vehicleType)
    SpecializationUtil.registerEventListener(vehicleType, "onLoad", SoilSampler)
    SpecializationUtil.registerEventListener(vehicleType, "onPostLoad", SoilSampler)
    SpecializationUtil.registerEventListener(vehicleType, "onReadStream", SoilSampler)
    SpecializationUtil.registerEventListener(vehicleType, "onWriteStream", SoilSampler)
    SpecializationUtil.registerEventListener(vehicleType, "onUpdate", SoilSampler)
    SpecializationUtil.registerEventListener(vehicleType, "onFoldStateChanged", SoilSampler)
    SpecializationUtil.registerEventListener(vehicleType, "onLeaveRootVehicle", SoilSampler)
    SpecializationUtil.registerEventListener(vehicleType, "onPreDetach", SoilSampler)
    SpecializationUtil.registerEventListener(vehicleType, "onRegisterActionEvents", SoilSampler)
end

---
-- @includeCode
function SoilSampler:onLoad(savegame)
    local specName = SoilSampler.MOD_NAME .. ".soilSampler"
    self.spec_soilSampler = self["spec_" .. specName]
    local spec = self.spec_soilSampler

    local baseName = "vehicle.soilSampler"

    spec.samplingNode = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, baseName .. "#node"), self.i3dMappings)
    spec.samplingRadius = getXMLFloat(self.xmlFile, baseName .. "#radius") or 10

    local actionNameTake = getXMLString(self.xmlFile, baseName .. "#actionNameTake") or "IMPLEMENT_EXTRA"
    spec.inputActionTake = InputAction[actionNameTake] or InputAction.IMPLEMENT_EXTRA

    local actionNameSend = getXMLString(self.xmlFile, baseName .. "#actionNameSend") or "IMPLEMENT_EXTRA3"
    spec.inputActionSend = InputAction[actionNameSend] or InputAction.IMPLEMENT_EXTRA

    spec.isSampling = false

    spec.animationName = getXMLString(self.xmlFile, baseName .. "#animationName")
    spec.animationSpeed = getXMLFloat(self.xmlFile, baseName .. "#animationSpeed") or 1

    spec.numCollectedSamples = 0

    spec.foldMinLimit = getXMLFloat(self.xmlFile, baseName .. "#foldMinLimit") or 0
    spec.foldMaxLimit = getXMLFloat(self.xmlFile, baseName .. "#foldMaxLimit") or 1

    spec.samplesAnimation = {}
    spec.samplesAnimation.name = getXMLString(self.xmlFile, baseName .. ".samplesAnimation#name") or 1
    spec.samplesAnimation.speed = getXMLFloat(self.xmlFile, baseName .. ".samplesAnimation#speed") or 1
    spec.samplesAnimation.minSamples = getXMLInt(self.xmlFile, baseName .. ".samplesAnimation#minSamples") or 0
    spec.samplesAnimation.maxSamples = getXMLInt(self.xmlFile, baseName .. ".samplesAnimation#maxSamples") or 0

    spec.visualSampleUpdateTime = getXMLFloat(self.xmlFile, baseName .. ".visualSamples#updateTime") or 0.5
    spec.visualSampleUpdated = true
    spec.visualSamples = {}
    local i = 0
    while true do
        local visualSampleKey = string.format("%s.visualSamples.visualSample(%d)", baseName, i)
        if not hasXMLProperty(self.xmlFile, visualSampleKey) then
            break
        end

        local node = I3DUtil.indexToObject(self.components, getXMLString(self.xmlFile, visualSampleKey .. "#node"), self.i3dMappings)
        if node ~= nil then
            setVisibility(node, false)
            table.insert(spec.visualSamples, node)
        end

        i = i + 1
    end

    spec.texts = {}
    spec.texts.takeSample = g_i18n:getText("action_takeSoilSample", self.customEnvironment)
    spec.texts.sendSoilSamples = g_i18n:getText("action_sendSoilSamples", self.customEnvironment)
    spec.texts.numSamplesTaken = g_i18n:getText("info_numSamplesTaken", self.customEnvironment)
    spec.texts.infoSamplesSend = g_i18n:getText("info_samplesSend", self.customEnvironment)

    if g_precisionFarming ~= nil then
        spec.soilMap = g_precisionFarming.soilMap
        spec.farmlandStatistics = g_precisionFarming.farmlandStatistics
    end

    -- set speed limit here instead of xml, so it won't be displayed in the shop
    self.speedLimit = 0
end

---
-- @includeCode
function SoilSampler:onPostLoad(savegame)
    local spec = self.spec_soilSampler
    if savegame ~= nil and not savegame.resetVehicles then
        local specName = SoilSampler.MOD_NAME .. ".soilSampler"

        local numSamples = getXMLInt(savegame.xmlFile, savegame.key .."." .. specName .. "#numCollectedSamples") or spec.numCollectedSamples
        self:setNumCollectedSoilSamples(numSamples, true)
    end
end

---
-- @includeCode
function SoilSampler:saveToXMLFile(xmlFile, key, usedModNames)
    local spec = self.spec_soilSampler
    setXMLInt(xmlFile, key.."#numCollectedSamples", spec.numCollectedSamples)
end

---Called on client side on join
-- @param integer streamId streamId
-- @param integer connection connection
-- @includeCode
function SoilSampler:onReadStream(streamId, connection)
    local spec = self.spec_soilSampler

    local numSamples = streamReadUIntN(streamId, SoilSampler.SEND_NUM_BITS) or spec.numCollectedSamples
    self:setNumCollectedSoilSamples(numSamples, true)
end

---Called on server side on join
-- @param integer streamId streamId
-- @param integer connection connection
-- @includeCode
function SoilSampler:onWriteStream(streamId, connection)
    local spec = self.spec_soilSampler
    streamWriteUIntN(streamId, spec.numCollectedSamples, SoilSampler.SEND_NUM_BITS)
end

---Called on update
-- @param float dt time since last call in ms
-- @param boolean isActiveForInput true if vehicle is active for input
-- @param boolean isSelected true if vehicle is selected
-- @includeCode
function SoilSampler:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
    local spec = self.spec_soilSampler
    if spec.isSampling then
        if self.isClient then
            if not spec.visualSampleUpdated then
                if self:getAnimationTime(spec.animationName) >= spec.visualSampleUpdateTime then
                    local sampleIndex = self:getNormalizedSampleIndex()

                    for i=1, #spec.visualSamples do
                        setVisibility(spec.visualSamples[i], i <= sampleIndex)
                    end
                    spec.visualSampleUpdated = false
                end
            end
        end

        if not self:getIsAnimationPlaying(spec.animationName) then
            spec.isSampling = false
            self:setAnimationTime(spec.animationName, 0, false)

            if spec.soilMap:getMinimapAdditionalElementLinkNode() == spec.samplingNode then
                spec.soilMap:setMinimapSamplingState(false)
            end

            self:processSoilSampling()
        end
    end

    if isActiveForInputIgnoreSelection and spec.numCollectedSamples > 0 then
        g_currentMission:addExtraPrintText(string.format(spec.texts.numSamplesTaken, spec.numCollectedSamples))
    end
end

---
-- @includeCode
function SoilSampler:onFoldStateChanged(direction, moveToMiddle)
    local spec = self.spec_soilSampler
    if self:getIsActiveForInput(true) then
        spec.soilMap:setRequireMinimapDisplay(direction == 1, self)
    end
end

---
-- @includeCode
function SoilSampler:onLeaveRootVehicle()
    local spec = self.spec_soilSampler
    if spec.soilMap:getMinimapAdditionalElementLinkNode() == spec.samplingNode then
        spec.soilMap:setRequireMinimapDisplay(false, self)
        spec.soilMap:setMinimapAdditionalElementLinkNode(nil)
        spec.soilMap:setMinimapSamplingState(false)
    end
end

---
-- @includeCode
function SoilSampler:onPreDetach()
    local spec = self.spec_soilSampler
    if spec.soilMap:getMinimapAdditionalElementLinkNode() == spec.samplingNode then
        spec.soilMap:setRequireMinimapDisplay(false, self)
        spec.soilMap:setMinimapAdditionalElementLinkNode(nil)
        spec.soilMap:setMinimapSamplingState(false)
    end
end

---
-- @includeCode
function SoilSampler:startSoilSampling(noEventSend)
    local spec = self.spec_soilSampler
    spec.isSampling = true

    if self.isServer then
        if not self:getIsLowered(false) then
            local attacherVehicle = self:getAttacherVehicle()
            if attacherVehicle ~= nil then
                local jointDesc = attacherVehicle:getAttacherJointDescFromObject(self)
                if jointDesc.allowsLowering then
                    local jointDescIndex = attacherVehicle:getAttacherJointIndexFromObject(self)
                    attacherVehicle:setJointMoveDown(jointDescIndex, true, false)
                end
            end
        end

        local farmlandStatistics, isOnField, farmlandId = self:getPAStatisticInfo()
        if farmlandStatistics ~= nil and isOnField and farmlandId ~= nil then
            spec.farmlandStatistics:updateStatistic(farmlandId, "numSoilSamples", 1)
        end
    end

    self:playAnimation(spec.animationName, spec.animationSpeed, self:getAnimationTime(spec.animationName), true)

    self:setNumCollectedSoilSamples(spec.numCollectedSamples + 1, false)

    if self:getIsActiveForInput(true) then
        spec.soilMap:setMinimapSamplingState(true)
    end

    if self.isClient then
        if (spec.numCollectedSamples - 1) % #spec.visualSamples == 0 then
            for i=1, #spec.visualSamples do
                setVisibility(spec.visualSamples[i], false)
            end

            self:playAnimation(spec.samplesAnimation.name, -spec.samplesAnimation.speed, self:getAnimationTime(spec.samplesAnimation.name), true)
        else
            local sampleIndex = self:getNormalizedSampleIndex()
            local stopTime = (sampleIndex - spec.samplesAnimation.minSamples) / (spec.samplesAnimation.maxSamples - spec.samplesAnimation.minSamples)
            if self:getAnimationTime(spec.samplesAnimation.name) < stopTime then
                self:setAnimationStopTime(spec.samplesAnimation.name, stopTime)
                self:playAnimation(spec.samplesAnimation.name, spec.samplesAnimation.speed, self:getAnimationTime(spec.samplesAnimation.name), true)
            end
        end

        spec.visualSampleUpdated = false
    end

    SoilSampler.updateActionEventState(self)

    SoilSamplerStartEvent.sendEvent(self, noEventSend)
end

---
-- @includeCode
function SoilSampler:setNumCollectedSoilSamples(num, updateVisuals)
    local spec = self.spec_soilSampler
    spec.numCollectedSamples = num or (spec.numCollectedSamples + 1)

    if updateVisuals then
        local sampleIndex = self:getNormalizedSampleIndex()
        for j=1, #spec.visualSamples do
            setVisibility(spec.visualSamples[j], j <= sampleIndex)
        end

        local stopTime = (sampleIndex - spec.samplesAnimation.minSamples) / (spec.samplesAnimation.maxSamples - spec.samplesAnimation.minSamples)
        self:setAnimationTime(spec.samplesAnimation.name, stopTime, true)
    end
end

---
-- @includeCode
function SoilSampler:getNormalizedSampleIndex()
    local spec = self.spec_soilSampler
    local sampleIndex = spec.numCollectedSamples % #spec.visualSamples
    if spec.numCollectedSamples == 0 then
        return 0
    else
        if sampleIndex == 0 then
            return #spec.visualSamples
        end
    end

    return sampleIndex
end

---
-- @includeCode
function SoilSampler:getCanStartSoilSampling()
    local spec = self.spec_soilSampler
    local x, _, z = getWorldTranslation(spec.samplingNode)
    local farmlandId = g_farmlandManager:getFarmlandIdAtWorldPosition(x, z)
    if farmlandId == nil then -- no valid farmland, or not buyable
        return false, g_i18n:getText("warning_youDontHaveAccessToThisLand")
    end

    local landOwner = g_farmlandManager:getFarmlandOwner(farmlandId)
    local accessible = landOwner ~= 0 and g_currentMission.accessHandler:canFarmAccessOtherId(self:getActiveFarm(), landOwner)
    if not accessible then
        return false, g_i18n:getText("warning_youDontHaveAccessToThisLand")
    end

    if self.getIsMotorStarted ~= nil then
        if not self:getIsMotorStarted() then
            return false, g_i18n:getText("warning_motorNotStarted")
        end
    else
        local rootAttacherVehicle = self:getRootVehicle()
        if rootAttacherVehicle ~= self then
            if rootAttacherVehicle.getIsMotorStarted ~= nil then
                if not rootAttacherVehicle:getIsMotorStarted() then
                    return false, g_i18n:getText("warning_motorNotStarted")
                end
            end
        end
    end

    if self.getFoldAnimTime ~= nil then
        local time = self:getFoldAnimTime()
        if time > spec.foldMaxLimit or time < spec.foldMinLimit then
            return false, self.spec_foldable.unfoldWarning
        end
    end

    return not self.spec_soilSampler.isSampling
end

---
-- @includeCode
function SoilSampler:processSoilSampling()
    if self.isServer then
        local spec = self.spec_soilSampler
        if spec.soilMap ~= nil then
            local length = spec.samplingRadius
            local octagon = SoilSampler.SAMPLING_OCTAGON
            for i=1, #octagon do
                local position = octagon[i]
                local xs, _, zs = localToWorld(spec.samplingNode, position[1] * length, position[2] * length, position[3] * length)
                local xw, _, zw = localToWorld(spec.samplingNode, position[4] * length, position[5] * length, position[6] * length)
                local xh, _, zh = localToWorld(spec.samplingNode, position[7] * length, position[8] * length, position[9] * length)

                spec.soilMap:analyseArea(xs, zs, xw, zw, xh, zh, nil, self:getOwnerFarmId())
            end
        end
    end
end

---
-- @includeCode
function SoilSampler:sendTakenSoilSamples(noEventSend)
    local spec = self.spec_soilSampler

    if self.isServer then
        if spec.soilMap ~= nil then
            spec.soilMap:analyseSoilSamples(self:getActiveFarm(), spec.numCollectedSamples)
        end
    end

    if self.isClient then
        for i=1, #spec.visualSamples do
            setVisibility(spec.visualSamples[i], false)
        end

        self:playAnimation(spec.samplesAnimation.name, -spec.samplesAnimation.speed, self:getAnimationTime(spec.samplesAnimation.name), true)
    end

    spec.numCollectedSamples = 0
    SoilSampler.updateActionEventState(self)

    SoilSamplerSendEvent.sendEvent(self, noEventSend)
end

---
-- @includeCode
function SoilSampler:onRegisterActionEvents(isActiveForInput, isActiveForInputIgnoreSelection)
    if self.isClient then
        local spec = self.spec_soilSampler
        self:clearActionEventsTable(spec.actionEvents)
        if isActiveForInputIgnoreSelection then
            local _, actionEventId = self:addActionEvent(spec.actionEvents, spec.inputActionTake, self, SoilSampler.actionEventStartSample, false, true, false, true, nil)
            g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH)
            g_inputBinding:setActionEventText(actionEventId, spec.texts.takeSample)

            _, actionEventId = self:addActionEvent(spec.actionEvents, spec.inputActionSend, self, SoilSampler.actionEventSendSamples, false, true, false, true, nil)
            g_inputBinding:setActionEventTextPriority(actionEventId, GS_PRIO_HIGH)
            g_inputBinding:setActionEventText(actionEventId, spec.texts.sendSoilSamples)

            SoilSampler.updateActionEventState(self)

            spec.soilMap:setRequireMinimapDisplay(self:getFoldAnimTime() > 0, self)
            spec.soilMap:setMinimapAdditionalElementLinkNode(spec.samplingNode)
            spec.soilMap:setMinimapAdditionalElementRealSize(spec.samplingRadius * 2, spec.samplingRadius * 2)
        end
    end
end

---
-- @includeCode
function SoilSampler.updateActionEventState(self)
    local spec = self.spec_soilSampler
    local actionEventSend = spec.actionEvents[spec.inputActionSend]
    if actionEventSend ~= nil then
        g_inputBinding:setActionEventActive(actionEventSend.actionEventId, spec.numCollectedSamples > 0)
    end
end

---
-- @includeCode
function SoilSampler.actionEventStartSample(self, actionName, inputValue, callbackState, isAnalog)
    local canStart, warning = self:getCanStartSoilSampling()
    if canStart then
        self:startSoilSampling()
    elseif warning ~= nil then
        g_currentMission:showBlinkingWarning(warning, 2000)
    end
end

---
-- @includeCode
function SoilSampler.actionEventSendSamples(self, actionName, inputValue, callbackState, isAnalog)
    local spec = self.spec_soilSampler
    if spec.numCollectedSamples > 0 and not self.spec_soilSampler.isSampling then
        g_gui:showInfoDialog({text=spec.texts.infoSamplesSend, callback=self.sendTakenSoilSamples, target=self})
    end
end

---Returns if speed limit should be checked
-- @return boolean checkSpeedlimit check speed limit
-- @includeCode
function SoilSampler:doCheckSpeedLimit(superFunc)
    return superFunc(self) or self.spec_soilSampler.isSampling
end

---
-- @includeCode
function SoilSampler:getIsFoldAllowed(superFunc, direction, onAiTurnOn)
    return not self.spec_soilSampler.isSampling and superFunc(self, direction, onAiTurnOn)
end
