local CRAFT_TOOLS = { Mining = 204228, Woodworking = 204232, Herbalism = 204230} -- Small Hoe, Small Hatchet, Small Spade
local CRAFT_INDEX_LEVELS = {
	[1] = 1,
	[2] = 8,
	[3] = 11,
	[4] = 14,
	[5] = 20,
	[6] = 26,
	[7] = 31,
	[8] = 32,
	[9] = 38,
	[10] = 41,
	[11] = 44,
	[12] = 50,
	[13] = 56,
	[14] = 61,
}

local FOOD_LEVLES = {
	[204791] = 5, -- CAKE
	[204925] = 10, -- CHEESE
	[204924] = 20, -- MILK
	[204234] = 30, -- BEEF
	[204510] = 1, -- Desert of Happiness
	[204511] = 10, -- Favorite Meal
}

local petInfoInstalled = nil

CEggPet = class(
	function (self, eggSlot)
		self.EggSlot = eggSlot
		self.Name = ""
		self.Available = false
		self.EggId = 0
		self.PetId = 0
		self.Level = 0
		self.Crafting = false
		self.Summoned = false
		self.Exp = 0
		self.MaxExp = 0
		self.TP = 0
		self.MaxTP = 0
		self.Loyalty = 0
		self.Nourishment = 0
		self.Aptitude = 0
		self.Training = 0
		self.Str = 0
		self.Sta = 0
		self.Dex = 0
		self.Int = 0
		self.Wis = 0
		self.Skills = {}
		self.Mining = 0
		self.Woodworking = 0
		self.Herbalism = 0
		self.Tool = {}
		self.Products = {}

		if eggSlot ~= nil and eggSlot ~= 0 then
			self:update()
		end
	end
)

