--[[
Interface: 1.7.1.0 b10490

Copyright (C) GtX (Andy), 2020

Author: GtX | Andy
Date: 08.04.2020
Version: 1.0.0.0

History:
V 1.0.1.0 @ 20.11.2020 - Fixed bug where incorrect breeding information was shown in MP.

Contact:
https://forum.giants-software.com
https://github.com/GtX-Andy

Important:
Free for use in other mods - no permission needed.
No changes are to be made to this script without permission from GtX | Andy

Frei verwendbar - keine erlaubnis nötig
An diesem Skript dürfen ohne Genehmigung von GtX | Andy keine Änderungen vorgenommen werden
]]


HorseBreeding = {}

local HorseBreeding_mt = Class(HorseBreeding, AnimalHusbandry)
InitObjectClass(HorseBreeding, "HorseBreeding")

function HorseBreeding:new(isServer, isClient, customMt)
    local self = Placeable:new(isServer, isClient, customMt or HorseBreeding_mt)

    self.saveId = ""

    self.modulesById = {}
    self.modulesByName = {}

    self.useMultiRootNode = true
    self.hasStatistics = false

    self.isHorseBreeding = true
    self.isPlayerInRange = false

    self.interactionTrigger = nil
    self.nextAnimalText = "Next animal in:  %s"

    self.updateMinutes = 0
    self.globalProductionFactor = 0

    self.husbandryDirtyFlag = self:getNextDirtyFlag()

    registerObjectClassName(self, "HorseBreeding")

    return self
end

