// Removed useless files, to speed up build process and make it clearer.
const path = require("path");
const fs = require("fs");
const readline = require("readline");

// cmdid current version
const read_cmdid = "cmdid.csv";
const read_cmdid_output = "cmdid.json";
const read_cmdid_output_gc = "cmdid_gc.json";
const read_cmdid_output_gc_update = "cmdid_gc_update.json";
const read_cmdid_output_gc_nofound = "cmdid_gc_nofound.json";

const file_gc_needed = "gc_needed.json";
const file_gc_needed2 = "gc_needed2.json";

const write_op = "PacketOpcodes.java";

console.log(process.cwd());

//(TODO: add input file)
// folder gc auto-generated proto
const folder_proto_gc_gen =
  "../Grasscutter-Yuuki/src/generated/main/java/emu/grasscutter/net/proto/";

// file PacketOpcodes currently in use
const read_cmdid_gc =
  "../Grasscutter-Yuuki/src/main/java/emu/grasscutter/net/packet/PacketOpcodes.java";

const folder_packet_gc =
  "../Grasscutter-Yuuki/src/main/java/emu/grasscutter/server/packet/";

const folder_gc_scan = "../Grasscutter-Yuuki/src/main/java/emu/grasscutter/";

//const read_cmdid = fs.readFileSync("cmdid.csv");
//const read_packetopcodes = fs.readFileSync("PacketOpcodes.java");

var data = [];
var data_gc = [];

var index_file_gen = 0;
var index_file_cmdid = 0;
var index_cmdid_gc = 0;
var index_cmdid_gc_out = 0;
function check_gen() {
  fs.readdir(folder_proto_gc_gen, function (err, files) {
    //handling error
    if (err) {
      return console.log("Unable to scan directory: " + err);
    }
    files.forEach(function (file) {
      index_file_gen++;
    });
    console.log("file proto gen: " + index_file_gen);
  });
}

// gen json file cmdid
function get_cmdid_json() {
  const inputStreamcmdid = fs.createReadStream(read_cmdid);
  var lineReadercmdid = readline.createInterface({
    input: inputStreamcmdid,
    terminal: false,
  });
  lineReadercmdid.on("line", function (line) {
    var config = line.split(",");
    var subdata = new Object();
    subdata["name"] = config[0];
    subdata["id"] = parseInt(config[1]);
    data.push(subdata);
    index_file_cmdid++;
  });
  lineReadercmdid.on("close", function () {
    console.log("found cmd id " + index_file_cmdid);
    save_json(data, read_cmdid_output);
  });
}

// create cmdid from gc which comes from PacketOpcodes
function get_cmdid_gc() {
  const inputStreamcmdid = fs.createReadStream(read_cmdid_gc);
  var lineReadercmdid = readline.createInterface({
    input: inputStreamcmdid,
    terminal: false,
  });
  lineReadercmdid.on("line", function (line) {
    var config = line.split(" = ");
    var name = config[0];
    var id = parseInt(config[1]);
    if (name.includes("public static final int")) {
      name = name.replace("\tpublic static final int ", "");
      // skip 0 ?
      if (id == 0) {
        return;
      }
      var subdata = new Object();
      subdata["name"] = name;
      subdata["id"] = id;
      data_gc.push(subdata);
      //console.log(name);
      index_cmdid_gc++;
    } else {
      index_cmdid_gc_out++;
    }
  });
  lineReadercmdid.on("close", function () {
    console.log(
      "found cmd id " + index_cmdid_gc + " | no need " + index_cmdid_gc_out
    );
    save_json(data_gc, read_cmdid_output_gc);
  });
}

var found_cmdid_new = 0;
var nofound_cmdid_new = 0;
var rename_name_cmdid = 0;
var noneed_rename_name_cmdid = 0;
var data_gc_cmdid_nofound = [];

