---@class backstock_item
---@field name string the name of the item
---@field unlocked boolean whether the item is unlocked
---@field remaining number? the amount remaining
---@field default number? the default amount
---@field variable string? the name of the mission variable to use for loadouts
---@field faction string? the faction of the item
---@field creator string? the creator of the item
---@field category string? the category of the item
---@field key string? the key of the item in the inventory or requisitions ui

---@class backstock_requisition_item
---@field name string the name of the item
---@field displayName string the display name of the item
---@field amount number the amount of the item
---@field faction string the faction of the item
---@field cost number? the cost of the item
---@field id string? the requisition ID
---@field creator string? the manufacturer of the item
---@field category string? the category of the item

---@class backstock_requisition
---@field ships backstock_requisition_item[] the ship requisitions
---@field primaries backstock_requisition_item[] the primary weapon requisitions
---@field secondaries backstock_requisition_item[] the secondary weapon requisitions

---@class backstock_data
---@field ships backstock_item[]? the current ship inventory
---@field primaries backstock_item[]? the current primary weapon inventory
---@field secondaries backstock_item[]? the current secondary weapon inventory
---@field requisitions backstock_requisition? the current requisitions
---@field wings string[]? the current wings being tracked
---@field playerCP number? the player's total earned CP

Backstock = {
	--saveFilename = nil,
	defaultFilename = nil, ---@type string the default filename to load
	active = false, ---@type boolean whether backstock is active for the campaign or mission
	newReqs = false, ---@type boolean whether new requisitions are available
	current = {}, ---@type backstock_data the current backstock data
	default = {}, ---@type backstock_data the default backstock data
	missionCP = {}, ---@type table the current CP earned this mission
	strings = {
		NewReqsAvail = {"New requisitions are now available! Please check the Inventory Terminal", 9255},
		Earned = {"Earned this mission: ", 9256},
		Total = {"Total CP upon mission acceptance: ", 9257},
		CP = {"CP", 9258}
	}
}

local md = require("debugFunctions")

--- Set initial values for backstock
--- @param action string "restart" to reset all the data
--- @return nil
function Backstock:SetInitial(action)

	if action == "restart" then
		self:LoadData(action)
	end

	local section = nil

	---@class backstock_data
	local backstockReset = {
		ships = {},
		primaries = {},
		secondaries = {},
		requisitions = {
			ships = {},
			primaries = {},
			secondaries = {}
		},
		wings = nil,
		playerCP = nil
	}

	--Get default ships data
	section = Backstock.default.ships
	assert(section, "BACKSTOCK could not find default ships data!")
	for i, v in ipairs(section) do
		backstockReset.ships[i] = {
			name = v.name,
			unlocked = v.unlocked,
			remaining = v.default
		}
	end

	--Get default primaries data
	section = Backstock.default.primaries
	assert(section, "BACKSTOCK could not find default primaries data!")
	for i, v in ipairs(section) do
		backstockReset.primaries[i] = {
			name = v.name,
			unlocked = v.unlocked,
			remaining = v.default
		}
	end

	--Get default secondaries data
	section = Backstock.default.secondaries
	assert(section, "BACKSTOCK could not find default secondaries data!")
	for i, v in ipairs(section) do
		backstockReset.secondaries[i] = {
			name = v.name,
			unlocked = v.unlocked,
			remaining = v.default
		}
	end

	--Get default wings data
	backstockReset.wings = Backstock.default.wings

	--Get default requisitions data
	backstockReset.requisitions.ships = Backstock.default.requisitions.ships
	backstockReset.requisitions.primaries = Backstock.default.requisitions.primaries
	backstockReset.requisitions.secondaries = Backstock.default.requisitions.secondaries

	--Get default CP
	backstockReset.playerCP = Backstock.default.playerCP

	--Generate IDs for any built-in requisitions
	if backstockReset.requisitions.ships[1] then
		for i, v in ipairs(backstockReset.requisitions.ships) do
			backstockReset.requisitions.ships[i].id = self:GenerateReqId(v)
		end
	end
	if backstockReset.requisitions.primaries[1] then
		for i, v in ipairs(backstockReset.requisitions.primaries) do
			backstockReset.requisitions.primaries[i].id = self:GenerateReqId(v)
		end
	end
	if backstockReset.requisitions.secondaries[1] then
		for i, v in ipairs(backstockReset.requisitions.secondaries) do
			backstockReset.requisitions.secondaries[i].id = self:GenerateReqId(v)
		end
	end

	Backstock.current = backstockReset
	self:SaveChanges()

end

--- Parse custom data for a ship or weapon
--- @param entry shipclass | weaponclass the ship or weapon entry
--- @param item backstock_item the item to parse data for
--- @param wep boolean? whether the item is a weapon
--- @return nil
function Backstock:ParseCustomData(entry, item, wep)

	local utils = require("lib_utils")

	-- faction
	if entry.CustomData["faction"] ~= nil then
		local faction = entry.CustomData["faction"]
		item.faction = Factions:getFactionByAcronym(faction).DisplayName
	else
		md.print("BACKSTOCK found entry, " .. item.name .. ", that does not have a faction in custom data! Setting to GTA.")
		item.faction = Factions:getFactionByAcronym("GTA").DisplayName
	end

	-- manufacturer
	if entry.CustomData["manufacturer"] ~= nil then
		local creator = entry.CustomData["manufacturer"]
		item.creator = Factions:getFactionByName(creator).DisplayName
	else
		if wep then
			md.print("BACKSTOCK found entry, " .. item.name .. ", that does not have a manufacturer in custom data! Setting to UNKNOWN.")
			item.creator = Factions.dummyFaction.DisplayName
		else
			local creator = entry.ManufacturerString

			if creator == nil or item.creator == "" then
				md.print("BACKSTOCK found entry, " .. item.name .. ", that does not have a manufacturer in tech data! Setting to UNKNOWN.")
				creator = Factions.dummyFaction.DisplayName
			end
			item.creator = Factions:getFactionByName(creator).DisplayName
		end
	end

	-- type
	if entry.CustomData["type"] ~= nil then
		local category = entry.CustomData["type"]
		item.category = utils.xstr(utils.parseCustomXSTR(category))
	else
		if wep then
			md.print("BACKSTOCK found entry, " .. item.name .. ", that does not have a category in custom data! Setting to UNKNOWN.")
			item.category = utils.xstr("UNKNOWN", 153)
		else
			local category = entry.TypeString

			if category == nil or item.category == "" then
				md.print("BACKSTOCK found entry, " .. item.name .. ", that does not have a type in tech data! Setting to UNKNOWN.")
				category = utils.xstr("UNKNOWN", 153)
			end
			item.category = category
		end
	end