function HorseBreeding:loadModules(xmlFilename)
    if not HorseBreeding:superClass().loadModules(self, xmlFilename) then
        return false
    end

    local animalsModule = self.modulesByName["animals"]

    if animalsModule ~= nil and animalsModule.animalType == "HORSE" then
        -- Limited to parent horses and the foal once born so this mod is not a cheat given the value of horses.

        local xmlFile = loadXMLFile("AnimalHusbandryXML", xmlFilename)

        animalsModule.maxNumAnimals = 3
        animalsModule.carryingCapacity = 3
        animalsModule.maxNumVisualAnimals = 3

        animalsModule.isHorseBreeding = true
        animalsModule.horseBreeding_bredHorsesCount = 0

        animalsModule.updateBreeding = Utils.overwrittenFunction(animalsModule.updateBreeding, self.inj_husbandryModuleAnimal_updateBreeding)
        animalsModule.updateAnimalParameters = Utils.prependedFunction(animalsModule.updateAnimalParameters, self.inj_husbandryModuleAnimal_updateAnimalParameters)
        animalsModule.loadFromXMLFile = Utils.appendedFunction(animalsModule.loadFromXMLFile, self.inj_husbandryModuleAnimal_loadFromXMLFile)
        animalsModule.saveToXMLFile = Utils.appendedFunction(animalsModule.saveToXMLFile, self.inj_husbandryModuleAnimal_saveToXMLFile)

        animalsModule.readStream = Utils.appendedFunction(animalsModule.readStream, self.inj_husbandryModuleAnimal_readStream)
        animalsModule.writeStream = Utils.appendedFunction(animalsModule.writeStream, self.inj_husbandryModuleAnimal_writeStream)
        animalsModule.readUpdateStream = Utils.appendedFunction(animalsModule.readUpdateStream, self.inj_husbandryModuleAnimal_readUpdateStream)
        animalsModule.writeUpdateStream = Utils.appendedFunction(animalsModule.writeUpdateStream, self.inj_husbandryModuleAnimal_writeUpdateStream)

        if animalsModule.owner.isServer and not g_currentMission.gameStarted then
            animalsModule.onMissionStarted = Utils.appendedFunction(animalsModule.onMissionStarted, self.inj_husbandryModuleAnimal_onMissionStarted)
        end

        -- Make the fill planes look more realistic by allowing one for each fill type.
        local foodModule = self.modulesByName["food"]

        if foodModule ~= nil and foodModule.fillLevels[FillType.OAT] ~= nil and foodModule.fillLevels[FillType.DRYGRASS_WINDROW] ~= nil then
            local i = 0
            while true do
                local moduleKey = string.format("placeable.husbandry.modules.module(%d)", i)
                if not hasXMLProperty(xmlFile, moduleKey) then
                    break
                end

                if getXMLString(xmlFile, moduleKey .. "#name") == "food" then
                    local j = 0
                    while true do
                        local fillPlaneKey = string.format("%s.config.fillPlanes.fillPlane(%d)", moduleKey, j)
                        if not hasXMLProperty(xmlFile, fillPlaneKey) then
                            break
                        end

                        local fillTypeIndex = g_fillTypeManager:getFillTypeIndexByName(getXMLString(xmlFile, fillPlaneKey .. "#fillType"))

                        if fillTypeIndex ~= nil then
                            if foodModule.horseBreeding_fillPlanes == nil then
                                foodModule.horseBreeding_fillPlanes = {}
                                foodModule.isHorseBreeding = true

                                foodModule.updateFillPlane = Utils.overwrittenFunction(foodModule.updateFillPlane, self.inj_husbandryModuleFood_updateFillPlane)
                                foodModule.delete = Utils.overwrittenFunction(foodModule.delete, self.inj_husbandryModuleFood_delete)
                            end

                            if foodModule.horseBreeding_fillPlanes[fillTypeIndex] == nil then
                                local fillPlane = FillPlane:new()
                                fillPlane:load(self.nodeId, xmlFile, fillPlaneKey)
                                fillPlane.colorChange = false -- Not needed for individual fill planes.

                                foodModule.horseBreeding_fillPlanes[fillTypeIndex] = fillPlane
                            end
                        end

                        j = j + 1
                    end

                    if foodModule.horseBreeding_fillPlanes ~= nil and foodModule.fillPlane ~= nil then
                        foodModule.fillPlane:delete()
                        foodModule.fillPlane = nil
                    end

                    break
                end

                i = i + 1
            end
        end

        local interactionTrigger = I3DUtil.indexToObject(self.nodeId, getXMLString(xmlFile, "placeable.husbandry.interactionTrigger#node"))

        if interactionTrigger ~= nil then
            if bitAND(Player.COLLISIONMASK_TRIGGER, getCollisionMask(interactionTrigger)) ~= 0 then
                addTrigger(interactionTrigger, "interactionTriggerCallback", self)
                self.interactionTrigger = interactionTrigger

                self.nextAnimalText = g_i18n:getText("statistic_timeTillNextAnimal") .. ":  %s"
                self.isPlayerInRange = false
            else
                g_logManager:xmlWarning(xmlFilename, "Invalid collision mask for interaction trigger 'placeable.husbandry.interactionTrigger#node'. Bit 20 needs to be set!")
            end
        end

        delete(xmlFile)

        return true
    end

    return false
end

function HorseBreeding:delete()
    if self.interactionTrigger ~= nil then
        removeTrigger(self.interactionTrigger)
        self.interactionTrigger = nil
    end

    HorseBreeding:superClass().delete(self)
end

function HorseBreeding:update(dt)
    HorseBreeding:superClass().update(self, dt)

    if self.interactionTrigger ~= nil and g_currentMission.controlPlayer then
        if self.isPlayerInRange and self:getOwnerFarmId() == g_currentMission:getFarmId() then
            local animalsModule = self.modulesByName["animals"]

            if animalsModule ~= nil then
                if g_gui.currentGui == nil then
                    local minutesUntilNextAnimal

                    if animalsModule.horseBreeding_foalFillType ~= nil then
                        minutesUntilNextAnimal = animalsModule:getMinutesUntilNextAnimal(animalsModule.horseBreeding_foalFillType) or 0

                        if minutesUntilNextAnimal <= 0 then
                            minutesUntilNextAnimal = nil
                        end
                    end

                    g_currentMission:addExtraPrintText(string.format(self.nextAnimalText, g_i18n:formatMinutes(minutesUntilNextAnimal)))
                end

                self:raiseActive()
            end
        end
    end
