#!/usr/bin/env python3
###############################################################################
###### Use: python3 tools/script_extractor --noauto --error location     ######
###### --noauto  turns off automatic script parsing (enter to continue)  ######
###### --error   stops execution if an error occurs                      ######
###### location can be local to bank or global. This program assumes     ######
###### every script is in bank 3, which seems to be the case.            ######
######                                                                   ######
###### Script list is a work in progress. The following arguments are    ######
###### accepted and accounted for.                                       ######
###### b - byte    w - word    j - jump (within script)    t - text (tx) ######
###### f - flag    d - direction    i - decimal byte    m - npc move ptr ######
###### q - Used when the script's arguments have not been determined yet ######
###############################################################################
import argparse

# Quit Types
DO_NOT_QUIT = 0
QUIT_CONTINUE_CODE = 1
QUIT_JUMP = 2
QUIT_SPECIAL = 3
QUIT_DEBUG = -1

dir_list = ["NORTH","EAST","SOUTH","WEST"]

def printHeader(loc, prefix):
		ls = format(loc,"04x")
		lsa = format(loc-0x8000,"04x")
		print(prefix + ls + ": ; " + ls + " (3:" + lsa + ")" )

def extractMovement(game_data, loc, errQuit):
	printHeader(loc, "NPCMovement_")
	loc -= 1 # so we can continue without breaking things
	while game_data[loc+1] != 0xff:
		loc += 1
		dirLow = game_data[loc] & 0x0f
		if dirLow > 3:
			print("ERROR: [" + format(loc,"04x") + "] was not a valid direction. Got: " +  format(game_data[loc],"02x"))
			if errQuit:
				return QUIT_DEBUG
			continue
		lineStr = "\tdb " + dir_list[dirLow]
		dirHigh = game_data[loc] & 0xf0
		if dirHigh == 0x80:
			lineStr += " | NO_MOVE"
		elif dirHigh != 0x00:
			print("ERROR: [" + format(loc,"04x") + "] was not a valid direction. Got: " +  format(game_data[loc],"02x"))
			if errQuit:
				return QUIT_DEBUG
			continue
		print(lineStr)
	print("\tdb $ff")
	print("; " + format(loc+2,"04x"))
	return DO_NOT_QUIT

def decodeLine(scriptList, game_data, loc, ignore_broken, locList):
	currLine = scriptList[game_data[loc]]
	macroMode = False
	if currLine[3] != "": # this seems like a bad way to handle macros, but the least bad I wanna do rn
		macroMode = True
		ret = "\t" + currLine[3] + " "
	else:
		ret = "\trun_command " + currLine[0] + "\n"
	loc+=1
	quit = currLine[2]
	for c in currLine[1]:
		if c == "b":
			if macroMode:
				ret += "$" + format(game_data[loc],"02x") + ", "
			else:
				ret += "\tdb $" + format(game_data[loc],"02x") + "\n"
			loc += 1
		elif c == "i":
			if macroMode:
				ret += str(game_data[loc]) + ", "
			else:
				ret += "\tdb " + str(game_data[loc]) + "\n"
			loc += 1
		elif c == "w":
			if macroMode:
				ret += "$" + format((game_data[loc] + (game_data[loc+1]<<8)),"04x") + ", "
			else:
				ret += "\tdw $" + format((game_data[loc] + (game_data[loc+1]<<8)),"04x") + "\n"
			loc += 2
		elif c == "j":
			wordLoc = (game_data[loc] + (game_data[loc+1]<<8))
			if wordLoc == 0000:
				if macroMode:
					ret += "NULL, "
				else:
					ret += "\tdw NULL\n"
			else:
				if macroMode:
					ret += ".ows_" + format(wordLoc+0x8000,"04x") + ", "
				else:
					ret += "\tdw .ows_" + format(wordLoc+0x8000,"04x") + "\n"
				locList.append(wordLoc)
			loc += 2
		elif c == "t":
			addr = (game_data[loc] + (game_data[loc+1]<<8))
			if addr == 0:
				if macroMode:
					ret += "$0000, "
				else:
					ret += "\tdw $0000\n"
			else:
				if macroMode:
					ret += "Text" + format(addr,"04x") + ", "
				else:
					ret += "\ttx Text" + format(addr,"04x") + "\n"
			loc += 2
		elif c == "f":
			if macroMode:
				ret += "EVENT_FLAG_" + format(game_data[loc],"02X") + ", "
			else:
				ret += "\tdb EVENT_FLAG_" + format(game_data[loc],"02X") + "\n"
			loc += 1
		elif c == "d":
			if macroMode:
				ret += dir_list[game_data[loc]] + ", "
			else:
				ret += "\tdb " + dir_list[game_data[loc]] + "\n"
			loc += 1
		elif c == "m":
			wordLoc = (game_data[loc] + (game_data[loc+1]<<8))
			if macroMode:
				ret += "NPCMovement_" + format(wordLoc + 0x8000, "04x") + ", "
			else:
				ret += "\tdw NPCMovement_" + format(wordLoc + 0x8000, "04x") + "\n"
			loc += 2
		elif c == "q":
			print("haven't updated data for this yet")
			if not ignore_broken:
				quit = QUIT_DEBUG
		else:
			print("UNACCEPTED CHARACTER: " + c)
	# if in macro mode, remove the extra `, ` from the end
	if ret[-2:] == ", ":
	    ret = ret[:-1]
	return (loc, ret, quit)