end

--- Get custom data for all backstock items
--- @return nil
function Backstock:GetCustomData()

	for i, v in ipairs(Backstock.default.ships) do
		local ship = tb.ShipClasses[v.name]

		if ship:hasCustomData() then

			self:ParseCustomData(ship, v)

		end
	end

	for _, v in ipairs(Backstock.default.primaries) do
		local weapon = tb.WeaponClasses[v.name]

		if weapon:hasCustomData() then

			self:ParseCustomData(weapon, v, true)
		end
	end

	for _, v in ipairs(Backstock.default.secondaries) do
		local weapon = tb.WeaponClasses[v.name]

		if weapon:hasCustomData() then

			self:ParseCustomData(weapon, v, true)
		end
	end

end

--- Load data from file
--- @param action string? "restart" to reset all the data to default
--- @return nil
function Backstock:LoadData(action)

	local utils = require("lib_utils")

	if action == "restart" then
		--Backstock.saveFilename = 'bkstk_' .. hv.Campaign .. "_" .. ba.getCurrentPlayer():getName():sub(1,15) .. '.cfg'
		Backstock.defaultFilename = 'backstock_' .. hv.Campaign .. '.cfg'
	else
		--Backstock.saveFilename = 'bkstk_' .. ba.getCurrentPlayer():getCampaignFilename() .. "_" .. ba.getCurrentPlayer():getName():sub(1,15) .. '.cfg'
		Backstock.defaultFilename = 'backstock_' .. ba.getCurrentPlayer():getCampaignFilename() .. '.cfg'
	end

	md.printAlways("BACKSTOCK loading campaign data from " .. Backstock.defaultFilename)

	if cf.fileExists(Backstock.defaultFilename, 'data/config', true) then
		Backstock.default = utils.loadConfig(Backstock.defaultFilename)
		self:GetCustomData()
		Backstock.active = true
	else
		Backstock.active = false
		md.printAlways("BACKSTOCK did not detect a config for campaign " .. ba.getCurrentPlayer():getCampaignFilename() .. " and is deactivating")
	end

	if Backstock.active == true then
		local saveLocation = "backstock_" .. ba.getCurrentPlayer():getCampaignFilename()
		md.printAlways("BACKSTOCK loading player data from " .. saveLocation)
		local datasaver = require("lib_data_saver")
		Backstock.current = datasaver:loadDataFromFile(saveLocation, true)
		if Backstock.current ~= nil then
		--if cf.fileExists(Backstock.saveFilename, 'data/config', true) then
			--Backstock.current = utils.loadConfig(Backstock.saveFilename)

			--Here we count the Inventory items in the config and the save file. If they differ, overwrite the save file portion with the config.
			if #Backstock.default.ships ~= #Backstock.current.ships then
				md.print("BACKSTOCK detected off-by-one in the save file for ships. Comparing lists!")
				for i, v in ipairs(Backstock.default.ships) do
					if not self:CheckExists(Backstock.current.ships, v.name) then
						md.print("BACKSTOCK detected " .. v.name .. " is missing in the save file. Adding!")
						local index = #Backstock.current.ships + 1
						Backstock.current.ships[index] = {
							name = v.name,
							unlocked = v.unlocked,
							remaining = v.default
						}
					end
				end
				for i, v in ipairs(Backstock.current.ships) do
					if not self:CheckExists(Backstock.default.ships, v.name) then
						md.print("BACKSTOCK detected " .. v.name .. " is missing in the config file. Removing!")
						Backstock.current.ships[i] = nil
					end
				end
				self:RemoveNilEntries(Backstock.current.ships)
				self:SaveChanges()
			end
			if #Backstock.default.primaries ~= #Backstock.current.primaries then
				md.print("BACKSTOCK detected off-by-one in the save file for primaries. Comparing lists!")
				for i, v in ipairs(Backstock.default.primaries) do
					if not self:CheckExists(Backstock.current.primaries, v.name) then
						md.print("BACKSTOCK detected " .. v.name .. " is missing in the save file. Adding!")
						local index = #Backstock.current.primaries + 1
						Backstock.current.primaries[index] = {
							name = v.name,
							unlocked = v.unlocked,
							remaining = v.default
						}
					end
				end
				for i, v in ipairs(Backstock.current.primaries) do
					if not self:CheckExists(Backstock.default.primaries, v.name) then
						md.print("BACKSTOCK detected " .. v.name .. " is missing in the config file. Removing!")
						Backstock.current.primaries[i] = nil
					end
				end
				self:RemoveNilEntries(Backstock.current.primaries)
				self:SaveChanges()
			end
			if #Backstock.default.secondaries ~= #Backstock.current.secondaries then
				md.print("BACKSTOCK detected off-by-one in the save file for secondaries. Comparing lists!")
				for i, v in ipairs(Backstock.default.secondaries) do
					if not self:CheckExists(Backstock.current.secondaries, v.name) then
						md.print("BACKSTOCK detected " .. v.name .. " is missing in the save file. Adding!")
						local index = #Backstock.current.secondaries + 1
						Backstock.current.secondaries[index] = {
							name = v.name,
							unlocked = v.unlocked,
							remaining = v.default
						}
					end
				end
				for i, v in ipairs(Backstock.current.secondaries) do
					if not self:CheckExists(Backstock.default.secondaries, v.name) then
						md.print("BACKSTOCK detected " .. v.name .. " is missing in the config file. Removing!")
						Backstock.current.secondaries[i] = nil
					end
				end
				self:RemoveNilEntries(Backstock.current.secondaries)
				self:SaveChanges()
			end
		else
			if action ~= "restart" then
				md.warningAlways("BACKSTOCK could not find a save file! Please report. Loading defaults instead.")
				self:SetInitial("error")
			end
		end
	end

	self:VerifyItems()