var check_dunp_id = [];
function update_cmdid_gc() {
  const cmd_last = fs.readFileSync(read_cmdid_output);
  const cmd_old = fs.readFileSync(read_cmdid_output_gc);
  const json_cmdid_last = JSON.parse(cmd_last);
  const json_cmdid_old = JSON.parse(cmd_old);
  json_cmdid_old.forEach(function (s, index) {
    var id = s.id;
    var name = s.name.trim();

    var found_id = json_cmdid_last.find((j) => j.id == id);
    if (found_id) {
      found_cmdid_new++;
      if (name == found_id.name) {
        noneed_rename_name_cmdid++;
      } else {
        rename_name_cmdid++;
        //console.log("Wow rename -> ID: "+id+" | Name: "+name+" > "+found_id.name);
        // rename json_cmdid_old
        s.replace = s.name;
        s.name = found_id.name;
      }
    } else {
      console.log("Wow nofound -> ID: " + id + " | Name: " + name);
      data_gc_cmdid_nofound.push(s);
      nofound_cmdid_new++;
    }

    // find dump by id
    var found_id = check_dunp_id.find((j) => j.id == id);
    if (found_id) {
      console.log(
        "Wow dup -> ID: " +
          id +
          " (ADD " +
          found_id.id +
          ") | Name Remove: " +
          name +
          " (ADD " +
          found_id.name +
          ")"
      );
      // remove bad
      json_cmdid_old.splice(index, 1);
    } else {
      check_dunp_id.push(s);
    }
  });

  check_dunp_id = []; // clear

  // I don't know why this happened but make sure to check again
  var check_dunp_name = [];
  json_cmdid_old.forEach(function (s, index) {
    var id = s.id;
    var name = s.name.trim();

    var found_name = check_dunp_name.find((j) => j.name === name);
    if (found_name) {
      console.log(
        "Wow dup -> ID: " +
          id +
          " (ADD " +
          found_name.id +
          ") | Name Remove: " +
          name +
          " (ADD " +
          found_name.name +
          ")"
      );
      // remove bad
      json_cmdid_old.splice(index, 1);
    } else {
      check_dunp_name.push(s);
    }

    var found_id = json_cmdid_last.find((j) => j.id == id);
    if (found_id) {
      if (name != found_id.name) {
        console.log(
          "Wow why? -> ID: " + id + " | Name: " + name + " > " + found_id.name
        );
        s.name = found_id.name;
      }
    }
  });

  check_dunp_name = []; // clear

  console.log(
    "found " +
      found_cmdid_new +
      " | no found " +
      nofound_cmdid_new +
      " | rename " +
      rename_name_cmdid +
      " | noneed rename " +
      noneed_rename_name_cmdid
  );
  save_json(json_cmdid_old, read_cmdid_output_gc_update);
  save_json(data_gc_cmdid_nofound, read_cmdid_output_gc_nofound);
}

function read_json(file) {
  return JSON.parse(fs.readFileSync(file));
}

// save json
function save_json(raw, file) {
  var j = JSON.stringify(raw, null, 4);
  save(j, file);
}

function save(raw, file) {
  fs.writeFile(file, raw, "utf8", function (err) {
    if (err) {
      console.log("An error occured while writing to File.");
      return console.log(err);
    }
    console.log("File has been saved: " + file);
  });
}

function cmdid_to_op() {
  let melon =
    "\
    package emu.grasscutter.net.packet;\
    \n\
    \npublic class PacketOpcodes {\
        \n// Empty\
        \npublic static final int NONE = 0;\
        \n\
        \n// Opcodes\
    ";

  const cmdidfix_raw = fs.readFileSync(read_cmdid_output_gc_update);
  const json_cmdidfix_raw = JSON.parse(cmdidfix_raw);
  json_cmdidfix_raw.forEach(function (s) {
    melon += "\npublic static final int " + s.name + " = " + s.id + ";";
  });

  melon += "\n}";
  save(melon, write_op); // use "npx prettier --write PacketOpcodes.java" for better Formatter
}

