--[[

Game:	 Farming Simulator 22
Title:	 DynamicRotator
Author:  ThundR
Created: 03.14.2022
Updated: 03.14.2022

Copyright ©2022, ThundR (Todd Hundersmarck)

--]]

-- <<[Declarations]>> -----------------------------------------------------------------------------

---@class THDynamicRotator
THDynamicRotator = {}
local THDynamicRotator_mt = Class(THDynamicRotator)

-- <<[Local Functions]>> --------------------------------------------------------------------------

--[[ runScript --------------------------------]]
-- * runs script startup code
local function runScript()
	g_thDynamicRotator = THDynamicRotator:new()

    -- hook PlaceableHusbandryStraw function(s)
    g_thDynamicRotator:hookFunction(Rotator, "onCreate", THDynamicRotator.Rotator)
    g_thDynamicRotator:hookFunction(Rotator, "update",   THDynamicRotator.Rotator)
end

-- <<[Initialization]>> ---------------------------------------------------------------------------

--[[ new --------------------------------------]]
-- * creates and returns a new environment
    ---@param mt table @custom metatable [optional]
    ---@return THDynamicRotator
function THDynamicRotator:new(mt)
    mt = Utils.getNoNil(mt, THDynamicRotator_mt)

	---@type THDynamicRotator
	local newEnv = setmetatable({}, mt)

    if newEnv ~= nil then
        local modInfo = g_modManager:getModByName(g_currentModName)

        newEnv.modList = g_modManager:getActiveMods()
        newEnv.modInfo = modInfo

        newEnv.modName      = modInfo.modName
        newEnv.modTitle     = modInfo.title
        newEnv.modPath      = modInfo.modDir
        newEnv.xmlFilename  = modInfo.modFile
        newEnv.settingsPath = g_modSettingsDirectory
    end

    return newEnv
end

-- <<[Message/Error Handling Utilities]>> ---------------------------------------------------------

--[[ displayMsg -------------------------------]]
-- * displays a custom message in the log
-- * allows for formated strings
	---@param text string @text to display
	---@param vararg any  @string.format args [optional]
function THDynamicRotator:displayMsg(text, ...)
    if type(text) ~= "string" then
		text = string.format("ERROR: Invalid displayMsg text, %q", text)
	end

    local msgString = string.format(">> %s: ", self.modName)..string.format(text, ...)
	print(msgString)
end

--[[ errorMsg ---------------------------------]]
-- * displays an error message in the log
-- * prints the call stack
	---@param text string @text to display
	---@param vararg any  @string.format() args [optional]
function THDynamicRotator:errorMsg(text, ...)
    if type(text) == "string" then
		text = "ERROR: "..text
	end

	self:displayMsg(text, ...)
	printCallstack()
end

--[[ msgOnFalse -------------------------------]]
-- * prints error message on false or nil
-- * returns true if message is displayed
    ---@param expression any @expression to check
    ---@param text string @text to display on error
    ---@param vararg any @string.format() args [optional]
    ---@return boolean
function THDynamicRotator:msgOnFalse(expression, text, ...)
    if not expression then
        self:errorMsg(text, ...)
        return true
    end

    return false
end

--[[ argIsInvalid -----------------------------]]
-- * displays a message and prints the stack on failure
	---@param expression any @expression to check
	---@param name string    @name of failed argument
	---@param arg any        @failed argument
	---@return boolean
function THDynamicRotator:argIsInvalid(expression, name, arg)
	if not expression then
		self:errorMsg("Invalid argument %q (%s) in function call", name, arg)
		return true
	end

	return false
end

-- <<[General Utilities]>> ------------------------------------------------------------------------

--[[ pack --------------------------------------]]
-- * packs a list of values into a table array
-- * table length is stored in embedded n variable
	---@param vararg any @arguments to pack
	---@return table
function THDynamicRotator:pack(...)
    return {n = select("#", ...), ...}
end

--[[ unpack -----------------------------------]]
-- * wrapper function for lua unpack()
-- * companion to THDynamicRotator.pack()
	---@param tbl table    @table to unpack
	---@param index number @start index [1]
	---@return any