function CEggPet:update()
	if self.EggSlot and self.EggSlot > 0 then
		eggPetAddress = addresses.eggPetBaseAddress + 0x348 * (self.EggSlot - 1)
	else
		printf("Egg pet not updated. Invalid EggSlot specified.\n")
		return
	end

	if self.EggSlot > 2 then
		self.Available = memoryReadUInt(getProc(), addresses.rentEggSlotBase + (self.EggSlot - 3) * 4) ~= 0xFFFFFFFF
	else
		self.Available = true
	end

	self.EggId = memoryReadInt(getProc(), eggPetAddress + addresses.eggPetEggId_offset)
	if self.EggId ~= nil and self.EggId > 0 and self.Available then -- There is an egg pet
		self.PetId = memoryReadInt(getProc(), eggPetAddress + addresses.eggPetPetId_offset)
		self.Name = memoryReadString(getProc(), eggPetAddress)
		if self.Name == "" then self.Name = GetIdName(self.PetId) end
		self.Level = memoryReadInt(getProc(), eggPetAddress + addresses.eggPetLevel_offset)
		self.Summoned = (memoryReadInt(getProc(), eggPetAddress + addresses.eggPetSummoned_offset) == 2)
		self.Exp = memoryReadInt(getProc(), eggPetAddress + addresses.eggPetExp_offset)
		self.MaxExp = memoryReadIntPtr(getProc(), addresses.eggPetMaxExpTablePtr, 0x4 * self.Level)
		self.TP = memoryReadInt(getProc(), eggPetAddress + addresses.eggPetTP_offset)
		self.MaxTP = memoryReadInt(getProc(), eggPetAddress + addresses.eggPetMaxTP_offset)
		self.Loyalty = memoryReadInt(getProc(), eggPetAddress + addresses.eggPetLoyalty_offset)
		self.Nourishment = memoryReadInt(getProc(), eggPetAddress + addresses.eggPetNourishment_offset)
		self.Aptitude = memoryReadFloat(getProc(), eggPetAddress + addresses.eggPetAptitude_offset)
		self.Training = memoryReadInt(getProc(), eggPetAddress + addresses.eggPetTraining_offset)
		self.Str = memoryReadFloat(getProc(), eggPetAddress + addresses.eggPetStr_offset)
		self.Sta = memoryReadFloat(getProc(), eggPetAddress + addresses.eggPetSta_offset)
		self.Dex = memoryReadFloat(getProc(), eggPetAddress + addresses.eggPetDex_offset)
		self.Int = memoryReadFloat(getProc(), eggPetAddress + addresses.eggPetInt_offset)
		self.Wis = memoryReadFloat(getProc(), eggPetAddress + addresses.eggPetWis_offset)
		self.Skills = {}
		local skillBase = eggPetAddress + addresses.eggPetSkills_offset
		for i = 0, 7 do
			local id = memoryReadInt(getProc(), skillBase + 0x8 * i)
			if id and id > 0 then
				self.Skills[i+1] = {}
				self.Skills[i+1].Id = id
				self.Skills[i+1].Name = GetIdName(id)
				self.Skills[i+1].Level = memoryReadInt(getProc(), skillBase + 0x8 * i + 0x4) + 1
			end
		end
		self.Mining = memoryReadFloat(getProc(), eggPetAddress + addresses.eggPetMining_offset)
		self.Woodworking = memoryReadFloat(getProc(), eggPetAddress + addresses.eggPetWoodworking_offset)
		self.Herbalism = memoryReadFloat(getProc(), eggPetAddress + addresses.eggPetHerbalism_offset)
		self.Tool = {}
		local toolId = memoryReadInt(getProc(), eggPetAddress + addresses.eggPetToolId_offset)
		if toolId ~= nil and toolId > 0 then
			self.Tool.Id = toolId
			self.Tool.Name = GetIdName (toolId)
			self.Crafting = (memoryReadInt(getProc(), eggPetAddress + addresses.eggPetCrafting_offset) == 10)
		else
			self.Crafting = false
		end
		self.Products = {}
		local productsBase = eggPetAddress + addresses.eggPetProducts_offset
		for i = 0, 3 do
			local id = memoryReadInt(getProc(), productsBase + 0x44 * i)
			if id and id > 0 then
				self.Products[i+1] = {}
				self.Products[i+1].Id = id
				self.Products[i+1].Name = GetIdName(id)
				self.Products[i+1].ItemCount = memoryReadInt(getProc(), productsBase + 0x44 * i + 0x10)
			end
		end
	else
		self.Name = ""
		self.EggId = 0
		self.PetId = 0
		self.Level = 0
		self.Crafting = false
		self.Summoned = false
		self.Exp = 0
		self.MaxExp = 0
		self.TP = 0
		self.MaxTP = 0
		self.Loyalty = 0
		self.Nourishment = 0
		self.Aptitude = 0
		self.Training = 0
		self.Str = 0
		self.Sta = 0
		self.Dex = 0
		self.Int = 0
		self.Wis = 0
		self.Skills = {}
		self.Mining = 0
		self.Woodworking = 0
		self.Herbalism = 0
		self.Tool = {}
		self.Products = {}
	end
end

function CEggPet:feed(foodNameOrId, number)
	self:update()

	local originalFoodNumber = inventory:itemTotalCount(foodNameOrId)

	-- 'number' can be "all" to feed all food.
	if number == nil then
		number = 1 -- default, use 1
	elseif string.lower(number) == "all" then
		number = originalFoodNumber
	end

	local summonedState = self.Summoned

	repeat -- this loop is if you need to use more than 1 stack of food
		-- find food item.
		local foodItem = inventory:findItem(foodNameOrId)

		if( foodItem ~= nil ) then
			printf("Feeding Egg Pet...\n")
			if self.Summoned == true then -- Return pet for feeding
				self:Return(); yrest(200)
				self:update()
			end

			-- Insert food in pet feed slot
			RoMCode("ClearPetFeedItem()")
			RoMCode("PickupBagItem("..foodItem.BagId..")")
			RoMCode("ClickPetFeedItem()")

			-- How many left to do
			local numberDone = originalFoodNumber - inventory:itemTotalCount(foodNameOrId)
			local numberLeft = number - numberDone

			-- How many times to feed this loop
			if foodItem.ItemCount >= numberLeft then
				times = numberLeft
			else
				times = foodItem.ItemCount
			end

			-- Feed number of times
			for i = 1, times do
				RoMCode("FeedPet(" .. self.EggSlot .. ")")
			end

			RoMCode("ClearPetFeedItem()")
		else
			break -- No feed found
		end
	until (originalFoodNumber - inventory:itemTotalCount(foodNameOrId)) >= number


	if summonedState == true and self.Summoned == false then
		self:Summon()
	end

	self:update()