end

--- Remove nil entries from a table
--- @param tbl table the table to clean
--- @return nil
function Backstock:RemoveNilEntries(tbl)
	--Remove killed entries
	for i = #tbl, 1, -1 do
		if tbl[i] == nil then
			table.remove(tbl, i)
		end
	end
end

--- Used by Inventory and Requisitions to load data but faster
--- @return nil
function Backstock:LoadDataQuick()
	local utils = require("lib_utils")
	if Backstock.active then
		Backstock.default = utils.loadConfig(Backstock.defaultFilename)
		self:GetCustomData()
		Backstock.current = nil --Intentionally clear Current in the case of a campaign restart

		local saveLocation = "backstock_" .. ba.getCurrentPlayer():getCampaignFilename()
		local datasaver = require("lib_data_saver")
		Backstock.current = datasaver:loadDataFromFile(saveLocation, true)
	else
		Backstock.default = {}
		Backstock.current = {}
	end
end

--- Save data to file
--- @return nil
function Backstock:SaveChanges()

	md.print("BACKSTOCK saving data..")
	local saveLocation = "backstock_" .. ba.getCurrentPlayer():getCampaignFilename()
	local datasaver = require("lib_data_saver")
	datasaver:saveDataToFile(saveLocation, Backstock.current, true)
end

--- Generate a requisition ID
--- @param item backstock_requisition_item the item to generate an ID for
--- @return string itemId the requisition ID
function Backstock:GenerateReqId(item)

	local itemId = tostring(ba.rand32(10000)) -- Generate random ID juuuust in case so that we still have a way of getting at the item
	if item.name and item.faction then
		itemId = item.name .. "_" .. item.faction
	end
	md.print("BACKSTOCK generated item ID: " .. itemId)
	return itemId

end

--- Find a requisition ID
--- @param tab table the table to search
--- @param val string the value to search for
--- @return boolean|number result the value if found, false otherwise
function Backstock:FindReqId(tab, val)

	for i, v in ipairs(tab) do
		if v.id == val then
			return v.id
		end
	end
	return false

end

--- Check if an item exists in a table
--- @param tab table the table to search
--- @param val string the value to search for
--- @return boolean result true if found, false otherwise
function Backstock:CheckExists(tab, val)

	for i, v in ipairs(tab) do
		if v.name == val then
			return true
		end
	end
	return false

end

--- Subtract a ship's ammo from inventory on death
--- @param ship ship the ship to subtract ammo from
--- @return nil
function Backstock:SubtractOnDeath(ship)

	local thisShip = {
		PBanks = {
		},
		SBanks = {
			One = {
			},
			Two = {
			},
			Three = {
			}
		}
	}

	--Get all the relevant data
	thisShip.Class = ship.Class.Name
	thisShip.Wing = ship:getWing().Name

	thisShip.PBanks.One = ship.PrimaryBanks[1].WeaponClass.Name or nil
	thisShip.PBanks.Two = ship.PrimaryBanks[2].WeaponClass.Name or nil
	thisShip.PBanks.Three = ship.PrimaryBanks[3].WeaponClass.Name or nil

	thisShip.SBanks.One.Class = ship.SecondaryBanks[1].WeaponClass.Name or nil
	thisShip.SBanks.Two.Class = ship.SecondaryBanks[2].WeaponClass.Name or nil
	thisShip.SBanks.Three.Class = ship.SecondaryBanks[3].WeaponClass.Name or nil

	thisShip.SBanks.One.AmmoLeft = ship.SecondaryBanks[1].AmmoLeft or nil
	thisShip.SBanks.Two.AmmoLeft = ship.SecondaryBanks[2].AmmoLeft or nil
	thisShip.SBanks.Three.AmmoLeft = ship.SecondaryBanks[3].AmmoLeft or nil

	for i, v in ipairs(Backstock.current.ships) do
		if v.name == thisShip.Class and v.remaining > 0 then
			md.print("BACKSTOCK subtracting 1 from " .. v.name)
			v.remaining = v.remaining - 1
		end
	end

	for i, v in ipairs(Backstock.current.primaries) do
		if v.name == thisShip.PBanks.One and v.remaining > 0 then
			md.print("BACKSTOCK subtracting 1 from " .. v.name)
			v.remaining = v.remaining - 1
		end
	end

	for i, v in ipairs(Backstock.current.primaries) do
		if v.name == thisShip.PBanks.Two and v.remaining > 0 then
			md.print("BACKSTOCK subtracting 1 from " .. v.name)
			v.remaining = v.remaining - 1
		end
	end

	for i, v in ipairs(Backstock.current.primaries) do
		if v.name == thisShip.PBanks.Three and v.remaining > 0 then
			md.print("BACKSTOCK subtracting 1 from " .. v.name)
			v.remaining = v.remaining - 1
		end
	end

	for i, v in ipairs(Backstock.current.secondaries) do
		if v.name == thisShip.SBanks.One.Class then
			if v.remaining > thisShip.SBanks.One.AmmoLeft then
				md.print("BACKSTOCK subtracting " .. thisShip.SBanks.One.AmmoLeft .. " from " .. v.name)
				v.remaining = v.remaining - thisShip.SBanks.One.AmmoLeft
			else
				md.print("BACKSTOCK subtracting all remaining from " .. v.name)
				v.remaining = 0
			end
		end
	end

	for i, v in ipairs(Backstock.current.secondaries) do
		if v.name == thisShip.SBanks.Two.Class then
			if v.remaining > thisShip.SBanks.Two.AmmoLeft then
				md.print("BACKSTOCK subtracting " .. thisShip.SBanks.Two.AmmoLeft .. " from " .. v.name)
				v.remaining = v.remaining - thisShip.SBanks.Two.AmmoLeft
			else
				md.print("BACKSTOCK subtracting all remaining from " .. v.name)
				v.remaining = 0
			end
		end
	end

	for i, v in ipairs(Backstock.current.secondaries) do
		if v.name == thisShip.SBanks.Three.Class then
			if v.remaining > thisShip.SBanks.Three.AmmoLeft then
				md.print("BACKSTOCK subtracting " .. thisShip.SBanks.Three.AmmoLeft .. " from " .. v.name)
				v.remaining = v.remaining - thisShip.SBanks.Three.AmmoLeft
			else
				md.print("BACKSTOCK subtracting all remaining from " .. v.name)
				v.remaining = 0
			end
		end
	end