function THDynamicRotator:unpack(tbl, index)
    index = Utils.getNoNil(index, 1)

    if self:argIsInvalid(type(tbl) == "table", "tbl", tbl)
    or self:argIsInvalid(type(index) == "number" and index > 0, "index", index)
    then
        return
    end

    local numEntries = Utils.getNoNil(tbl.n, #tbl)

    if index <= numEntries then
        return unpack(tbl, index, numEntries)
    end
end

--[[ toNumber ---------------------------------]]
-- * wrapper function for tonumber()
-- * nil values pass through
    ---@param value any @value to convert
    ---@return number
function THDynamicRotator:toNumber(value)
    local valType = type(value)

    if value == nil or valType == "number" then
        return value
    else
        return tonumber(value)
    end
end

--[[ toBoolean --------------------------------]]
-- * converts a value to boolean
-- * returns nil if invalid value is passed
    ---@param value any @value to convert
    ---@return boolean
function THDynamicRotator:toBoolean(value)
    local valType = type(value)

    if value == nil or valType == "boolean" then
        return value
    elseif valType == "string" then
        local valString = string.upper(value)
        if valString == "TRUE" then
            return true
        elseif valString == "FALSE" then
            return false
        end
    end
end

--[[ getIsType --------------------------------]]
-- * returns true if value is one of the listed types
    ---@param value any @value to check
    ---@param typeList string @type(s) to check
    ---@return boolean
function THDynamicRotator:getIsType(value, typeList)
    local valType = type(value)

    if valType == typeList then -- exact match (fastest)
        return true
    end

    if string.find(typeList, "^"..valType.."|") ~= nil
    or string.find(typeList, "|"..valType.."|") ~= nil
    or string.find(typeList, "|"..valType.."$") ~= nil
    then
        return true
    end

    return false
end

--[[ getGlobalEnv -----------------------------]]
-- * returns global environment table
    ---@return table
function THDynamicRotator:getGlobalEnv()
    local globalEnv = getmetatable(_G)

    if type(globalEnv) == "table" then
        return globalEnv.__index
    end
end

-- <<[Function Utilities]>> -----------------------------------------------------------------------

--[[ hookFunction -----------------------------]]
-- * special function injection utility
    ---@param srcClass table @source class
    ---@param srcFuncName string @source function name
    ---@param tgtClass table @target class
    ---@param tgtFuncName string @target function name [optional]
    ---@param useSelf boolean @add self to as first return parameter [true]
    ---@return boolean
function THDynamicRotator:hookFunction(srcClass, srcFuncName, tgtClass, tgtFuncName, useSelf)
    useSelf = Utils.getNoNil(useSelf, true)

    if self:argIsInvalid(type(srcClass) == "table", "srcClass", srcClass)
    or self:argIsInvalid(type(srcFuncName) == "string", "srcFuncName", srcFuncName)
    or self:argIsInvalid(type(tgtClass) == "table", "tgtClass", tgtClass)
    or self:argIsInvalid(type(useSelf) == "boolean", "useSelf", useSelf)
    then
        return false
    end

    tgtFuncName = Utils.getNoNil(tgtFuncName, "hook_"..srcFuncName)

    if self:argIsInvalid(type(tgtFuncName) == "string", "tgtFuncName", tgtFuncName) then
        return false
    end

    local oldFunc = srcClass[srcFuncName]
    local newFunc = tgtClass[tgtFuncName]

    if self:msgOnFalse(type(oldFunc) == "function", "Cannot find original function %q", srcFuncName)
    or self:msgOnFalse(type(newFunc) == "function", "Cannot find target function %q", tgtFuncName)
    then
        return false
    end

    srcClass[srcFuncName] = function(p1, ...)
        if useSelf then
            return newFunc(p1, oldFunc, ...)
        else
            return newFunc(oldFunc, p1, ...)
        end
    end

    return true
end

-- <<{THDynamicRotator.Rotator}>> #################################################################

-- <<[Declarations]>> -----------------------------------------------------------------------------

THDynamicRotator.Rotator = {}

-- <<[Function Hooks]>> ---------------------------------------------------------------------------

--[[ hook_onCreate ----------------------------]]
-- * called from Rotator.onCreate()
    ---@param superFunc function @original function
function THDynamicRotator.Rotator:hook_onCreate(superFunc, id, ...)
    if entityExists(id) then
        return superFunc(self, id, ...)
    end
end

--[[ hook_update ------------------------------]]
-- * called from Rotator.update()
    ---@param superFunc function @original function
function THDynamicRotator.Rotator:hook_update(superFunc, dt, ...)
    if entityExists(self.me) then
        return superFunc(self, dt, ...)
    elseif self.me ~= nil then
        g_currentMission:removeUpdateable(self.me)
    end
end

-- <<{Script Start}>> #############################################################################

runScript()