end

function CEggPet:Summon()
	self:update()
	if self.EggId > 0 and player.Level > self.Level-5 and self.Summoned == false and player.Alive and player.HP > 0 then
		keyboardRelease( settings.hotkeys.MOVE_FORWARD.key ); yrest(200)
		RoMCode("SummonPet("..self.EggSlot..")")
		repeat
			yrest(500)
			self:update()
			player:updateBattling()
			player:updateCasting()
		until self.Summoned or player.Battling or player.Casting == false
	end
end

function CEggPet:Return()
	self:update()
	if self.EggId > 0 and self.Summoned then
		repeat
			RoMCode("ReturnPet("..self.EggSlot..")")
			yrest(100)
			self:update()
		until self.Summoned == false
	end
end

function CEggPet:craft(_craftType, indexLevel)
	-- Stop petInfo from auto replacing tools if installed
	if petInfoInstalled == nil then -- so it only checks once
		petInfoInstalled = RoMScript("PetInfo~=nil")
		if petInfoInstalled then
			RoMCode("PetInfoFrame:UnregisterEvent(\"PET_CRAFT_END\")")
		end
	end

	self:update()

	--Check if already crafting
	if self.Crafting then
		return
	elseif self.Summoned then
		self:Return()
	end

	-- Clear Harvest
	self:harvest()
	inventory:update()

	-- Remove existing tool tool, even if the correct tool (resolves a bug in the game).
	if self.Tool.Id ~= nil then
		local emptyBagSlot = inventory:findItem(0,"bags")
		if emptyBagSlot == nil then
			return
		end

		RoMCode("ClickPetCraftItem(".. self.EggSlot ..")")
		RoMCode("PickupBagItem(".. emptyBagSlot.BagId ..")");
		-- wait for a maximum of 1 second
		for i = 1, 4 do
			yrest(250)
			self:update()
			if self.Tool.Id == nil then
				break
			end
		end
	end

	if self.Tool.Id ~= nil or self.Crafting then -- maybe an addon replaced the tools. Give up.
		return
	end

	-- check '_craftType' value
	if _craftType ~= nil and CRAFT_TOOLS[_craftType] == nil then
		-- invalid type
		return
	elseif _craftType == nil then
		-- then use profile settings

		-- Which tool
		local bestTool = self:getBestTool()

		if bestTool == nil then -- no tools in inventory
			return
		end

		_craftType = bestTool.Type
	end

	-- Get tool item
	local craftTool = inventory:findItem(CRAFT_TOOLS[_craftType])

	if craftTool == nil then
		return
	end

	-- Get pet craft Level
	local petCraftLvl = self[_craftType]

	-- Get highest possible craft level
	local maxCraftIndex = 0
	for i = 1, 14 do
		if petCraftLvl >= CRAFT_INDEX_LEVELS[i] then
			maxCraftIndex = i
		else
			break
		end
	end

	-- Check indexLevel
	if indexLevel == nil then
		-- See if there is a user profile index override
		if settings.profile.options.EGGPET_CRAFT_INDEXES then
			local mIndex, wIndex, hIndex = string.match(settings.profile.options.EGGPET_CRAFT_INDEXES,"(%w*)%s*,%s*(%w*)%s*,%s*(%w*)")
			if _craftType == "Mining" then
				indexLevel = mIndex
			elseif _craftType == "Woodworking" then
				indexLevel = wIndex
			elseif _craftType == "Herbalism" then
				indexLevel = hIndex
			end
		end
		-- convert to numbers
		indexLevel = tonumber(indexLevel)
	end

	if indexLevel == nil or indexLevel > maxCraftIndex then
		indexLevel = maxCraftIndex -- Default to highest level
	end

	-- Insert tool
	RoMCode("PickupBagItem("..craftTool.BagId..")")
	RoMCode("ClickPetCraftItem(".. self.EggSlot ..")")

	yrest(1000)

	-- Start crafting
	RoMCode("SendSystemChat('Pet Crafting started.')")
	RoMCode("PetCraftingStart(".. self.EggSlot ..",".. indexLevel ..")")

	self:update()
end