end

function HorseBreeding:interactionTriggerCallback(triggerId, otherActorId, onEnter, onLeave, onStay, otherShapeId)
    if (onEnter or onLeave) and g_currentMission.player ~= nil and otherActorId == g_currentMission.player.rootNode then
        if onEnter and self:getOwnerFarmId() == g_currentMission:getFarmId() then
            self.isPlayerInRange = true
        else
            self.isPlayerInRange = false
        end

        self:raiseActive()
    end
end

function HorseBreeding:loadHotspotFromXML(xmlFile, key)
    local hotspot = HorseBreeding:superClass().loadHotspotFromXML(self, xmlFile, key)

    if hotspot ~= nil then
        -- Correct the 'fullName' bug in base code using customEnvironment.
        local fullViewName = g_i18n:convertText(Utils.getNoNil(getXMLString(xmlFile, key .. "#fullName"), ""), self.customEnvironment)

        if fullViewName ~= "" then
            hotspot.fullViewName = fullViewName
        end
    end

    return hotspot
end

function HorseBreeding:canBuy()
    if g_horseBreedingLoader == nil then
        return false, g_i18n:getText("warning_placeable_error_cannotBeBought")
    end

    if g_seasons ~= nil then
        local text = "Horse breeding is currently no compatible with Seasons!"
        if g_i18n:hasText("warning_seasonsCompatibility", self.customEnvironment) then
            text = g_i18n:getText("warning_seasonsCompatibility", self.customEnvironment)
        end

        return false, text
    end

    return HorseBreeding:superClass().canBuy(self)
end

function HorseBreeding:getReproductionTimePerDay(fillTypeIndex)
    local animalsModule = self:getModuleByName("animals")

    if animalsModule ~= nil then
        fillTypeIndex = animalsModule.horseBreeding_foalFillType or fillTypeIndex
        return animalsModule:getReproductionTimePerDay(fillTypeIndex)
    end

    return 0
end

function HorseBreeding:getMinutesUntilNextAnimal(fillTypeIndex)
    local animalsModule = self:getModuleByName("animals")

    if animalsModule ~= nil then
        fillTypeIndex = animalsModule.horseBreeding_foalFillType or fillTypeIndex
        return animalsModule:getMinutesUntilNextAnimal(fillTypeIndex)
    end

    return nil
end

function HorseBreeding:setModuleParameters(moduleName, capacity, usagePerDay)
    if g_seasons == nil then
        if moduleName == "straw" then
            -- Require more bedding straw to be used as they are breeding with it.
            usagePerDay = math.max(usagePerDay * 2.5, 200)
            capacity = math.max(usagePerDay * HusbandryModuleAnimal.TROUGH_CAPACITY, 2000)
        elseif moduleName == "water" then
            -- Water should be higher to simulate cleaning of the trough as you would do daily.
            usagePerDay = math.max(usagePerDay * 2.6, 130)
            capacity = math.max(usagePerDay * HusbandryModuleAnimal.TROUGH_CAPACITY, 1300)
        end
    end

    HorseBreeding:superClass().setModuleParameters(self, moduleName, capacity, usagePerDay)
end

function HorseBreeding:getMaxNumAnimals()
    if g_seasons == nil then
        return 2 -- This only seems to be used by delivery strategies and there is no need to deliver more than 2 at a time.
    end

    return HorseBreeding:superClass().getMaxNumAnimals(self)
end

