-- IndustrialTest
-- Copyright (C) 2024 mrkubax10

-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.

-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.

-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

local S=minetest.get_translator("industrialtest")

local function createItemPowerText(itemstack)
	local meta=itemstack:get_meta()
	local powerCapacity=meta:get_int("industrialtest.powerCapacity")
	local powerAmount=meta:get_int("industrialtest.powerAmount")
	local lowerLimit=math.floor(powerCapacity*0.25)
	local color=(powerAmount>lowerLimit and "#00FFFF" or "#FF0000")
	return minetest.colorize(color,S("@1 / @2 EU",powerAmount,powerCapacity))
end

-- \brief Adds power storage to metadata
-- \param capacity How much EU item/node can store
-- \param flow How much EU can flow in or out item/node per industrialtest.updateDelay
-- \param ioConfig Input/Output configuration in following side order: -X, +X, -Y, +Y, -Z, +Z
--                 a - bidirectional, i - input, o - output.
--                 Instead of string containing IO mode for each side, ioConfig can hold a table for each side, containing
--                 IO mode and flow override
-- \returns nil
function industrialtest.api.addPowerStorage(meta,capacity,flow,ioConfig)
	meta:set_int("industrialtest.powerCapacity",capacity)
	meta:set_int("industrialtest.powerFlow",flow)
	meta:set_int("industrialtest.powerAmount",0)
	meta:set_int("industrialtest.extendedIoConfig",type(ioConfig)~="string" and 1 or 0)
	meta:set_string("industrialtest.ioConfig",type(ioConfig)=="string" and ioConfig or minetest.serialize(ioConfig))
end

-- \brief Checks if metadata contains power storage
-- \param meta MetaDataRef which should be checked
-- \returns true if metadata contains power storage, false otherwise
function industrialtest.api.hasPowerStorage(meta)
	local values={"industrialtest.powerCapacity","industrialtest.powerFlow","industrialtest.powerAmount","industrialtest.ioConfig"}
	for _,value in ipairs(values) do
		if not meta:contains(value) then
			return false
		end
	end
	return true
end

-- \brief Checks if ioConfig from metadata is extended one. It exploits the fact that minetest.serialize returns Lua code
--        which returns table, hence it can check for return at the beginning of the string
-- \param meta MetaDataRef
-- \returns bool
function industrialtest.api.isExtendedIoConfig(meta)
	return meta:get_int("industrialtest.extendedIoConfig")==1
end

-- \brief Returns ioConfig from metadata, deserializing it if it's extended one
-- \param meta MetaDataRef
-- \returns table or string
function industrialtest.api.getIoConfig(meta)
	local ioConfig=meta:get_string("industrialtest.ioConfig")
	return industrialtest.api.isExtendedIoConfig(meta) and minetest.deserialize(ioConfig) or ioConfig
end

-- \brief Changes node's power IO config. Function doesn't check if meta actually contains power storage.
-- \param meta MetaDataRef of node which power IO config should be changed
-- \param side Side number. See industrialtest.api.addPowerStorage to check order.
-- \param mode Side mode. See industrialtest.api.addPowerStorage for possible values.
-- \returns nil
function industrialtest.api.changeIoConfig(meta,side,mode)
	local ioConfig=industrialtest.api.getIoConfig(meta)
	if type(ioConfig)=="string" then
		ioConfig=string.sub(ioConfig,1,side-1)..mode..string.sub(ioConfig,side+1)
		meta:set_string("industrialtest.ioConfig",ioConfig)
	else
		ioConfig[side]=mode
		meta:set_string("industrialtest.ioConfig",minetest.serialize(ioConfig))
	end
end

-- \brief Returns power flow for side
-- \param meta MetaDataRef
-- \param side number Side number
-- \returns number
function industrialtest.api.getPowerFlowForSide(meta,side)
	local ioConfig=industrialtest.api.getIoConfig(meta)
	if type(ioConfig)=="string" then
		return meta:get_int("industrialtest.powerFlow")
	else
		return ioConfig[side].flow
	end
end

-- \brief Checks if provided side is power input
-- \param meta MetaDataRef of node
-- \param side Side number. See industrialtest.api.addPowerStorage to check order.
-- \returns true if provided side is power input, false otherwise
function industrialtest.api.isPowerInput(meta,side)
	local ioConfig=industrialtest.api.getIoConfig(meta)
	if type(ioConfig)=="string" then
		local mode=string.sub(ioConfig,side,side)
		return (mode=="i" or mode=="a")
	else
		return (ioConfig[side].mode=="i" or ioConfig[side].mode=="a")
	end
end

-- \brief Checks if provided side is power output
-- \param meta MetaDataRef of node
-- \param side Side number. See industrialtest.api.addPowerStorage to check order.
-- \returns true if provided side is power output, false otherwise
function industrialtest.api.isPowerOutput(meta,side)
	local ioConfig=industrialtest.api.getIoConfig(meta)
	if type(ioConfig)=="string" then
		local mode=string.sub(ioConfig,side,side)
		return (mode=="o" or mode=="a")
	else
		return (ioConfig[side].mode=="o" or ioConfig[side].mode=="a")
	end