end

--- Subtract a secondary from the inventory
--- @param thisSecondary weapon the secondary to subtract
--- @return nil
function Backstock:SubtractSecondary(thisSecondary)

	for i, v in ipairs(Backstock.current.secondaries) do
		if v.name == thisSecondary.Class.Name and v.remaining > 0 then
			md.print("BACKSTOCK subtracting 1 from " .. v.name)
			v.remaining = v.remaining - 1
		end
	end

end

--- Reset the wings to default strings
--- @return nil
function Backstock:ResetWings()

	md.print("BACKSTOCK is resetting tracked wings to default!")
	Backstock.current.wings = {}
	for i, v in ipairs(Backstock.default.wings) do
		Backstock.current.wings[i] = v
	end

end

--- Verify that all the items in the inventory list are valid ship or weapon classes
--- @return nil
function Backstock:VerifyItems()

	md.print("BACKSTOCK is verifying the default items list!")

	if Backstock.current == nil then return end

	if Backstock.current.ships then
		for i, v in ipairs(Backstock.default.ships) do
			if not tb.ShipClasses[v.name]:isValid() then
				md.warning("BACKSTOCK found invalid default ship class '" .. v.name .. "'! Please correct.")
			end
		end
	else
		md.printAlways("BACKSTOCK could not find Ships section in the save file. This may be an error!")
	end

	if Backstock.current.primaries then
		for i, v in ipairs(Backstock.default.primaries) do
			if not tb.WeaponClasses[v.name]:isValid() then
				md.warning("BACKSTOCK found invalid default primary weapon class '" .. v.name .. "'! Please correct.")
			end
		end
	else
		md.printAlways("BACKSTOCK could not find Ships section in the save file. This may be an error!")
	end

	if Backstock.current.secondaries then
		for i, v in ipairs(Backstock.default.secondaries) do
			if not tb.WeaponClasses[v.name]:isValid() then
				md.warning("BACKSTOCK found invalid default secondary weapon class '" .. v.name .. "'! Please correct.")
			end
		end
	else
		md.printAlways("BACKSTOCK could not find Ships section in the save file. This may be an error!")
	end

	md.print("BACKSTOCK verification complete!")
end

--- Unlock all items based on their presence in the tech database
--- @return nil
function Backstock:UnlockItems()

	if Backstock.current.ships then
		for i, v in ipairs(Backstock.default.ships) do
			local thisShip = tb.ShipClasses[v.name]
			if thisShip.InTechDatabase then
				md.print("BACKSTOCK setting " .. v.name .. " as unlocked")
				Backstock.current.ships[i].unlocked = true
			else
				md.print("BACKSTOCK setting " .. v.name .. " as locked")
				Backstock.current.ships[i].unlocked = false
			end
		end
	else
		md.printAlways("BACKSTOCK could not find Ships section in the save file. This may be an error!")
	end
	if Backstock.current.primaries then
		for i, v in ipairs(Backstock.default.primaries) do
			local thisPrimary = tb.WeaponClasses[v.name]
			if thisPrimary.InTechDatabase then
				md.print("BACKSTOCK setting " .. v.name .. " as unlocked")
				Backstock.current.primaries[i].unlocked = true
			else
				md.print("BACKSTOCK setting " .. v.name .. " as locked")
				Backstock.current.primaries[i].unlocked = false
			end
		end
	else
		md.printAlways("BACKSTOCK could not find Primaries section in the save file. This may be an error!")
	end
	if Backstock.current.secondaries then
		for i, v in ipairs(Backstock.default.secondaries) do
			local thisSecondary = tb.WeaponClasses[v.name]
			if thisSecondary.InTechDatabase then
				md.print("BACKSTOCK setting " .. v.name .. " as unlocked")
				Backstock.current.secondaries[i].unlocked = true
			else
				md.print("BACKSTOCK setting " .. v.name .. " as locked")
				Backstock.current.secondaries[i].unlocked = false
			end
		end
	else
		md.printAlways("BACKSTOCK could not find Secondaries section in the save file. This may be an error!")
	end

end