function HorseBreeding.inj_husbandryModuleAnimal_updateBreeding(module, superFunc, dayToInterval)
    local animalHusbandry = module.owner

    if animalHusbandry ~= nil and animalHusbandry.isServer and g_seasons == nil then
        if #module.animals == 2 then

            local horse1 = module.animals[1]
            local horse2 = module.animals[2]

            if module.horseBreeding_birthRateScale == nil then
                -- Set the 'birthRateScale' float value from 0.7 to 1.0 so that each birth is a different length.
                module.horseBreeding_birthRateScale = (math.random() + math.random(7, 9)) * 0.1
            end

            if module.horseBreeding_foalFillType == nil then
                local subType

                -- Choose if the foal is the same as the Mare or the Stallion.
                if math.random() < 0.5 then
                    subType = horse1:getSubType()
                else
                    subType = horse2:getSubType()
                end

                if subType == nil or subType.fillType == nil then
                    return
                end

                module.horseBreeding_foalFillType = subType.fillType
            end

            local foalFillType = module.horseBreeding_foalFillType

            module.reproductionRatesPerDay[foalFillType] = animalHusbandry:getGlobalProductionFactor() * 0.1946 * module.horseBreeding_birthRateScale
            module.newAnimalPercentages[foalFillType] = module.newAnimalPercentages[foalFillType] + (module.reproductionRatesPerDay[foalFillType] * dayToInterval)

            if module.newAnimalPercentages[foalFillType] > 1 then
                module.newAnimalPercentages[foalFillType] = 0
                module.reproductionRatesPerDay[foalFillType] = 0

                -- Create a new animal using the random 'fillTypeIndex' chosen.
                local foal = Animal.createFromFillType(animalHusbandry.isServer, animalHusbandry.isClient, animalHusbandry, foalFillType)
                foal:register()

                -- Set the fitness using a 30% range up to half the combined parents fitness or a minimum of 5%. Using range in the hundreds for a broader random result.
                local maxFitness = MathUtil.clamp(horse1.fitnessScale + horse2.fitnessScale, 0.1, 2) * 0.5
                local minFitness = MathUtil.round(math.max(maxFitness - 0.3, 0.05) * 100, 0)
                foal:setFitnessScale(math.random(minFitness, MathUtil.round(maxFitness * 100, 0)) * 0.01, true)

                -- Set the health to the parents if they are not looked after and below 90%.
                local currentHealth = MathUtil.clamp(horse1.healthScale + horse2.healthScale, 1, 2) * 0.5
                if currentHealth < 0.9 then
                    foal:setHealthScale(currentHealth + ((1 - currentHealth) * math.random()), true)
                end

                -- Add foal to stable. Just adding a standard horse at this stage until foal 3D model is made.
                if module:addSingleAnimal(foal) then
                    -- Individual breeding stat for this stable only.
                    module.horseBreeding_bredHorsesCount = module.horseBreeding_bredHorsesCount + 1

                    -- Try and update the stats unless they are removed in a Giants update as they are not used.
                    local farmStats = g_currentMission:farmStats(animalHusbandry:getOwnerFarmId())
                    if farmStats.statistics["breedHorsesCount"] ~= nil then
                        farmStats:updateStats("breedHorsesCount", 1)
                    end
                end

                module.horseBreeding_birthRateScale = nil
                module.horseBreeding_foalFillType = nil
            end
        else
            if module.horseBreeding_foalFillType ~= nil then
                module.reproductionRatesPerDay[module.horseBreeding_foalFillType] = 0
                module.newAnimalPercentages[module.horseBreeding_foalFillType] = 0

                module.horseBreeding_birthRateScale = nil
                module.horseBreeding_foalFillType = nil
            end
        end
    end
end

function HorseBreeding.inj_husbandryModuleAnimal_updateAnimalParameters(module)
    if module.owner.isServer then
        local foalFillType = module.horseBreeding_foalFillType

        if foalFillType ~= nil then
            if module:getNumOfAnimals() ~= 2 then
                module.reproductionRatesPerDay[foalFillType] = 0
                module.newAnimalPercentages[foalFillType] = 0

                module.horseBreeding_birthRateScale = nil
                module.horseBreeding_foalFillType = nil
            end
        end
    end
end

function HorseBreeding.inj_husbandryModuleAnimal_readStream(module, streamId, connection)
    if streamReadBool(streamId) then
        module.horseBreeding_foalFillType = streamReadUIntN(streamId, FillTypeManager.SEND_NUM_BITS)
    end
end

