-- Credits for most of memory reading logic : https://drive.google.com/drive/folders/1M-PdZrACBkGPpceTanCq_ltbGNT24lR8

local lastTime = os.clock()

local terminator = 0xFF
local monNameLength = 10
local speciesNameLength = 11
local playerNameLength = 10
local boxMonSize = 80
local partyMonSize = 100
local partyloc = 0x2023a98 --gPlayerParty
local partyCount = 0x2023a95 --gPlayerPartyCount
local storageLoc = 0x2028848 -- gPokemonStorage
local speciesNameTable = 0x3185c8

local defeatedTrainersStart = 0X020262DD
local defeatedTrainersEnd = 0x020263ED
local pickedStarterAddress = 0x02026442 -- 0 Turtwig / 1 Chimchar / 2 Piplup

local levelCapThreasholds = {0x30, 0x70, 0x81, 0x87, 0xF0, 0x83, 0x38, 0x8B, 0x3A, 0x8B, 0x8F, 0xB0, 0x40, 0x9F, 0x90, 0x11, 0xC0, 0x19, 0xBF, 0x06, 0xFF, 0x10, 0xFF}
local levelCapValues =      {  17,   21,   25,   32,   35,   38,   42,   48,   54,   57,   65,   66,   69,   73,   76,   79,   81,   85,   89,   91,   95,   99,  100}
local levelCapAddresses =   {
	0x020263EC, -- Route 104 Aqua Grunt
	0x020263EC, -- Museum Aqua Grunts
	0x020263DD, -- Leader Brawly 
	0x020263DC, -- Leader Roxanne
	0x020263EC, -- Route 117 Chelle
	0x020263DD, -- Leader Wattson
	0x020263ED, -- Cycling Road Rival
	0x020263DD, -- Leader Norman 
	0x020263ED, -- Fallarbor Town Vito
	0x020262E1, -- Mt. Chimney Maxie
	0x020263DD, -- Leader Flannery
	0x020262E2, -- Weather Institute Shelly
	0x020262DD, -- Route 119 Rival
	0x020263DD, -- Leader Winona
	0x020262F4, -- Lilycove City Rival
	0x020262EA, -- Mt. Pyre Archie
	0x020262DD, -- Magma Hideout Maxie
	0x020262DE, -- Aqua Hideout Matt
	0x020263DD, -- Leaders Tate & Liza
	0x020262E0, -- Seafloor Cavern Archie
	0x020263DD, -- Leader Juan
	0x020263E6, -- Victory Road Vito
	0x020263DF, -- Champion Wallace
}