def main():
	scriptList = createList()
	locList = []

	parser = argparse.ArgumentParser(description='Pokemon TCG Script Extractor')
	parser.add_argument('--noauto', action='store_true', help='turns off automatic script parsing')
	parser.add_argument('--error', action='store_true', help='stops execution if an error occurs')
	parser.add_argument('-m', '--movement', action='store_true', help='interprets bytes as a movement sequence rather than a Script')
	parser.add_argument('-r', '--rom', default="baserom.gbc", help='rom file to extract script from')
	parser.add_argument('locations', nargs="+", help='locations to extract from. May be local to bank or global.')
	args = parser.parse_args()
	for locStr in args.locations:
		loc = int(locStr,0)
		if loc > 0x7fff:
			# Must be a global location
			loc -= 0x8000
		locList.append(loc)

	# this is a list of every start location we've read to avoid infinite loops
	exploredList = []

	with open(args.rom, "rb") as file:
	    game_data = file.read()

	auto = not args.noauto
	end = DO_NOT_QUIT
	ignore_broken = not args.error
	while (len(locList) > 0 and end != QUIT_DEBUG):
		locList.sort() # export parts in order somewhat
		loc = locList.pop(0) + 0x8000
		if args.movement:
			end = extractMovement(game_data,loc, args.error)
		else:
			end = printScript(game_data, loc, auto, ignore_broken, scriptList,\
			locList, exploredList)

def printScript(game_data, loc, auto, ignore_broken, scriptList, \
				locList, exploredList):
	if loc in exploredList:
		return
	exploredList.append(loc)
	script = ""
	end = DO_NOT_QUIT
	if game_data[loc] != 0xe7:
		#print("Error: first byte was not start_script")
		print(".ows_" + format(loc,"04x"))
	else:
		
		# TODO this is hacky please don't do this 
		printHeader(loc, "Script_")
		loc += 1
		print("\tstart_script")
	while end == DO_NOT_QUIT:
		loc, outstr, end = decodeLine(scriptList,game_data,loc,ignore_broken,locList)
		outstr = outstr[:-1] # [:-1] strips the newline at the end
		if auto:
			print(outstr)
		else:
			input(outstr)
	warning = ""
	if end == QUIT_CONTINUE_CODE:
		warning = " WARNING: There is probably regular assembly here"

	print("; " + hex(loc) + warning)

	# if the next byte is a ret, print it for the continue_code case
	if game_data[loc] == 0xc9:
		print("\tret")

	return end