function HorseBreeding.inj_husbandryModuleAnimal_writeStream(module, streamId, connection)
    if streamWriteBool(streamId, module.horseBreeding_foalFillType ~= nil) then
        streamWriteUIntN(streamId, module.horseBreeding_foalFillType, FillTypeManager.SEND_NUM_BITS)
    end
end

function HorseBreeding.inj_husbandryModuleAnimal_readUpdateStream(module, streamId, timestamp, connection)
    if streamReadBool(streamId) then
        module.horseBreeding_foalFillType = streamReadUIntN(streamId, FillTypeManager.SEND_NUM_BITS)
    end
end

function HorseBreeding.inj_husbandryModuleAnimal_writeUpdateStream(module, streamId, connection, dirtyMask)
    if streamWriteBool(streamId, module.horseBreeding_foalFillType ~= nil) then
        streamWriteUIntN(streamId, module.horseBreeding_foalFillType, FillTypeManager.SEND_NUM_BITS)
    end
end

function HorseBreeding.inj_husbandryModuleAnimal_loadFromXMLFile(module, xmlFile, key)
    local foalFillTypeName = getXMLString(xmlFile, key .. ".breeding(0)#foalFillType")
    module.horseBreeding_foalFillTypeToSet = g_fillTypeManager:getFillTypeIndexByName(foalFillTypeName)

    module.horseBreeding_birthRateScaleToSet = getXMLFloat(xmlFile, key .. ".breeding(0)#birthRateScale")
    module.horseBreeding_bredHorsesCount = Utils.getNoNil(getXMLInt(xmlFile, key .. ".breeding(0)#bredHorsesCount"), 0)
end

function HorseBreeding.inj_husbandryModuleAnimal_saveToXMLFile(module, xmlFile, key, usedModNames)
    if module.horseBreeding_foalFillType ~= nil then
        setXMLString(xmlFile, key .. ".breeding(0)#foalFillType", g_fillTypeManager:getFillTypeNameByIndex(module.horseBreeding_foalFillType))
    end

    if module.horseBreeding_birthRateScale ~= nil then
        setXMLFloat(xmlFile, key .. ".breeding(0)#birthRateScale", module.horseBreeding_birthRateScale)
    end

    setXMLInt(xmlFile, key .. ".breeding(0)#bredHorsesCount", module.horseBreeding_bredHorsesCount or 0)
end

function HorseBreeding.inj_husbandryModuleAnimal_onMissionStarted(module)
    if g_seasons == nil then
        if module.horseBreeding_foalFillTypeToSet ~= nil and module.horseBreeding_birthRateScaleToSet ~= nil then
            module.horseBreeding_foalFillType = module.horseBreeding_foalFillTypeToSet
            module.horseBreeding_birthRateScale = MathUtil.clamp(module.horseBreeding_birthRateScaleToSet, 0.7, 1.0)

            module.horseBreeding_foalFillTypeToSet = nil
            module.horseBreeding_birthRateScaleToSet = nil

            module:updateBreeding(0)
        end
    end
end

function HorseBreeding.inj_husbandryModuleFood_updateFillPlane(module, superFunc)
    superFunc(module)

    if module.horseBreeding_fillPlanes ~= nil then
        local capacity = module:getCapacity() or 0

        for fillTypeIndex, fillLevel in pairs (module.fillLevels) do
            local fillPlane = module.horseBreeding_fillPlanes[fillTypeIndex]

            if fillPlane ~= nil then
                local progress = MathUtil.clamp(fillLevel / capacity, 0, 1)
                local fillPlaneFactor = 1 - math.max(0, math.abs(math.min(progress - 1, 1)) * 2 - 1) ^ 2

                fillPlane:setState(fillPlaneFactor)
            end
        end
    end
end

function HorseBreeding.inj_husbandryModuleFood_delete(module, superFunc)
    if module.horseBreeding_fillPlanes ~= nil then
        for _, fillPlane in pairs (module.horseBreeding_fillPlanes) do
            fillPlane:delete()
        end

        module.horseBreeding_fillPlanes = nil
    end

    superFunc(module)
end
