// Script to export decompiled C code from Ghidra
// @category _Reman3
// @menupath Reman3.Decompile All

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Arrays;
import java.util.Dictionary;

import ghidra.app.decompiler.ClangFieldToken;
import ghidra.app.decompiler.ClangLine;
import ghidra.app.decompiler.ClangSyntaxToken;
import ghidra.app.decompiler.ClangToken;
import ghidra.app.decompiler.DecompileResults;
import ghidra.app.decompiler.PrettyPrinter;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.AbstractStringDataType;
import ghidra.program.model.data.Array;
import ghidra.program.model.data.ArrayDataType;
import ghidra.program.model.data.BitFieldDataType;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.EnumDataType;
import ghidra.program.model.data.PointerDataType;
import ghidra.program.model.data.ProgramBasedDataTypeManager;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.TypedefDataType;
import ghidra.program.model.listing.Function;
import ghidra.program.model.listing.VariableStorage;
import ghidra.program.model.pcode.HighFunction;
import ghidra.program.model.pcode.HighSymbol;
import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode;
import ghidra.program.model.symbol.Symbol;
import ghidra.util.task.TaskMonitor;
import re3lib.*;

public class Decompile extends GhidraScript {
 

  // void headerGuardPre(PrintWriter writer, String tag) {
  //   writer.println("#ifndef GH_GENERATED_" + tag + "_H");
  //   writer.println("#define GH_GENERATED_" + tag + "_H");
  //   writer.println();
  // }

  // void headerGuardPost(PrintWriter writer, String tag) {
  //   writer.println("#endif // GH_GENERATED_" + tag + "_H");
  // }

  // void dumpGlobals(Hashtable<String, GlobalRec> globalSymbols) throws Exception {
  //   File globalSymbolsListH = new File(RecompileConfig.INSTANCE.outputDir, "gh_global.h");
  //   PrintWriter hwriter = new PrintWriter(globalSymbolsListH, "UTF-8");
  //   hwriter.println("// AUTO-GENERATED FILE ");
  //   headerGuardPre(hwriter, "GLOBALS");
  //   hwriter.println("#include <r3/binder/global.h>");
  //   hwriter.println();

  //   File globalSymbolsListC = new File(RecompileConfig.INSTANCE.outputDir, "gh_global.cxx");
  //   PrintWriter cwriter = new PrintWriter(globalSymbolsListC, "UTF-8");
  //   cwriter.println("// AUTO-GENERATED FILE ");
  //   cwriter.println("#include <r3/binder/global.h>");
  //   hwriter.println();

  //   for (GlobalRec sym : globalSymbols.values()) {
  //     HighSymbol highSym = sym.highSymbol;
  //     DataType dt = highSym.getDataType();
  //     String dataType = dt.getDisplayName();
  //     String sanitizedName = sanitizeFunctionName(highSym.getName());
  //     String name = highSym.getName();
  //     if (!sanitizedName.equals(name)) {
  //       println("Invalid global symbol name: " + name);
  //       name = sanitizedName;
  //     }
  //     Address addr = sym.address;
  //     // println("Symbol: " + symbol + " Addr: " + addr + " Size:" + symSize + " " +
  //     // storage.getSerializationString());
  //     try {
  //       String initBlk = " = ";
  //       boolean fullyDefinedType = false;
  //       if (dt instanceof AbstractStringDataType) {
  //         AbstractStringDataType sdt = (AbstractStringDataType) dt;
  //         dataType = "const char*";
  //         // String type
  //         initBlk += "\"" + escapeCString(readCString(addr, 2048)) + "\"";
  //         fullyDefinedType = true;
  //       } else if (dt instanceof PointerDataType) {
  //         PointerDataType pdt = (PointerDataType) dt;
  //         DataType baseType = pdt.getDataType();
  //         dataType = baseType.getDisplayName() + "*";
  //         initBlk += "(" + dataType + ")&GH_MEM(0x" + addr + ")";
  //         fullyDefinedType = true;
  //       }
  //       if (fullyDefinedType) {
  //         hwriter.println("extern " + dataType + " " + name + "; // " + addr);
  //         cwriter.println(dataType + " " + name + initBlk + "; // " + addr);
  //       } else {
  //         if (dt instanceof Array) {
  //           // println("Array: " + dt.getDisplayName() + " - " + addr + " - " +
  //           // dt.getClass().getSimpleName());
  //           Array adt = (Array) dt;
  //           DataType baseType = adt.getDataType();
  //           hwriter.println(
  //               "extern " + baseType.getDisplayName() + "(&" + name + ")[" + adt.getNumElements() + "]; // " + addr);
  //           cwriter.println(
  //               baseType.getDisplayName() + "(&" + name + ")[" + adt.getNumElements() + "] = *reinterpret_cast<"
  //                   + baseType.getDisplayName() + "(*)[" + adt.getNumElements() + "]>(GH_MEM(0x" + addr + "));");
  //         } else {
  //           String refTypeStr = dt.getDisplayName() + "&";
  //           hwriter.println("extern " + refTypeStr + " " + name + "; // " + addr);
  //           cwriter.println(dataType + " " + name + "= (" + refTypeStr + ") GH_MEM(0x" + addr + ");");
  //         }
  //       }
  //     } catch (Exception e) {
  //       println("Error processing global symbol: " + e);
  //       println("Symbol: " + highSym.getName() + " - " + addr + " - "
  //           + highSym.getHighFunction().getFunction().getName());
  //     }
  //   }

  //   headerGuardPost(hwriter, "GLOBALS");
  //   hwriter.close();
  //   cwriter.close();
  // }

  void decompileAll(List<Function> functions) throws Exception {
    // Hashtable<String, GlobalRec> globalSymbols = new Hashtable<>();

    // for (Function function : functions) {
    //   decompileFunction(globalSymbols, function);
    // }

    // dumpStructureTypes();
    // dumpGlobals(globalSymbols);
  }

  @Override
  public void run() throws Exception {
    if (currentProgram == null) {
      return;
    }

    RecompileConfig.INSTANCE = new RecompileConfig(this);

    if (!new File(RecompileConfig.INSTANCE.outputDir).exists()) {
      throw new Exception("Output directory does not exist: " + RecompileConfig.INSTANCE.outputDir);
    }

    // Make sure to create output folders
    RecompileConfig.INSTANCE.dirDecompFix.mkdirs();
    RecompileConfig.INSTANCE.dirDecompAuto.mkdirs();
    RecompileConfig.INSTANCE.dirDecompRef.mkdirs();

    // buildFunctionBlacklist();

    List<Function> functions = new ArrayList<>();

    Iterator<Function> functionsIt = currentProgram.getFunctionManager().getFunctions(true).iterator();
    while (functionsIt.hasNext()) {
      Function function = functionsIt.next();
      // if (!shouldDecompileFunction(function)) {
      //   continue;
      // }

      functions.add(function);
    }

    decompileAll(functions);
  }

  String sanitizeFunctionName(String name) {
    return name.replaceAll("[^a-zA-Z0-9_]", "_");
  }
}