def createList(): # this is a func just so all this can go at the bottom
	# name, arg list, is an ender
	return [
	("ScriptCommand_EndScriptLoop1", "", QUIT_CONTINUE_CODE,"end_script_loop"),
	("ScriptCommand_CloseAdvancedTextBox", "", DO_NOT_QUIT,"close_advanced_text_box"),
	("ScriptCommand_PrintTextString", "t", DO_NOT_QUIT,"print_text_string"),
	("Func_ccdc", "t", DO_NOT_QUIT,""),
	("ScriptCommand_AskQuestionJump", "tj", DO_NOT_QUIT,"ask_question_jump"), # more complex behavior too (jumping)
	("ScriptCommand_StartBattle", "bbb", DO_NOT_QUIT,"start_battle"),
	("ScriptCommand_PrintVariableText", "tt", DO_NOT_QUIT,"print_variable_text"),
	("Func_cda8", "bbbb", DO_NOT_QUIT,""),
	("ScriptCommand_PrintTextQuitFully", "t", QUIT_SPECIAL,"print_text_quit_fully"),
	("Func_cdcb", "", DO_NOT_QUIT,""),
	("ScriptCommand_MoveActiveNPCByDirection", "w", DO_NOT_QUIT,"move_active_npc_by_direction"),
	("ScriptCommand_CloseTextBox", "", DO_NOT_QUIT,"close_text_box"),
	("ScriptCommand_GiveBoosterPacks", "bbb", DO_NOT_QUIT,"give_booster_packs"),
	("ScriptCommand_JumpIfCardOwned", "bj", DO_NOT_QUIT,"jump_if_card_owned"),
	("ScriptCommand_JumpIfCardInCollection", "bj", DO_NOT_QUIT,"jump_if_card_in_collection"),
	("ScriptCommand_GiveCard", "b", DO_NOT_QUIT,"give_card"),
	("ScriptCommand_TakeCard", "b", DO_NOT_QUIT,"take_card"),
	("Func_cf53", "w", DO_NOT_QUIT,""), # more complex behavior too (jumping)
	("Func_cf7b", "", DO_NOT_QUIT,""),
	("ScriptCommand_JumpIfEnoughCardsOwned", "wj", DO_NOT_QUIT,"jump_if_enough_cards_owned"), 
	("ScriptCommand_JumpBasedOnFightingClubPupilStatus", "jjjjj", DO_NOT_QUIT,"fight_club_pupil_jump"),
	("Func_cfc6", "b", DO_NOT_QUIT,""),
	("Func_cfd4", "", DO_NOT_QUIT,""),
	("Func_d00b", "", DO_NOT_QUIT,""), # includes something with random and extra data
	("Func_d025", "w", DO_NOT_QUIT,""), # possibly only jumps, still needs args
	("Func_d032", "w", DO_NOT_QUIT,""), # see above
	("Func_d03f", "", DO_NOT_QUIT,""),
	("ScriptCommand_Jump", "j", QUIT_JUMP,"script_jump"),
	("ScriptCommand_TryGiveMedalPCPacks", "", DO_NOT_QUIT,"try_give_medal_pc_packs"),
	("ScriptCommand_SetPlayerDirection", "d", DO_NOT_QUIT,"set_player_direction"),
	("ScriptCommand_MovePlayer", "di", DO_NOT_QUIT,"move_player"),
	("ScriptCommand_ShowCardReceivedScreen", "b", DO_NOT_QUIT,"show_card_received_screen"),
	("ScriptCommand_SetDialogNPC", "b", DO_NOT_QUIT,"set_dialog_npc"),
	("ScriptCommand_SetNextNPCAndScript", "bj", DO_NOT_QUIT,"set_next_npc_and_script"),
	("Func_d095", "bbb", DO_NOT_QUIT,""),
	("Func_d0be", "bb", DO_NOT_QUIT,""),
	("ScriptCommand_DoFrames", "i", DO_NOT_QUIT,"do_frames"),
	("Func_d0d9", "bbw", DO_NOT_QUIT,""), # jumps but still needs args
	("ScriptCommand_JumpIfPlayerCoordsMatch", "iij", DO_NOT_QUIT,"jump_if_player_coords_match"), # jumps but still needs args
	("ScriptCommand_MoveActiveNPC", "m", DO_NOT_QUIT,"move_active_npc"),
	("ScriptCommand_GiveOneOfEachTrainerBooster", "", DO_NOT_QUIT,"give_one_of_each_trainer_booster"),
	("Func_d103", "q", DO_NOT_QUIT,""),
	("Func_d125", "b", DO_NOT_QUIT,""),
	("Func_d135", "b", DO_NOT_QUIT,""),
	("Func_d16b", "b", DO_NOT_QUIT,""),
	("Func_cd4f", "bbb", DO_NOT_QUIT,""),
	("Func_cd94", "q", DO_NOT_QUIT,""),
	("ScriptCommand_MoveWramNPC", "m", DO_NOT_QUIT,"move_wram_npc"),
	("Func_cdd8", "", DO_NOT_QUIT,""),
	("Func_cdf5", "bb", DO_NOT_QUIT,""),
	("Func_d195", "", DO_NOT_QUIT,""),
	("Func_d1ad", "", DO_NOT_QUIT,""),
	("Func_d1b3", "", DO_NOT_QUIT,""),
	("ScriptCommand_QuitScriptFully", "", QUIT_SPECIAL,"quit_script_fully"),
	("Func_d244", "q", DO_NOT_QUIT,""),
	("ScriptCommand_ChooseDeckToDuelAgainstMultichoice", "", DO_NOT_QUIT,"choose_deck_to_duel_against_multichoice"),
	("ScriptCommand_OpenDeckMachine", "b", DO_NOT_QUIT,"open_deck_machine"),
	("ScriptCommand_ChooseStarterDeckMultichoice", "", DO_NOT_QUIT,""),
	("ScriptCommand_EnterMap", "bbood", DO_NOT_QUIT,"enter_map"),
	("ScriptCommand_MoveArbitraryNPC", "bm", DO_NOT_QUIT,"move_arbitrary_npc"),
	("Func_d209", "", DO_NOT_QUIT,""),
	("Func_d38f", "b", DO_NOT_QUIT,""),
	("Func_d396", "b", DO_NOT_QUIT,""),
	("Func_cd76", "", DO_NOT_QUIT,""),
	("Func_d39d", "b", DO_NOT_QUIT,""),
	("Func_d3b9", "", DO_NOT_QUIT,""),
	("ScriptCommand_TryGivePCPack", "b", DO_NOT_QUIT,"try_give_pc_pack"),
	("ScriptCommand_nop", "", DO_NOT_QUIT,"script_nop"),
	("Func_d3d4", "q", DO_NOT_QUIT,""),
	("Func_d3e0", "", DO_NOT_QUIT,""),
	("Func_d3fe", "q", DO_NOT_QUIT,""),
	("Func_d408", "b", DO_NOT_QUIT,""),
	("ScriptCommand_PlaySong", "b", DO_NOT_QUIT,"play_song"),
	("ScriptCommand_PlaySFX", "b", DO_NOT_QUIT,"play_sfx"),
	("ScriptCommand_PauseSong", "", DO_NOT_QUIT,"pause_song"),
	("ScriptCommand_ResumeSong", "", DO_NOT_QUIT,"resume_song"),
	("Func_d41d", "", DO_NOT_QUIT,""),
	("ScriptCommand_WaitForSongToFinish", "", DO_NOT_QUIT,"wait_for_song_to_finish"),
	("Func_d435", "b", DO_NOT_QUIT,""),
	("ScriptCommand_AskQuestionJumpDefaultYes", "tj", DO_NOT_QUIT,"ask_question_jump_default_yes"),
	("ScriptCommand_ShowSamNormalMultichoice", "", DO_NOT_QUIT,"show_sam_normal_multichoice"),
	("ScriptCommand_ShowSamTutorialMultichoice", "", DO_NOT_QUIT,"show_sam_tutorial_multichoice"),
	("Func_d43d", "", DO_NOT_QUIT,""),
	("ScriptCommand_EndScriptLoop2", "", QUIT_CONTINUE_CODE,"end_script_loop_2"),
	("ScriptCommand_EndScriptLoop3", "", QUIT_CONTINUE_CODE,"end_script_loop_3"),
	("ScriptCommand_EndScriptLoop4", "", QUIT_CONTINUE_CODE,"end_script_loop_4"),
	("ScriptCommand_EndScriptLoop5", "", QUIT_CONTINUE_CODE,"end_script_loop_5"),
	("ScriptCommand_EndScriptLoop6", "", QUIT_CONTINUE_CODE,"end_script_loop_6"),
	("ScriptCommand_SetFlagValue", "fb", DO_NOT_QUIT,"script_set_flag_value"),
	("ScriptCommand_JumpIfFlagZero1", "fj", DO_NOT_QUIT,"jump_if_flag_zero_1"),
	("ScriptCommand_JumpIfFlagNonzero1", "fj", DO_NOT_QUIT,"jump_if_flag_nonzero_1"),
	("ScriptCommand_JumpIfFlagEqual", "fbj", DO_NOT_QUIT,"jump_if_flag_equal"),
	("ScriptCommand_JumpIfFlagNotEqual", "fbj", DO_NOT_QUIT,"jump_if_flag_not_equal"),
	("ScriptCommand_JumpIfFlagNotLessThan", "fbj", DO_NOT_QUIT,"jump_if_flag_not_less_than"),
	("ScriptCommand_JumpIfFlagLessThan", "fbj", DO_NOT_QUIT,"jump_if_flag_less_than"),
	("ScriptCommand_MaxOutFlagValue", "f", DO_NOT_QUIT,"max_out_flag_value"),
	("ScriptCommand_ZeroOutFlagValue", "f", DO_NOT_QUIT,"zero_out_flag_value"),
	("ScriptCommand_JumpIfFlagNonzero2", "fj", DO_NOT_QUIT,"jump_if_flag_nonzero_2"),
	("ScriptCommand_JumpIfFlagZero2", "fj", DO_NOT_QUIT,"jump_if_flag_zero_2"),
	("ScriptCommand_IncrementFlagValue", "f", DO_NOT_QUIT,"increment_flag_value"),
	("ScriptCommand_EndScriptLoop7", "q", QUIT_CONTINUE_CODE,"end_script_loop_7"),
	("ScriptCommand_EndScriptLoop8", "q", QUIT_CONTINUE_CODE,"end_script_loop_8"),
	("ScriptCommand_EndScriptLoop9", "q", QUIT_CONTINUE_CODE,"end_script_loop_9"),
	("ScriptCommand_EndScriptLoop10", "q", QUIT_CONTINUE_CODE,"end_script_loop_10")
	]

main()
