/* CreateSkeleton
 * Creates a skeleton PlayStation dev project based on the output of SymDumpTE
 * or another source of the appropriate JSON data
 * Copyright 2019 Ben Lincoln
 * https://www.beneaththewaves.net/
 * 
 * This file is part of CreateSkeleton.
 * 
 * CreateSkeleton is free software: you can redistribute it and/or modify
 * it under the terms of version 3 of the GNU General Public License as published by
 * the Free Software Foundation.
 * 
 * CreateSkeleton 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 CreateSkeleton (in the file LICENSE.txt).  
 * If not, see <http://www.gnu.org/licenses/>.
 */
// %PROJECT_NAME%: Machine-generated symbol-renaming script (not currently used!)

import java.io.*;
import java.util.*;
import java.nio.charset.StandardCharsets;

import ghidra.app.plugin.core.script.Ingredient;
import ghidra.app.plugin.core.script.IngredientDescription;
import ghidra.app.script.GatherParamPanel;
import ghidra.app.script.GhidraScript;
import ghidra.program.flatapi.FlatProgramAPI;
import ghidra.program.model.address.*;
import ghidra.program.model.block.*;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.*;
import ghidra.program.model.lang.*;
import ghidra.program.model.symbol.*;

import utilities.util.FileUtilities;

public class TDRRenameProblematicSymbols extends GhidraScript implements Ingredient {
	
	@Override
	public void run() throws Exception {
		IngredientDescription[] ingredients = getIngredientDescriptions();
		for (IngredientDescription ingredient : ingredients) {
			state.addParameter(ingredient.getID(), ingredient.getLabel(), ingredient.getType(),
				ingredient.getDefaultValue());
		}
		if (!state.displayParameterGatherer("Script Options")) {
			return;
		}
		long minAddress = Long.decode(String.valueOf(state.getEnvironmentVar("MinimumAddress")));
		long maxAddress = Long.decode(String.valueOf(state.getEnvironmentVar("MaximumAddress")));

		SymbolTable symbolTable = currentProgram.getSymbolTable();
		SymbolIterator si = symbolTable.getAllSymbols(true);

		List<String> existingSymbolNames = new ArrayList<String>();

		while (si.hasNext()) {

			if (monitor.isCancelled())
			{
				break;
			}

			Symbol currentSymbol = si.next();
			
			long symbolAddressOffset = currentSymbol.getAddress().getOffset();
			
			if ((symbolAddressOffset >= minAddress) && (symbolAddressOffset <= maxAddress))
			{
				String currentSymbolName = currentSymbol.getName();
				String newSymbolName = getSymbolNameWithoutBadChars(currentSymbolName);
				
				if (!currentSymbolName.equals(newSymbolName))
				{
					if (existingSymbolNames.contains(newSymbolName))
					{
						newSymbolName = newSymbolName + "_" + longAsHexString(symbolAddressOffset);
					}
					println("Renaming symbol '" + currentSymbolName + "' @" + longAsHexString(symbolAddressOffset) + " to '" + newSymbolName + "'");
					existingSymbolNames.add(newSymbolName);
					//currentSymbol.setName(newSymbolName);
				}
			}
		}
		
		writer.close();
	}
	
	protected String longAsHexString(long addr)
	{
		return String.format("0x%08X", addr);
	}
	
	protected String getSymbolNameWithoutBadChars(String unescapedString)
	{
		StringBuffer result = new StringBuffer();
		byte[] stringBytes = unescapedString.getBytes(StandardCharsets.US_ASCII);
		Byte[] badChars = new Byte[]
		{
			0x21,	// !
			0x22,	// "
			0x23,	// #
			0x24,	// $
			0x25,	// %
			0x26,	// &
			0x27,	// '
			0x28,	// (
			0x29,	// )
			0x2A,	// *
			0x2B,	// +
			0x2C,	// ,
			//0x2E,	// .
			0x2F,	// /
			0x3A,	// :
			0x3B,	// ;
			0x3C,	// <
			0x3D,	// =
			0x3E,	// >
			0x3F,	// ?
			0x40,	// @
			0x5B,	// [
			0x5C,	// backslash
			0x5D,	// ]
			0x5E,	// ^
			0x60,	// `
		};
		List<Byte> badByteList = Arrays.asList(badChars);
		
		for (int i = 0; i < stringBytes.length; i++)
		{
			boolean handled = false;
			if ((stringBytes[i] < 0x21) || (stringBytes[i] > 0x7A))
			{
				handled = true;
			}
			if ((!handled) && (badByteList.contains(stringBytes[i])))
			{
				handled = true;
			}
			if ((!handled) && (stringBytes[i] == 0x2E))
			{
				result.append("_");
				handled = true;
			}
		
			if (!handled)
			{
				result.append(Character.toString(stringBytes[i]));
			}
		}
		
		return result.toString();
	}

	@Override
	public IngredientDescription[] getIngredientDescriptions()
	{
		// note: as of this writing, trying to use GatherParamPanel.ADDRESS results in a null pointer exception when reading the value
		IngredientDescription[] retVal = new IngredientDescription[]
		{
			new IngredientDescription("MinimumAddress", "Minimum Address for Initial Pass", GatherParamPanel.STRING, "0x80000000"),
			new IngredientDescription("MaximumAddress", "Maximum Address for Initial Pass", GatherParamPanel.STRING, "0x9FFFFFFF"),
		};
		return retVal;
	}
	
}