var index_file_packet = 0;
var index_file_packet_found = 0;
var index_file_packet_nofound = 0;
var index_file_packet_rename = 0;
var index_file_packet_norename = 0;
var index_file_packet_renamemulti = 0;
var file_gc_need = [];
function fix_packet(saveit = false) {
  const files = getAllFiles(folder_packet_gc);

  const json_cmdid_last = read_json(read_cmdid_output);
  const json_cmdid_old = read_json(read_cmdid_output_gc);
  const json_cmdidfix_raw = read_json(read_cmdid_output_gc_update);

  files.forEach(function (file) {
    //var f = path + "/" + file;
    const read = fs.readFileSync(file);
    var real = read.toString();

    var name = getPacketOpcodes(real);
    if (!name) {
      console.log("no found");
      return;
    }

    if (name === "NONE") {
      return;
    }

    var subdata = new Object();
    subdata["name"] = name;
    file_gc_need.push(subdata);

    //var name = c[1];

    //console.log(r);

    var found_old = json_cmdid_old.find((j) => j.name === name);
    if (found_old) {
      //console.log(found_old);
      index_file_packet_found++;
      var found_new = json_cmdid_last.find((j) => j.id == found_old.id);
      if (found_new) {
        if (found_new.name != found_old.name) {
          index_file_packet_rename++;
          console.log(
            "Found need rename: " + found_old.name + " > " + found_new.name
          );

          // rename all
          json_cmdidfix_raw.forEach(function (s) {
            var r = s.replace;
            if (r) {
              // var notify = HomeNewUnlockedBgmIdListNotify.Unk2700_MEBFPBDNPGO_ServerNotify
              // var notify = Unk2700MEBFPBDNPGOServerNotify.HomeNewUnlockedBgmIdListNotify

              // Unk2700MEBFPBDNPGOServerNotify to HomeNewUnlockedBgmIdListNotifyOuterClass
              // Unk2700OGHMHELMBNNServerRsp to  HomeChangeBgmRspOuterClass

              // - Need More Auto like -
              // addUnk2700ELJPLMIHNIP to addNewUnlockedBgmIdList (this should be found inside gen proto)
              // setUnk2700BJHAMKKECEI to setBgmId
              if (r.match("Unk")) {
                //console.log(r);
                var x = r.split("_");
                var tr = x.join("");
                var realneed = tr;

                if (tr.match("ServerNotify")) {
                  //console.log("found: "+tr);
                  let re = new RegExp(`${tr}`, "g");
                  tr = tr.replace(re, `${s.name}OuterClass`);
                  //console.log("found: " + tr);
                } else if (tr.match("ServerRsp")) {
                  let re = new RegExp(`${tr}`, "g");
                  tr = tr.replace(re, `${s.name}OuterClass`);
                } else {
                  let re = new RegExp(`${tr}`, "g");
                  tr = tr.replace(re, s.name);
                  //console.log("found: " + tr);
                }
                let re = new RegExp(`${realneed}`, "g");
                real = real.replace(re, tr);
              }
              let re = new RegExp(`${r}`, "g");
              real = real.replace(re, s.name);
            }
          });

          // simpel rename
          //let re = new RegExp(`${found_old.name}`, "g");
          //real = real.replace(re, found_new.name);

          //console.log(real);
          if (saveit) {
            save(real, f);
          }
        }
      } else {
        index_file_packet_norename++;
        //console.log("Same name "+name);
      }
    } else {
      index_file_packet_nofound++;
      console.log("No found " + name);
    }

    //return;
    index_file_packet++;
  });
  save_json(file_gc_need, file_gc_needed); // This only applies to PacketOpcodes
  console.log(
    "Index file: " +
      index_file_packet +
      " | found " +
      index_file_packet_found +
      " | No found " +
      index_file_packet_nofound +
      " | Rename " +
      index_file_packet_rename +
      " | NoRename " +
      index_file_packet_norename
  );
}

function isBlank(str) {
  return !!!str || /^\s*$/.test(str);
}

function getAllFiles(dirPath, arrayOfFiles) {
  files = fs.readdirSync(dirPath);

  arrayOfFiles = arrayOfFiles || [];

  files.forEach(function (file) {
    if (fs.statSync(dirPath + "/" + file).isDirectory()) {
      arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles);
    } else {
      arrayOfFiles.push(path.join(__dirname, dirPath, "/", file));
    }
  });

  return arrayOfFiles;
}

function getPacketOpcodes(raw) {
  var r = raw.match(/\(.*?\)/g).map((x) => x.replace(/[()]/g, ""));
  var name;
  r.forEach(function (s, index) {
    if (s.match("PacketOpcodes.")) {
      name = s.split("PacketOpcodes.")[1];
      if (name.match(",")) {
        name = name.split(",")[0];
      }
      return;
    }
  });
  return name;
}