--- Write the current values to the sexp variables on mission load
--- @return nil
function Backstock:WriteVars()

	md.print("BACKSTOCK starting to write data to variables")
	for i, v in ipairs(Backstock.default.ships) do

		--get the correct current value
		local value = nil
		for j, w in ipairs(Backstock.current.ships) do
			if w.name == v.name then
				value = w.remaining
				break
			end
		end

		md.print("BACKSTOCK looking for " .. v.variable)
		if mn.SEXPVariables[v.variable].Value ~= nil then
			md.print("BACKSTOCK found " .. v.variable .. ", current value is " .. mn.SEXPVariables[v.variable].Value)
			md.print("BACKSTOCK replacing " .. v.variable .. " with value " .. value)
			mn.SEXPVariables[v.variable] = value
			md.print("BACKSTOCK wrote " .. v.variable .. ", new value is " .. mn.SEXPVariables[v.variable].Value)
		end
	end

	for i, v in ipairs(Backstock.default.primaries) do

		--get the correct current value
		local value = nil
		for j, w in ipairs(Backstock.current.primaries) do
			if w.name == v.name then
				value = w.remaining
				break
			end
		end

		md.print("BACKSTOCK looking for " .. v.variable)
		if mn.SEXPVariables[v.variable].Value ~= nil then
			md.print("BACKSTOCK found " .. v.variable .. ", current value is " .. mn.SEXPVariables[v.variable].Value)
			md.print("BACKSTOCK replacing " .. v.variable .. " with value " .. value)
			mn.SEXPVariables[v.variable] = value
			md.print("BACKSTOCK wrote " .. v.variable .. ", new value is " .. mn.SEXPVariables[v.variable].Value)
		end
	end

	for i, v in ipairs(Backstock.default.secondaries) do

		--get the correct current value
		local value = nil
		for j, w in ipairs(Backstock.current.secondaries) do
			if w.name == v.name then
				value = w.remaining
				break
			end
		end

		md.print("BACKSTOCK looking for " .. v.variable)
		if mn.SEXPVariables[v.variable].Value ~= nil then
			md.print("BACKSTOCK found " .. v.variable .. ", current value is " .. mn.SEXPVariables[v.variable].Value)
			md.print("BACKSTOCK replacing " .. v.variable .. " with value " .. value)
			mn.SEXPVariables[v.variable] = value
			md.print("BACKSTOCK wrote " .. v.variable .. ", new value is " .. mn.SEXPVariables[v.variable].Value)
		end
	end

end

--- Set the amount of an item
--- @param item ship | weapon the item to set
--- @param amount number the amount to set
--- @return nil
function Backstock:SetAmount(item, amount)

	if amount < 0 then
		amount = 0
	end

	for i, v in ipairs(Backstock.default.ships) do
		if v.name == item.Name then
			md.print("BACKSTOCK got sexp to set " .. v.name .. " to " .. amount)
			Backstock.current.ships[i].remaining = amount
		end
	end
	for i, v in ipairs(Backstock.default.primaries) do
		if v.name == item.Name then
			md.print("BACKSTOCK got sexp to set " .. v.name .. " to " .. amount)
			Backstock.current.primaries[i].remaining = amount
		end
	end
	for i, v in ipairs(Backstock.default.secondaries) do
		if v.name == item.Name then
			md.print("BACKSTOCK got sexp to set " .. v.name .. " to " .. amount)
			Backstock.current.secondaries[i].remaining = amount
		end
	end

end

--- Get the amount of an item
--- @param item ship | weapon the item to get
--- @return number amount the amount of the item
function Backstock:GetAmount(item)

	local value = -1
	for i, v in ipairs (Backstock.default.ships) do
		if v.name == item.Name then
			md.print("BACKSTOCK got sexp to get " .. v.name)
			return Backstock.current.ships[i].remaining
		end
	end
	for i, v in ipairs (Backstock.default.primaries) do
		if v.name == item.Name then
			md.print("BACKSTOCK got sexp to get " .. v.name)
			return Backstock.current.primaries[i].remaining
		end
	end
	for i, v in ipairs (Backstock.default.secondaries) do
		if v.name == item.Name then
			md.print("BACKSTOCK got sexp to get " .. v.name)
			return Backstock.current.secondaries[i].remaining
		end
	end
	md.print("BACKSTOCK got sexp to get " .. item.Name .. ", which is not tracked. Returning -1")
	return value

end

--- Clean a table by removing entries with 0 amount
--- @param t backstock_requisition_item[] the table to clean
--- @return nil
function Backstock:CleanTable(t)
    for i, v in ipairs(t) do
        if v.amount == 0 then
            table.remove(t, i)
        end
    end
end

--- On mission acceptance, save the earned CP and reset or discard if not saving
--- @param save boolean whether to save the data
--- @return nil
function Backstock:CompleteBackstock(save)

    --technically not needed, but might as well
    if not mn.isInCampaign() then return end

    if Backstock.active then

        if save then
            local total = 0

            for i, v in ipairs(Backstock.missionCP) do
                total = total + v.value
            end

            Backstock.current.playerCP = Backstock.current.playerCP + total
        end

        Backstock.missionCP = {}

        Backstock.newReqs = false
    end

end

--- Save the current backstock to a file
--- @return nil
function Backstock:saveCurrentToFile()

	local json = require('dkjson')
	local location = 'data/config'
	local filename = "bta2_backstock_final.cfg"

	local file = nil
	local config = {}

	if cf.fileExists(filename) then
		file = cf.openFile(filename, 'r', location)
		config = json.decode(file:read('*a'))
		file:close()

		if not config then
			ba.error('Please ensure that ' .. filename .. ' exists in ' .. location .. ' and is valid JSON.')
		end
	end

	if ba.getCurrentPlayer():getName() == "" then
		ba.warning('Cannot save data when there is no player selected!')
		return
	end

	if not config[ba.getCurrentPlayer():getName()] then
		config[ba.getCurrentPlayer():getName()] = {}
	end

	config[ba.getCurrentPlayer():getName()] = Backstock.current

	local utils = require("lib_utils")
	config = utils.cleanPilotsFromSaveData(config)

	file = cf.openFile(filename, 'w', location)
	file:write(json.encode(config))
	file:close()