local curve = {3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,3,3,3,0,0,0,0,0,0,0,0,0,0,3,3,3,3,3,3,4,4,0,0,4,4,0,0,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,5,5,3,3,3,3,3,3,3
		,3,3,3,3,3,5,5,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,3,3,3,0,0,0,0,0,0,0,5,5,0,0,0,0,0,0,0,5,5,4,0,0,0,0,0,0,5,5,0,0,0,0,0,5,5,5,5,5,0,0,0,0
		,0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,3,3,3,3,3,3,3,3,3,3,0,0,0,0,4,4,4,4,0,5,5,0,4,4,4,4,0,0,3,3,3,3,4,4,0,3,3,3,3,4,3,3,0,0,0,0,0,3,0,4,0,0,0,0
		,0,0,3,0,4,4,0,0,3,5,3,0,0,0,0,5,5,4,0,0,4,5,5,5,5,0,0,0,0,5,4,0,0,0,0,0,5,4,5,5,5,5,5,5,5,5,3,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,3,3,3,3
		,3,3,3,3,0,0,5,5,5,0,0,2,2,5,5,5,1,1,1,3,3,3,2,2,4,0,4,4,3,4,5,5,5,0,0,5,5,0,0,1,2,3,2,2,5,5,2,2,0,0,0,4,4,4,3,3,3,3,3,1,1,1,2,4,4,0,0,2,2
		,0,0,1,1,1,1,1,1,0,3,4,4,4,4,5,4,3,0,0,0,3,3,3,1,1,1,5,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,3,3,3,3,3,3,3,3,3,3,3,3,0,0,3,3,3,3,3,3,3,1,1,1,1
		,0,0,0,3,3,0,0,0,0,0,0,0,4,2,2,0,0,4,3,4,4,4,0,0,0,0,0,0,4,3,0,5,5,5,5,3,3,5,5,5,5,0,0,5,1,1,5,5,5,3,0,0,5,0,0,0,4,0,0,0,3,5,0,5,0,4,0,0,5
		,5,5,5,5,5,5,5,5,5,5,5,3,5,5,3,3,3,3,3,3,3,3,3,0,0,3,3,3,0,0,0,0,0,0,0,0,4,4,3,3,3,0,0,3,3,3,0,0,0,0,4,3,3,3,3,3,3,0,0,3,3,3,3,3,3,0,0,0,0
		,0,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,4,4,3,3,3,3,3,3,0,0,5,5,5,0,0,0,0,0,0,0,0,0,4,0,0,0,0,3,3,3,5,5,5,0,0,3,3,3,5,5,5,0,0,0,0,0,0
		,3,3,0,0,0,0,0,0,5,5,5,5,0,0,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,3,3,3,3,3,3,3,3,3,0,0,3,3,3,0,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
		,0,0,0,0,5,5,0,0,0,0,0,0,0,0,0,5,5,5,5,4,0,0,0,0,0,0,0,0,5,5,5,5,5,5,3,3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0
		,0,0,0,0,3,3,3,4,5,5,0,0,0,0,4,5,5,0,5,0,0,0,0,0,0,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,3,3,3,3,3,3,3,3,3,0,0,3,3,3,0,0
		,0,4,4,0,0,0,0,0,0,4,4,3,3,3,1,1,1,0,0,0,5,5,3,3,0,0,3,3,0,0,5,5,5,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,5,5,4,0,0,0,5,5,5,5,0,5,5,5,5,5,5,5,5,5,5
		,5,5,5,5,5,0,0,0,3,0,5,3,3,3,3,0,3,3,0,3,0,5,5,5,5,5,3,0,0,5,5,5,3,3,3,5,3,4,5,0,5,5,0,1,4,3,0,5,5,5,5,0,5,3,5,5,4,5,5,5,5,0,0,0,0,0,0,0,0
		,0,0,0,3,3,3,0,0,5,0,0,0,0,0,0,0,0,0,5,5,5,0,4,0,0,3,3,0,0,5,5,0,0,3,0,3,3,0,3,3,5,5,5,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
		,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,3,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0,0,3,3,0,0,0,0,0,0,5,5
		,5,5,5,5,5,5,5,5,5,5,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,5,5,0,0
		,0,0,0,0,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,5,5,5,0,0,3,0,0,0,0,0,0,0,0,0,0,5,4,0,5,5,5,5,5,5,5,5}

function getLevelCap()
	local levelCap = 12 -- Default level cap

	for levelId = 1, #levelCapAddresses do
		currentThreashold = memory.read_u8(levelCapAddresses[levelId], "System Bus")
		levelThreashold = levelCapThreasholds[levelId]

		-- Check each threashold until one is not high enough
		if (currentThreashold >= levelThreashold) then
			levelCap = levelCapValues[levelId]
		else
			return levelCap
		end
	end
end

function slowCurve(n)
    return math.floor((5*(n^3))/4)
end
function fastCurve(n)
    return math.floor((4*(n^3))/5)
end
function medfastCurve(n)
    return n^3
end
function medslowCurve(n)
    return math.floor((6 * (n)^3) / 5) - (15 * (n)^2) + (100 * n) - 140
end
function erraticCurve(n)
    if (n<=50) then
        return math.floor(((100 - n)*n^3)/50)
    end
    if (n<=68) then
        return math.floor(((150 - n)*n^3)/100)
    end
    if (n<=98) then
        return math.floor(math.floor((1911 - 10 * n) / 3) * n^3 / 500)
    end
    return math.floor((160 - n) * n^3 / 100)
end
function flutuatingCurve(n)
	if (n<15) then
	  return math.floor((math.floor((n + 1) / 3) + 24) * n^3 / 50)
	end
	if (n<=36) then
		return math.floor((n + 14) * n^3 / 50)
	end
	return math.floor((math.floor(n / 2) + 32) * n^3 / 50)
end

function calcLevel(exp, species)
	level = 1
	while (exp>=expRequired(species,level+1)) do
		level=level+1
	end
	return level
end

function expRequired(species,level)
	expCurve = curve[species]
	if (expCurve == 0) then return medfastCurve(level) end
	if (expCurve == 1) then return erraticCurve(level) end
	if (expCurve == 2) then return flutuatingCurve(level) end
	if (expCurve == 3) then return medslowCurve(level) end
	if (expCurve == 4) then return fastCurve(level) end
	if (expCurve == 5) then return slowCurve(level) end
end

function readRange(address, length)
  local bytes = {}
  for i=0,length-1 do
    bytes[i] = memory.read_u8(address + i, "System Bus")
  end
  return bytes
end

