package re3lib;

import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import ghidra.app.script.GhidraScript;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataType;
import ghidra.program.model.data.EnumDataType;
import ghidra.program.model.data.ProgramBasedDataTypeManager;
import ghidra.program.model.data.Structure;
import ghidra.program.model.data.TypeDef;
import ghidra.program.model.data.TypedefDataType;
import ghidra.program.model.listing.Program;

public class TypeDumper {
  Program currentProgram;
  GhidraScript script;

  public TypeDumper(GhidraScript script) {
    this.script = script;
    currentProgram = script.getCurrentProgram();
    RecompileConfig.INSTANCE = new RecompileConfig(script);
  }

  public void run() throws Exception {
    ProgramBasedDataTypeManager dtm = currentProgram.getDataTypeManager();

    HashSet<String> typeBlacklist = Utils.loadSimpleBlacklist(RecompileConfig.INSTANCE.typeBlacklistPath);
    HashSet<String> categoryPathBlacklist = Utils
        .loadSimpleBlacklist(RecompileConfig.INSTANCE.categoryPathBlacklistPath);

    if (typeBlacklist == null) {
      script.println("Building struct blacklist from existing data types");
      typeBlacklist = new HashSet<>();
      Iterator<DataType> it = dtm.getAllDataTypes();
      while (it.hasNext()) {
        DataType dt = it.next();
        if (dt instanceof Structure || dt instanceof TypedefDataType) {
          typeBlacklist.add(dt.getDisplayName());
        }
      }
      Utils.saveStructBlacklist(typeBlacklist, RecompileConfig.INSTANCE.typeBlacklistPath);
    }

    List<DataType> filteredTypes = new ArrayList<>();

    // Iterator<DataType> compIt = dtm.getAllDataTypes();
    // while (compIt.hasNext()) {
    // DataType comp = compIt.next();
    // // script.println("Found: " + comp.getDisplayName() + " - " +
    // // comp.getClass().getSimpleName());

    // if (comp instanceof TypeDef) {
    // if (comp.getDisplayName().startsWith("FIL_")) {
    // script.println("Found: " + comp.getDisplayName() + " - " + comp.getName() + "
    // - " + comp.getClass().getSimpleName());
    // }
    // }

    // // if (comp.getName() == "FIL_tdstConcatFile") {
    // // // script.println("Found: " + dt.getDisplayName() + " - " +
    // // // dt.getClass().getSimpleName());
    // // throw new Exception("Found: " + comp.getDisplayName() + " - " +
    // comp.getClass().getSimpleName());
    // // }
    // }

    Iterator<DataType> it = dtm.getAllDataTypes();
    while (it.hasNext()) {
      DataType dt = it.next();
      if (typeBlacklist.contains(dt.getDisplayName()))
        continue;
      CategoryPath catPath = dt.getCategoryPath();
      if (catPath.getPathElements().length > 0 && categoryPathBlacklist.contains(catPath.getPathElements()[0]))
        continue;

      // script.println("Type: " + dt.getDisplayName() + " - CatPath: " + dt.getCategoryPath());

      // if (dt.getName().equals("ImageBaseOffset32"))
      //   throw new Exception("Found: " + dt.getDisplayName() + " - " + catPath.getPathElements()[0] + " - " + dt.getClass().getSimpleName());

      if (dt instanceof Structure || dt instanceof TypeDef || dt instanceof EnumDataType) {
        // script.println("Adding: " + dt.getDisplayName() + " - " +
        // dt.getClass().getSimpleName());
        filteredTypes.add(dt);
      }
    }

    try (PrintWriter writer = new PrintWriter(new File(RecompileConfig.INSTANCE.outputDir, "gh_types.h"),
        "UTF-8")) {
      Utils.headerGuardPre(writer, "STRUCTS");
      writer.println("// AUTO-GENERATED FILE ");
      writer.println("#include <r3/binders/type.h>");

      DataTypeWriter dtw = new DataTypeWriter(dtm, writer);
      dtw.blacklistedTypes = typeBlacklist;
      dtw.write(filteredTypes, script.getMonitor());

      Utils.headerGuardPost(writer, "STRUCTS");
    }
  }
}