end

--- Add a ship requisition to the available requisitions
--- @param ship shipclass the ship to add
--- @param sexpAmount number the amount to add
--- @param sexpFaction string the faction
--- @param sexpCost number? the cost
--- @return nil
function Backstock:AddShipRequisition(ship, sexpAmount, sexpFaction, sexpCost)
	if Backstock:CheckExists(Backstock.default.ships, ship.Name) then

		local factionKey = Factions:getFactionAcryonym(sexpFaction)
		if factionKey == nil then
			md.warningAlways("BACKSTOCK could not find faction " .. sexpFaction .. " in the config file. This is an error! Please report.")
		end

		---@type backstock_requisition_item
		local currentReq = {
			name = ship.Name,
			amount = sexpAmount,
			faction = tostring(factionKey),
			cost = nil
		}

		--Verify values
		if sexpCost then
			if sexpCost < 0 then
				currentReq.cost = 0
			else
				currentReq.cost = sexpCost
			end
		end
		if sexpAmount < 0 then
			currentReq.amount = 0
		end

		local shipId = Backstock:GenerateReqId(currentReq)
		local shipReqs = Backstock.current.requisitions.ships

		currentReq.id = shipId

		local count = #shipReqs
		local mergeId = Backstock:FindReqId(shipReqs, shipId)
		if mergeId == shipId then
			for i, item in ipairs(shipReqs) do
				if item.id == mergeId then
					item.amount = item.amount + currentReq.amount
					if currentReq.cost then
						item.cost = currentReq.cost
					end
				end
			end
		else
			currentReq.cost = 1000
			shipReqs[count + 1] = currentReq
		end

		Backstock.newReqs = true
	end
end

--- Add a weapon requisition to the available requisitions
--- @param weapon weaponclass the weapon to add
--- @param sexpAmount number the amount to add
--- @param sexpFaction string the faction
--- @param sexpCost number? the cost
--- @return nil
function Backstock:AddWeaponRequisition(weapon, sexpAmount, sexpFaction, sexpCost)
	if Backstock:CheckExists(Backstock.default.primaries, weapon.Name) or Backstock:CheckExists(Backstock.default.secondaries, weapon.Name) then
		local factionKey = Factions:getFactionAcryonym(sexpFaction)
		if factionKey == nil then
			md.warningAlways("BACKSTOCK could not find faction " .. sexpFaction .. " in the config file. This is an error! Please report.")
		end

		---@type backstock_requisition_item
		local currentReq = {
			name = weapon.Name,
			amount = sexpAmount,
			faction = tostring(factionKey),
			cost = nil
		}

		--Verify values
		if sexpCost then
			if sexpCost < 0 then
				currentReq.cost = 0
			else
				currentReq.cost = sexpCost
			end
		end
		if sexpAmount < 0 then
			currentReq.amount = 0
		end

		local weaponReqs = nil

		--Primary or Secondary?
		if weapon:isSecondary() then
			weaponReqs = Backstock.current.requisitions.secondaries
		else
			weaponReqs = Backstock.current.requisitions.primaries
		end

		local weaponId = Backstock:GenerateReqId(currentReq)

		currentReq.id = weaponId

		local count = #weaponReqs
		local mergeId = Backstock:FindReqId(weaponReqs, weaponId)
		if mergeId == weaponId then
			for i, item in ipairs(weaponReqs) do
				if item.id == mergeId then
					item.amount = item.amount + currentReq.amount
					if currentReq.cost then
						item.cost = currentReq.cost
					end
				end
			end
		else
			currentReq.cost = sexpCost
			weaponReqs[count + 1] = currentReq
		end

		Backstock.newReqs = true
	end
end

--- Remove a ship requisition from the available requisitions
--- @param ship shipclass the ship to remove
--- @param sexpAmount number the amount to
--- @param sexpFaction string the faction
--- @return nil
function Backstock:RemoveShipRequisition(ship, sexpAmount, sexpFaction)
	if Backstock:CheckExists(Backstock.default.ships, ship.Name) then

		if sexpFaction == "<none>" then
			return
		end

		local factionKey = Factions:getFactionAcryonym(sexpFaction)
		if factionKey == nil then
			md.warningAlways("BACKSTOCK could not find faction " .. sexpFaction .. " in the config file. This is an error! Please report.")
		end

		---@type backstock_requisition_item
		local currentReq = {
			name = ship.Name,
			amount = sexpAmount,
			faction = tostring(factionKey),
			}

		--Verify values
		if sexpAmount <= -1 then
			currentReq.amount = -1
		end

		local shipId = Backstock:GenerateReqId(currentReq)
		local shipReqs = Backstock.current.requisitions.ships

		currentReq.id = shipId

		local count = #shipReqs
		local mergeId = Backstock:FindReqId(shipReqs, shipId)
		if mergeId == shipId then
			for i, item in ipairs(shipReqs) do
				if item.id == mergeId then
					if currentReq.amount == -1 or (item.amount - currentReq.amount) < 0 then
						item.amount = 0
					else
						item.amount = item.amount - currentReq.amount
					end
				end
			end
		end

		--Clean the table
		Backstock:CleanTable(shipReqs)
	end
end