function CEggPet:harvest()
	if #self.Products ~= 0 then
		RoMCode("PetCraftHarvest(".. self.EggSlot.. ")")
		yrest(2000)
		self:update()
	end
end

function CEggPet:getToolChoices()
	-- Returns the tool ids in order based on the user profile ratio setting and the materials in your inventory

	local function countMaterials(craft)
		local t2
		if craft == "Mining" then
			t2 = 0
		elseif craft == "Woodworking" then
			t2 = 1
		elseif craft == "Herbalism" then
			t2 = 2
		end

		local count = 0
		for k, item in pairs(inventory.BagSlot) do
			if not item.Empty and item.Available and
				item.ObjType == 3 and item.ObjSubType == t2 then
				local tmpCount = item.ItemCount
				if item.Quality > 0 then
					tmpCount = tmpCount * 2
				end
				if item.Quality > 1 then
					tmpCount = tmpCount * 6^(item.Quality - 1)
				end
				count = count + tmpCount
			end
		end
		return count
	end

	-- Get user profile ratio settings
	local mRatio, wRatio, hRatio
	if settings.profile.options.EGGPET_CRAFT_RATIO ~= nil then
		mRatio, wRatio, hRatio = string.match(settings.profile.options.EGGPET_CRAFT_RATIO,"(%d*)%s*:%s*(%d*)%s*:%s*(%d*)")
	end

	-- check for nil values
	if mRatio == nil or wRatio == nil or hRatio == nil then
		mRatio, wRatio, hRatio = 1,1,1 -- default to 1:1:1 if no ratio given
	end

	-- convert to numbers
	mRatio = tonumber(mRatio)
	wRatio = tonumber(wRatio)
	hRatio = tonumber(hRatio)

	-- equilize values and add to tmpResult table
	local tmpResults = {}
	if mRatio > 0 then
		local tmp = {}
		tmp.Type = "Mining"
		tmp.Id = CRAFT_TOOLS[tmp.Type]
		tmp.Value = countMaterials(tmp.Type)/mRatio
		tmp.HaveSome = false
		table.insert(tmpResults,tmp)
	end
	if wRatio > 0 then
		local tmp = {}
		tmp.Type = "Woodworking"
		tmp.Id = CRAFT_TOOLS[tmp.Type]
		tmp.Value = countMaterials(tmp.Type)/wRatio
		tmp.HaveSome = false
		table.insert(tmpResults,tmp)
	end
	if hRatio > 0 then
		local tmp = {}
		tmp.Type = "Herbalism"
		tmp.Id = CRAFT_TOOLS[tmp.Type]
		tmp.Value = countMaterials(tmp.Type)/hRatio
		tmp.HaveSome = false
		table.insert(tmpResults,tmp)
	end

	-- Sort
	table.sort(tmpResults, function(a,b) return a.Value < b.Value end)

	results = {}
	for k,v in pairs(tmpResults) do
		results[k] = {Type = v.Type, Id = v.Id}
	end

	return results
end

function CEggPet:getBestTool()
	local toolChoices = self:getToolChoices()
	-- Check if you have tools in inventory

	-- Custom inventory search function to speed things up
	local item
	for slot = 51, 240 do
		item = inventory.BagSlot[slot]
		if item.Available and not item.Empty then

			for i, Type in pairs(toolChoices) do
				if Type.Id == item.Id or Type.Id == self.Tool.Id then
					toolChoices[i].HaveSome = true
					break
				end
			end

		end
	end

	-- Check if you have tools
	for i, Type in pairs(toolChoices) do
		if Type.HaveSome then
			return Type
		end
	end
end