function readBoxMon(address)
	local mon = {}
	mon.personality = memory.read_u32_le(address + 0, "System Bus")
	mon.otId = memory.read_u32_le(address + 4, "System Bus")
	mon.nickname = readRange(address + 8, monNameLength)
	mon.language = memory.read_u8(address + 18, "System Bus")
	local flags = memory.read_u8(address + 19, "System Bus")
	mon.isBadEgg = flags & 1
	mon.hasSpecies = (flags >> 1) & 1
	mon.isEgg = (flags >> 2) & 1
	mon.otName = readRange(address + 20, playerNameLength)
	mon.markings = memory.read_u8(address + 27, "System Bus")

	local key = mon.otId ~ mon.personality
	local substructSelector = {
		[ 0] = {0, 1, 2, 3},
		[ 1] = {0, 1, 3, 2},
		[ 2] = {0, 2, 1, 3},
		[ 3] = {0, 3, 1, 2},
		[ 4] = {0, 2, 3, 1},
		[ 5] = {0, 3, 2, 1},
		[ 6] = {1, 0, 2, 3},
		[ 7] = {1, 0, 3, 2},
		[ 8] = {2, 0, 1, 3},
		[ 9] = {3, 0, 1, 2},
		[10] = {2, 0, 3, 1},
		[11] = {3, 0, 2, 1},
		[12] = {1, 2, 0, 3},
		[13] = {1, 3, 0, 2},
		[14] = {2, 1, 0, 3},
		[15] = {3, 1, 0, 2},
		[16] = {2, 3, 0, 1},
		[17] = {3, 2, 0, 1},
		[18] = {1, 2, 3, 0},
		[19] = {1, 3, 2, 0},
		[20] = {2, 1, 3, 0},
		[21] = {3, 1, 2, 0},
		[22] = {2, 3, 1, 0},
		[23] = {3, 2, 1, 0},
	}

	local pSel = substructSelector[mon.personality % 24]
	local ss0 = {}
	local ss1 = {}
	local ss2 = {}
	local ss3 = {}

	for i = 0, 2 do
		ss0[i] = memory.read_u32_le(address + 32 + pSel[1] * 12 + i * 4, "System Bus") ~ key
		ss1[i] = memory.read_u32_le(address + 32 + pSel[2] * 12 + i * 4, "System Bus") ~ key
		ss2[i] = memory.read_u32_le(address + 32 + pSel[3] * 12 + i * 4, "System Bus") ~ key
		ss3[i] = memory.read_u32_le(address + 32 + pSel[4] * 12 + i * 4, "System Bus") ~ key
	end

	mon.species = ss0[0] & 0xFFFF
	mon.heldItem = ss0[0] >> 16
	mon.experience = ss0[1]
	mon.ppBonuses = ss0[2] & 0xFF
	mon.friendship = (ss0[2] >> 8) & 0xFF
	mon.hiddenNature = (ss0[2] >> 16) & 0x1F

	mon.moves = {
		ss1[0] & 0xFFFF,
		ss1[0] >> 16,
		ss1[1] & 0xFFFF,
		ss1[1] >> 16
	}
	mon.pp = {
		ss1[2] & 0xFF,
		(ss1[2] >> 8) & 0xFF,
		(ss1[2] >> 16) & 0xFF,
		ss1[2] >> 24
	}

	mon.hpEV = ss2[0] & 0xFF
	mon.attackEV = (ss2[0] >> 8) & 0xFF
	mon.defenseEV = (ss2[0] >> 16) & 0xFF
	mon.speedEV = ss2[0] >> 24
	mon.spAttackEV = ss2[1] & 0xFF
	mon.spDefenseEV = (ss2[1] >> 8) & 0xFF
	mon.cool = (ss2[1] >> 16) & 0xFF
	mon.beauty = ss2[1] >> 24
	mon.cute = ss2[2] & 0xFF
	mon.smart = (ss2[2] >> 8) & 0xFF
	mon.tough = (ss2[2] >> 16) & 0xFF
	mon.sheen = ss2[2] >> 24

	mon.pokerus = ss3[0] & 0xFF
	mon.metLocation = (ss3[0] >> 8) & 0xFF
	flags = ss3[0] >> 16
	mon.metLevel = flags & 0x7F
	mon.metGame = (flags >> 7) & 0xF
	mon.pokeball = (flags >> 11) & 0xF
	mon.otGender = (flags >> 15) & 0x1
	flags = ss3[1]
	mon.hpIV = (flags >> 1) & 0x1F
	mon.attackIV = (flags >> 6) & 0x1F
	mon.defenseIV = (flags >> 11) & 0x1F
	mon.speedIV = (flags >> 16) & 0x1F
	mon.spAttackIV = (flags >> 21) & 0x1F
	mon.spDefenseIV = (flags >> 26) & 0x1F
	-- Bit 30 is another "isEgg" bit
	flags = ss3[2]
	mon.coolRibbon = flags & 7
	mon.beautyRibbon = (flags >> 3) & 7
	mon.cuteRibbon = (flags >> 6) & 7
	mon.smartRibbon = (flags >> 9) & 7
	mon.toughRibbon = (flags >> 12) & 7
	mon.championRibbon = (flags >> 15) & 1
	mon.winningRibbon = (flags >> 16) & 1
	mon.victoryRibbon = (flags >> 17) & 1
	mon.artistRibbon = (flags >> 18) & 1
	mon.effortRibbon = (flags >> 19) & 1
	mon.marineRibbon = (flags >> 20) & 1
	mon.landRibbon = (flags >> 21) & 1
	mon.skyRibbon = (flags >> 22) & 1
	mon.countryRibbon = (flags >> 23) & 1
	mon.nationalRibbon = (flags >> 24) & 1
	mon.earthRibbon = (flags >> 25) & 1
	mon.worldRibbon = (flags >> 26) & 1
	mon.altAbility = (flags >> 29) & 3
	return mon