--- Remove a weapon requisition from the available requisitions
--- @param weapon weaponclass the weapon to remove
--- @param sexpAmount number the amount to
--- @param sexpFaction string the faction
--- @return nil
function Backstock:RemoveWeaponRequisition(weapon, sexpAmount, sexpFaction)
	if Backstock:CheckExists(Backstock.default.primaries, weapon.Name) or Backstock:CheckExists(Backstock.default.secondaries, weapon.Name) then

		if sexpFaction == "<none>" then
			return
		end

		local factionKey = Factions:getFactionAcryonym(sexpFaction)
		if factionKey == nil then
			md.warningAlways("BACKSTOCK could not find faction " .. sexpFaction .. " in the config file. This is an error! Please report.")
		end

		---@type backstock_requisition_item
		local currentReq = {
			name = weapon.Name,
			amount = sexpAmount,
			faction = tostring(factionKey),
			}

		--Verify values
		if sexpAmount <= -1 then
			currentReq.amount = -1
		end

		local weaponReqs = nil

		--Primary or Secondary?
		if weapon:isSecondary() then
			weaponReqs = Backstock.current.requisitions.secondaries
		else
			weaponReqs = Backstock.current.requisitions.primaries
		end

		local weaponId = Backstock:GenerateReqId(currentReq)

		currentReq.id = weaponId

		local count = #weaponReqs
		local mergeId = Backstock:FindReqId(weaponReqs, weaponId)
		if mergeId == weaponId then
			for i, item in ipairs(weaponReqs) do
				if item.id == mergeId then
					if currentReq.amount == -1 or (item.amount - currentReq.amount) < 0 then
						item.amount = 0
					else
						item.amount = item.amount - currentReq.amount
					end
				end
			end
		end

		--Clean the table
		Backstock:CleanTable(weaponReqs)
	end
end

--- Remove all requisitions for a faction
--- @param faction string the faction to remove
--- @param boolShips boolean whether to remove ships
--- @param boolPrimaries boolean whether to remove primaries
--- @param boolSecondaries boolean whether to remove secondaries
--- @return nil
function Backstock:RemoveFactionRequisitions(faction, boolShips, boolPrimaries, boolSecondaries)

	local factionKey = Factions:getFactionAcryonym(faction)

	if factionKey ~= nil then
		if boolShips then
			local shipList = Backstock.current.requisitions.ships
			for i, v in ipairs(shipList) do
				if v.faction == factionKey then
					v.amount = 0
				end
			end
			--Clean the table
			Backstock:CleanTable(shipList)
		end
		if boolPrimaries then
			local primaryList = Backstock.current.requisitions.primaries
			for i, v in ipairs(primaryList) do
				if v.faction == factionKey then
					v.amount = 0
				end
			end
			--Clean the table
			Backstock:CleanTable(primaryList)
		end
		if boolSecondaries then
			local secondaryList = Backstock.current.requisitions.secondaries
			for i, v in ipairs(secondaryList) do
				if v.faction == factionKey then
					v.amount = 0
				end
			end
			--Clean the table
			Backstock:CleanTable(secondaryList)
		end
	else
		md.warningAlways("BACKSTOCK could not find faction " .. faction .. " in the config file. This is an error! Please report.")
	end
end

--- Remove all requisitions of a type
--- @param boolShips boolean whether to remove ships
--- @param boolPrimaries boolean whether to remove primaries
--- @param boolSecondaries boolean whether to remove secondaries
--- @return nil
function Backstock:RemoveAllRequisitionsByType(boolShips, boolPrimaries, boolSecondaries)
	if boolShips == true then
		Backstock.current.requisitions.ships = {}
	end
	if boolPrimaries == true then
		Backstock.current.requisitions.primaries = {}
	end
	if boolSecondaries == true then
		Backstock.current.requisitions.secondaries = {}
	end
end

--- Clear the wings list
--- @return nil
function Backstock:clearWings()
	Backstock.current.wings = {}
end

--- Add a wing to the list
--- @param name string the name of the wing
--- @return nil
function Backstock:addWing(name)
	table.insert(Backstock.current.wings, name)
end

--- Remove a wing from the list
--- @param name string the name of the wing
--- @return nil
function Backstock:removeWing(name)
	for i, v in ipairs(Backstock.current.wings) do
		if string.lower(name) == string.lower(v) then
			table.remove(Backstock.current.wings, i)
		end
	end
end

--- Add a mission CP to the list earned for the current mission
--- @param num number the amount of CP
--- @param desc string the description
--- @param xstr string the xstr
--- @return nil
function Backstock:addMissionCP(num, desc, xstr)
	local entry = {
		value = num,
		description = desc,
		descID = xstr
	}
	table.insert(Backstock.missionCP, entry)
end

------- Hook Functions -------

function Backstock:OnLoadoutAboutToParse()
	Backstock:LoadData() --Load data here so that players who restart missions get their backstock reset to pre-mission values
	if Backstock.active == true and mn.isInCampaign() then
		md.print("BACKSTOCK is active and trying to load variables")
		Backstock:WriteVars()
	end
end

function Backstock:OnCampaignBegin()
	local defaultFilename = 'backstock_' .. hv.Campaign .. '.cfg'
	if cf.fileExists(defaultFilename, 'data/config', true) then
		Backstock.active = true
		md.printAlways("BACKSTOCK is activating and resetting to default values")
		Backstock:SetInitial("restart")
	else
		Backstock.active = false
	end
end

function Backstock:OnCampaignMissionAccept()
	if Backstock.active == true then
		Backstock:ResetWings()
		Backstock:SaveChanges()
	end
end

function Backstock:OnShipDeathStarted()
	if Backstock.active == true and mn.isInCampaign() == true then
		for i, v in ipairs(Backstock.current.wings) do
			if string.lower(v) == string.lower(hv.Ship:getWing().Name) then
				Backstock:SubtractOnDeath(hv.Ship)
			end
		end
	end