function checkEggPets()
	-- This function makes sure the pets are crafting and assisting as per the profile settings.

	local assistEgg = nil
	local craftEgg = nil

	-- Get Eggs
	if settings.profile.options.EGGPET_ENABLE_ASSIST and
	  settings.profile.options.EGGPET_ASSIST_SLOT ~= NIL then
		assistEgg = CEggPet(settings.profile.options.EGGPET_ASSIST_SLOT)
		if assistEgg.EggId == 0 then -- Bad egg
			printf("Bad egg slot given to EGGPET_ASSIST_SLOT in profile.\n")
			assistEgg = nil
		else
			player:updateLevel()
			if assistEgg.Level >= player.Level + 5 then
				assistEgg = nil
			end
		end
	end

	if settings.profile.options.EGGPET_ENABLE_CRAFT and
	  settings.profile.options.EGGPET_CRAFT_SLOT ~= NIL then
		craftEgg = CEggPet(settings.profile.options.EGGPET_CRAFT_SLOT)
		if craftEgg.EggId == 0 then -- Bad edd
			printf("Bad egg slot given to EGGPET_CRAFT_SLOT in profile.\n")
			craftEgg = nil
		elseif not craftEgg.Crafting then
			craftEgg:harvest() -- This 'harvest' makes sure to harvest when the tools run out
		end
	end

	-- Checks they are not the same
	if assistEgg and craftEgg and (assistEgg.EggSlot == craftEgg.EggSlot) then
		-- if there are tools, craft, else assist

		local choice = craftEgg:getBestTool()
		-- Check if you have tools in inventory
		if choice == nil and craftEgg.Tool.Id == nil then
			-- No tools, disable craft
			craftEgg = nil
		else
			-- Have tools, disable assist
			assistEgg = nil
		end

	end

	if assistEgg then
		-- This is the assist egg. Assist ...

		-- First make sure it isn't crafting
		if assistEgg.Crafting then
			RoMCode("PetCraftingStop(".. assistEgg.EggSlot ..")")
			repeat
				assistEgg:update()
			until assistEgg.Crafting == false
		end

		-- Custom count function because 6 itemTotalCounts is too slow
		inventory:update();

		local item, cake, cheese, milk, beef, favorite, dessert = 0,0,0,0,0,0,0
		for slot = 51, 240 do
			item = inventory.BagSlot[slot]
			if item.Available then
				if item.Id == 204791 then
					cake = cake + item.ItemCount
				elseif item.Id == 204925 then
					cheese = cheese + item.ItemCount
				elseif item.Id == 204924 then
					milk = milk + item.ItemCount
				elseif item.Id == beef then
					beef = beef + item.ItemCount
				elseif item.Id == 204511 then
					favorite = favorite + item.ItemCount
				elseif item.Id == 204510 then
					dessert = dessert + item.ItemCount
				end
			end;
		end;

		-- Check if needs to be fed

		-- Get best nourishment food
		local foodId = 0
		if assistEgg.Nourishment <= 95 and cake > 0 then -- use cake
			foodId = 204791
		elseif assistEgg.Nourishment <= 90 and cheese > 0 then -- use cheese
			foodId = 204925
		elseif assistEgg.Nourishment <= 80 and milk > 0 then -- use milk
			foodId = 204924
		elseif assistEgg.Nourishment <= 70 and beef > 0 then -- use beef
			foodId = 204234
		end

		-- Get best loyalty food
		local loyaltyFoodId = 0
		if assistEgg.Loyalty <= 90 and favorite > 0 then -- Use Favorite Meal
			loyaltyFoodId = 204511
		elseif assistEgg.Loyalty <= 99 and dessert > 0 then -- Use Dessert of Happiness
			loyaltyFoodId = 204510
		end

		if FOOD_LEVLES[foodId] and assistEgg.Nourishment <= (100 - FOOD_LEVLES[foodId]) or -- needs nourishment
		   FOOD_LEVLES[loyaltyFoodId] and assistEgg.Loyalty <= (100 - FOOD_LEVLES[loyaltyFoodId]) then -- needs loyalty
			if assistEgg.Summoned then
				assistEgg:Return()
			end

			-- feed nourishment
			if foodId ~= 0 then
				while assistEgg.Nourishment <= (100 - FOOD_LEVLES[foodId]) and inventory:itemTotalCount(foodId) > 0 do
					assistEgg:feed(foodId)
				end
			end

			-- feed loyalty
			if loyaltyFoodId ~= 0 then
				while assistEgg.Loyalty <= (100 - FOOD_LEVLES[loyaltyFoodId]) and inventory:itemTotalCount(loyaltyFoodId) > 0 do
					assistEgg:feed(loyaltyFoodId)
				end
			end
		end

		-- Resummon pet
		if assistEgg.Summoned == false then
			assistEgg:Summon()
		end
	end

	if craftEgg and not craftEgg.Crafting then
		-- This is the craft egg. Craft ...

		craftEgg:craft()
	end
end