end

-- \brief Checks if power storage is fully charged
-- \param meta MetaDataRef which should be checked
-- \returns true if power storage is fully charged, false otherwise
function industrialtest.api.isFullyCharged(meta)
	return meta:get_int("industrialtest.powerAmount")>=meta:get_int("industrialtest.powerCapacity")
end

-- \brief Adds power to power storage. Function doesn't check if meta contains power storage so you must be sure that it does.
-- \param meta MetaDataRef to which power should be added
-- \param amount Amount of power to add
-- \returns How much of power was actually added
function industrialtest.api.addPower(meta,amount)
	local powerAmount=meta:get_int("industrialtest.powerAmount")
	local powerCapacity=meta:get_int("industrialtest.powerCapacity")
	local prevPowerAmount=powerAmount
	powerAmount=industrialtest.internal.clamp(powerAmount+amount,0,powerCapacity)
	meta:set_int("industrialtest.powerAmount",powerAmount)
	return powerAmount-prevPowerAmount
end

-- \brief Adds power to destination metadata while subtracting it from source metadata
-- \Param srcMeta MetaDataRef from which take power
-- \param destMeta MetaDataRef to which add power
-- \returns How much of power was actually transferred
function industrialtest.api.transferPower(srcMeta,destMeta,amount)
	local currentFlow=math.min(srcMeta:get_int("industrialtest.powerAmount"),amount)
	if currentFlow==0 then
		return 0
	end
	local actualFlow=industrialtest.api.addPower(destMeta,currentFlow)
	srcMeta:set_int("industrialtest.powerAmount",srcMeta:get_int("industrialtest.powerAmount")-actualFlow)
	return actualFlow
end

-- \brief Updates itemstack description to show current power storage information, additionally updates item wear bar.
--        Function doesn't check if itemstack contains power storage so you should be sure that it does before calling this function
-- \param itemstack ItemStack which should be updated
-- \returns nil
function industrialtest.api.updateItemPowerText(itemstack)
	local meta=itemstack:get_meta()
	if industrialtest.mtgAvailable then
		local def=minetest.registered_tools[itemstack:get_name()]
		local desc=meta:contains("industrialtest.descriptionOverride") and meta:get_string("industrialtest.descriptionOverride") or def.description
		local powerText=createItemPowerText(itemstack)
		meta:set_string("description",string.format("%s\n%s",desc,powerText))
	elseif industrialtest.mclAvailable then
		tt.reload_itemstack_description(itemstack)
	end
	itemstack:set_wear(65535-meta:get_int("industrialtest.powerAmount")/meta:get_int("industrialtest.powerCapacity")*65534)
end

-- \brief Adds power to itemstack. Function checks if itemstack has power storage.
-- \param itemstack ItemStack to which add power
-- \param amount How much power to add
-- \returns Amount of power added
function industrialtest.api.addPowerToItem(itemstack,amount)
	local meta=itemstack:get_meta()
	if not industrialtest.api.hasPowerStorage(meta) then
		return 0
	end
	local added=industrialtest.api.addPower(meta,amount)
	industrialtest.api.updateItemPowerText(itemstack)
	return added
end

-- \brief Adds power to destination itemstack while subtracting it from source metadata
-- \param srcMeta MetaDataRef from which take power
-- \param itemstack ItemStack to which add power
-- \param amount number
-- \returns How much of power was actually transferred
function industrialtest.api.transferPowerToItem(srcMeta,itemstack,amount)
	local currentFlow=math.min(srcMeta:get_int("industrialtest.powerAmount"),amount)
	if currentFlow==0 then
		return 0
	end
	local actualFlow=industrialtest.api.addPowerToItem(itemstack,currentFlow)
	srcMeta:set_int("industrialtest.powerAmount",srcMeta:get_int("industrialtest.powerAmount")-actualFlow)
	return actualFlow
end

-- \brief Adds power to destination metadata while subtracting it from source itemstack
-- \param srcItemstack ItemStack from which subtract power
-- \param meta MetaDataRef to which add power
-- \param amount How much power should be transferred
-- \returns How much of power was actually transferred
function industrialtest.api.transferPowerFromItem(srcItemstack,meta,amount)
	local srcMeta=srcItemstack:get_meta()
	local currentFlow=math.min(srcMeta:get_int("industrialtest.powerAmount"),amount)
	if currentFlow==0 then
		return 0
	end
	local actualFlow=industrialtest.api.addPower(meta,currentFlow)
	industrialtest.api.addPowerToItem(srcItemstack,-actualFlow)
	return actualFlow
end

if industrialtest.mclAvailable then
	tt.register_snippet(function(itemstring,toolCapabilities,itemstack)
		if not itemstack then
			return nil
		end
		local meta=itemstack:get_meta()
		if not industrialtest.api.hasPowerStorage(meta) then
			return nil
		end
		return createItemPowerText(itemstack),false
	end)
end