end

function Backstock:OnWeaponCreated()
	if Backstock.active == true and mn.isInCampaign() == true then
        --If weapon isn't a secondary then bail
        if not hv.Weapon.Class:isSecondary() then
            return
        end

        --Get the parent, if no parent or parent is not a ship then bail
        local wing = nil
        if hv.Weapon.Parent ~= nil and hv.Weapon.Parent:getBreedName() == "Ship" then
            --Is the ship part of a wing? If not then bail
            wing = hv.Weapon.Parent:getWing()
            if wing:isValid() then
                wing = wing.Name
            else
                return
            end
        else
            return
        end

        --Is the parent part of a tracked wing? If not then bail
        local continue = false
        for i, wings in ipairs (Backstock.current.wings) do
            if string.lower(wings) == string.lower(wing) then
                continue = true
            end
        end
        if continue == false then
            return
        end

        --Now check that the weapon is one that's tracked
        for i, v in ipairs(Backstock.default.secondaries) do
            if v.name == hv.Weapon.Class.Name then
                md.print("BACKSTOCK got weapon " .. hv.Weapon.Class.Name .. " fired from a ship in wing " .. wing)
                Backstock:SubtractSecondary(hv.Weapon)
            end
        end
	end
end

function Backstock:OnMainMenu()
	if not Factions then
		ba.error("BACKSTOCK requires the Factions handler to be installed. Please install it and try again.")
	end
	Backstock:LoadData()
	if Backstock.active == true then
		Backstock:UnlockItems()
		Backstock:SaveChanges()
	end
end

function Backstock:OnGameplayStart()
	if Backstock.active == true then
		Backstock:ResetWings()
	end
end

------- SEXPS -------

---@param clear boolean whether to clear the list
---@param ... wing[] the wings to add
mn.LuaSEXPs['backstock-add-wing'].Action = function(clear, ...)
	if clear == true then
		Backstock:clearWings()
	end
	for i, wing in ipairs({...}) do
		Backstock:addWing(wing[i].Name)
	end
end

---@param ... wing[] the wings to remove
mn.LuaSEXPs['backstock-remove-wing'].Action = function(...)
	for i, wing in ipairs({...}) do
		Backstock:removeWing(wing[i].Name)
	end
end

mn.LuaSEXPs['backstock-reset-wings'].Action = function()
	Backstock:ResetWings()
end

---@param ship ship the ship to add
---@param amount number the amount to add
mn.LuaSEXPs['backstock-set-num-ships'].Action = function(ship, amount)
	Backstock:SetAmount(ship, amount)
end

---@param weapon weapon the weapon to add
---@param amount number the amount to add
mn.LuaSEXPs['backstock-set-num-weapons'].Action = function(weapon, amount)
	Backstock:SetAmount(weapon, amount)
end

---@param ship ship the ship to get
mn.LuaSEXPs['backstock-get-num-ships'].Action = function(ship)
	return Backstock:GetAmount(ship)
end

---@param weapon weapon the weapon to get
mn.LuaSEXPs['backstock-get-num-weapons'].Action = function(weapon)
	return Backstock:GetAmount(weapon)
end

---@param ship shipclass the ship to add
---@param sexpAmount number the amount to add
---@param sexpFaction string the faction
---@param sexpCost number? the cost
mn.LuaSEXPs['requisitions-add-ship'].Action = function(ship, sexpAmount, sexpFaction, sexpCost)
	if sexpFaction ~= "<none>" then
		Backstock:AddShipRequisition(ship, sexpAmount, sexpFaction, sexpCost)
	end
end

---@param weapon weaponclass the weapon
---@param sexpAmount number the amount to add
---@param sexpFaction string the faction
---@param sexpCost number? the cost
mn.LuaSEXPs['requisitions-add-weapon'].Action = function(weapon, sexpAmount, sexpFaction, sexpCost)
	if sexpFaction ~= "<none>" then
		Backstock:AddWeaponRequisition(weapon, sexpAmount, sexpFaction, sexpCost)
	end
end

---@param ship shipclass the ship to remove
---@param sexpAmount number the amount to
---@param sexpFaction string the faction
mn.LuaSEXPs['requisitions-remove-ship'].Action = function(ship, sexpAmount, sexpFaction)
	Backstock:RemoveShipRequisition(ship, sexpAmount, sexpFaction)
end

---@param weapon weaponclass the weapon to remove
---@param sexpAmount number the amount to
---@param sexpFaction string the faction
mn.LuaSEXPs['requisitions-remove-weapon'].Action = function(weapon, sexpAmount, sexpFaction)
	Backstock:RemoveWeaponRequisition(weapon, sexpAmount, sexpFaction)
end

---@param faction string the faction to remove
---@param boolShips boolean whether to remove ships
---@param boolPrimaries boolean whether to remove primaries
---@param boolSecondaries boolean whether to remove secondaries
mn.LuaSEXPs['requisitions-remove-faction'].Action = function(faction, boolShips, boolPrimaries, boolSecondaries)
	Backstock:RemoveFactionRequisitions(faction, boolShips, boolPrimaries, boolSecondaries)
end

---@param boolShips boolean whether to remove ships
---@param boolPrimaries boolean whether to remove primaries
---@param boolSecondaries boolean whether to remove secondaries
mn.LuaSEXPs['requisitions-remove-all'].Action = function(boolShips, boolPrimaries, boolSecondaries)
	Backstock:RemoveAllRequisitionsByType(boolShips, boolPrimaries, boolSecondaries)
end

---@param num number the amount of CP
---@param desc string the description
---@param xstr string the xstr
mn.LuaSEXPs['requisitions-sum-playercp'].Action = function(num, desc, xstr)
	Backstock:addMissionCP(num, desc, xstr)
end