end

function readPartyMon(address)
	local mon = readBoxMon(address)
	mon.status = memory.read_u32_le(address + 80, "System Bus")
	mon.level = memory.read_u8(address + 84, "System Bus")
	mon.mail = memory.read_u32_le(address + 85, "System Bus")
	mon.hp = memory.read_u16_le(address + 86, "System Bus")
	mon.maxHP = memory.read_u16_le(address + 88, "System Bus")
	mon.attack = memory.read_u16_le(address + 90, "System Bus")
	mon.defense = memory.read_u16_le(address + 92, "System Bus")
	mon.speed = memory.read_u16_le(address + 94, "System Bus")
	mon.spAttack = memory.read_u16_le(address + 96, "System Bus")
	mon.spDefense = memory.read_u16_le(address + 98, "System Bus")
	return mon
end

function getParty()
	local party = {}
	local monStart = partyloc
	for i = 1, memory.read_u8(partyCount, "System Bus") do
		party[i] = readPartyMon(monStart)
		monStart = monStart + partyMonSize
	end
	return party
end

-- Main loop, triggered each frame
function updateBuffer()
    local now = os.clock()

	-- Execute script logic every second
    if now - lastTime >= 1 then
        local lastTime = now
        local address = storageLoc + 4
		local i = 0

        local partyPokemon = "PARTY"
        local boxPokemon = "BOX"
        local deadPokemon = "DEAD"
		local levelCap = "LEVELCAP|" .. getLevelCap()
		local defeatedTrainers = "TRAINERS"
		local pickedStarter = "STARTER|" .. memory.read_u16_le(pickedStarterAddress, "System Bus")

		-- Retrieve party Pokémon
        for _, mon in ipairs(getParty()) do
            if (mon.species ~= 0) then
                partyPokemon = partyPokemon .. "|" .. mon.species .. "-" .. mon.level .. "-" .. mon.heldItem
            end
        end

		-- Retrieve PC Pokémon (box + dead)
		while i < 420 do
            if (i < 30 or i >= 390) then
				if (i < 30) then
					boxPokemon = boxPokemon .. "|"
				else
					deadPokemon = deadPokemon .. "|"
				end

				-- Only read valid data
				if (memory.read_u32_le(address, "System Bus") ~=0) then
					mon = readBoxMon(address)

					-- Pokémon found : add it to data file
					if (mon.species ~= 0) then
						if (i < 30) then
							boxPokemon = boxPokemon .. mon.species .. "-" .. calcLevel(mon.experience, mon.species) .. "-" .. mon.heldItem
						else
							deadPokemon = deadPokemon .. mon.species .. "-0-" .. mon.heldItem -- No need to calc level for dead Pokémon
						end
					end
				end
            end

            address = address + 80
            i = i + 1
        end

		-- Retrieve booleans indicating if trainers have been defeated
		for trainerAddress = defeatedTrainersStart, defeatedTrainersEnd do
			defeatedTrainers = defeatedTrainers .. "|" .. memory.read_u8(trainerAddress, "System Bus")
		end

		-- Retrieve pokemonData.txt path from env variable defined by Python script
		local dataFilePath = os.getenv("RUNANDBUNREADER_CONFFILE")

		-- Write data in pokemonData.txt
		if dataFilePath then
			local f = io.open(dataFilePath, "w")

			if f then
				f:write(partyPokemon .. "\n" .. boxPokemon .. "\n" .. deadPokemon .. "\n" .. levelCap .. "\n" .. defeatedTrainers .. "\n" .. pickedStarter)
				f:close()
			else
				print("Cannot write pokemon data\n")
			end
		else
			print('Cannot find current folder, run "Run&Bun Reader.exe" and restart mGBA\n')
		end
    end
end

while true do
    updateBuffer()
    emu.frameadvance()
end