// C:\Users\Administrator\Desktop\Projek\Docker\GS\gs\Grasscutter-Yuuki\src\main\java\emu\grasscutter

var file_toclean = [];
var file_toaddmore = [];
var found_noclean = 0;
var found_needclean = 0;
var found_maybe_related = 0;
function clean_proto_gen() {
  //const files = getAllFiles(folder_proto_gc_gen);
  const files = getAllFiles("./proto");

  const json_gc_needed = read_json(file_gc_needed);
  const json_gc_needed2 = read_json(file_gc_needed2);

  const json_gc_now = read_json(read_cmdid_output_gc);
  const json_gc_update = read_json(read_cmdid_output_gc_update);

  //AbilityInvokeArgument
  console.log(
    "File proto: " + files.length + " | Need " + json_gc_needed.length
  );

  const regex = /\import "(.*?)\.proto"/g;

  // find all file import
  files.forEach(function (file) {
    // read file
    const read = fs.readFileSync(file);
    var rd = read.toString();

    // find import
    while ((m = regex.exec(rd)) !== null) {
      // This is necessary to avoid infinite loops with zero-width matches
      if (m.index === regex.lastIndex) {
        regex.lastIndex++;
      }
      // The result can be accessed through the `m`-variable.
      m.forEach((match, groupIndex) => {
        // only index 1 grup
        if (groupIndex == 1) {
          var found_rt = file_toaddmore.find((j) => j.name === match);
          if (found_rt) {
            //console.log(`Skip ${match}`);
          } else {
            var subdata = new Object();
            subdata["name"] = match;
            file_toaddmore.push(subdata);
          }
        }
      });
    }
  });

  // last try
  files.forEach(function (file) {
    var todoremove = false;

    // main file
    var found = json_gc_needed.find((j) => file.match(j.name));
    if (found) {
      // skip
    } else {
      var found1 = file_toaddmore.find((j) => file.match(j.name)); // find import
      if (found1) {
        // skip
        //console.log("Files sub are required: "+file);
        found_maybe_related++;
      } else {
        if (file.match("Retcode")) {
          console.log("found");
        } else {
          var found2 = json_gc_needed2.find((j) => file.match(j.name)); // find miss scan
          if (found2) {
            // skip
            found_maybe_related++;
          } else {
            todoremove = true;
          }
        }
      }
    }

    if (todoremove) {
      found_needclean++;
      try {
        fs.unlinkSync(file);
        //file removed
      } catch (err) {
        console.error(err);
      }
    } else {
      found_noclean++;
    }
  });

  //console.log(file_toaddmore);

  console.log(
    "No clean: " +
      found_noclean +
      " | Need to clean: " +
      found_needclean +
      " | Related " +
      found_maybe_related
  );
}

var g_todump = [];
function scan_gc() {
  const files = getAllFiles(folder_gc_scan);

  console.log("index file: " + files.length);

  const regex = /import emu.grasscutter.net.proto.(.*?);/g;

  files.forEach(function (file) {
    // read file
    const read = fs.readFileSync(file);
    var rd = read.toString();
    // find import
    while ((m = regex.exec(rd)) !== null) {
      // This is necessary to avoid infinite loops with zero-width matches
      if (m.index === regex.lastIndex) {
        regex.lastIndex++;
      }
      // The result can be accessed through the `m`-variable.
      m.forEach((match, groupIndex) => {
        // only index 1 grup
        if (groupIndex == 1) {
          if (match === "*") {
            return;
          }
          if (match.match(".")) {
            match = match.split(".")[0];
          }
          match = match.replace("OuterClass", "");
          var found_rt = g_todump.find((j) => j.name === match);
          if (found_rt) {
            //found_maybe_related++;
            //console.log(`Skip ${match}`);
          } else {
            var subdata = new Object();
            subdata["name"] = match;
            g_todump.push(subdata);
          }
        }
      });
    }
  });

  save_json(g_todump, file_gc_needed2); // This only applies to PacketOpcodes

  //console.log(g_todump);
}

//scan_gc();
clean_proto_gen();
//fix_packet(false);
//cmdid_to_op();
//update_cmdid_gc();
//get_cmdid_gc();
//get_cmdid_json();
//check_gen();
