// === CONFIGURABLE VARIABLES

const bpfoldername = "Reeks2Missie6";
const isStoreVersion = true;

const resource_pack_name = "Reeks2Missie6RP";
const resource_pack_description = "De resource pack voor Reeks 2 Missie 6";
const resource_pack_authors = ["Me and my cat"];

const behavior_pack_name = "Reeks2Missie6BP";
const behavior_pack_description = "De behavior pack voor Reeks 2 Missie 6";
const behavior_pack_authors = ["Me and my cat"];

// === Optional variables

const exportWorldFolderPath = "";

// === END CONFIGURABLE VARIABLES

const gulp = require("gulp");
const ts = require("gulp-typescript");
const del = require("del");
const os = require("os");
const spawn = require("child_process").spawn;
const sourcemaps = require("gulp-sourcemaps");
const zip = require("gulp-zip");
const fs = require("fs");
const rename = require("gulp-rename");
const crypto = require("crypto");

var readLineSync = import("readline-sync");
var NBT = import("nbtify");

const worldsFolderName = "minecraftWorlds";

const regularVersionMojangRoot = os.homedir() + "/appdata/Roaming/Minecraft Education Edition/games/com.mojang/";
const storeVersionMojangRoot =
  os.homedir() +
  "/AppData/Local/Packages/Microsoft.MinecraftEducationEdition_8wekyb3d8bbwe/LocalState/games/com.mojang/";

const mcdir = isStoreVersion ? storeVersionMojangRoot : regularVersionMojangRoot;

function clean_build(callbackFunction) {
  del(["build/behavior_packs/", "build/resource_packs/"]).then(
    (value) => {
      callbackFunction(); // success
    },
    (reason) => {
      callbackFunction(); // error
    }
  );
}

function clean_export(cb) {
  del(["export/**/**"]).then(
    (value) => {
      cb(); // success
    },
    (reason) => {
      cb(); // error
    }
  );
}

function copy_behavior_packs() {
  return gulp.src(["behavior_packs/**/*"]).pipe(gulp.dest("build/behavior_packs"));
}

function copy_resource_packs() {
  return gulp.src(["resource_packs/**/*"]).pipe(gulp.dest("build/resource_packs"));
}

const copy_content = gulp.parallel(copy_behavior_packs, copy_resource_packs);

function compile_scripts() {
  return gulp
    .src("scripts/**/*.ts")
    .pipe(sourcemaps.init())
    .pipe(
      ts({
        module: "es2020",
        moduleResolution: "node",
        lib: ["es2020", "dom"],
        strict: true,
        target: "es2020",
        noImplicitAny: true,
      })
    )
    .pipe(
      sourcemaps.write("../../_" + bpfoldername + "Debug", {
        destPath: bpfoldername + "/scripts/",
        sourceRoot: "./../../../scripts/",
      })
    )
    .pipe(gulp.dest("build/behavior_packs/scripts"));
}

const build = gulp.series(clean_build, copy_content, compile_scripts);

function clean_localmc(callbackFunction) {
  if (!bpfoldername || !bpfoldername.length || bpfoldername.length < 2) {
    console.log("No bpfoldername specified.");
    callbackFunction();
    return;
  }

  del([mcdir + "development_behavior_packs/" + bpfoldername, mcdir + "development_resource_packs/" + bpfoldername], {
    force: true,
  }).then(
    (value) => {
      callbackFunction(); // Success
    },
    (reason) => {
      callbackFunction(); // Error
    }
  );
}

function deploy_localmc_behavior_packs() {
  console.log("Deploying to '" + mcdir + "development_behavior_packs/" + bpfoldername + "'");
  return gulp.src(["build/behavior_packs/**/*"]).pipe(gulp.dest(mcdir + "development_behavior_packs/" + bpfoldername));
}

function deploy_localmc_resource_packs() {
  console.log("Deploying to '" + mcdir + "development_resource_packs/" + bpfoldername + "'");

  return gulp.src(["build/resource_packs/**/*"]).pipe(gulp.dest(mcdir + "development_resource_packs/" + bpfoldername));
}

function getTargetWorldPath() {
  return mcdir + worldsFolderName + "/" + activeWorldFolderName;
}

function getTargetConfigPath() {
  return mcdir + "config";
}

function getTargetWorldBackupPath() {
  return "backups/worlds/" + activeWorldFolderName;
}

function getDevConfigPath() {
  return "config";
}

function getDevWorldPath() {
  return "worlds/default";
}

function getDevWorldBackupPath() {
  return "backups/worlds/devdefault";
}

function clean_localmc_world(callbackFunction) {
  console.log("Removing '" + getTargetWorldPath() + "'");

  del([getTargetWorldPath()], {
    force: true,
  }).then(
    (value) => {
      callbackFunction(); // Success
    },
    (reason) => {
      callbackFunction(); // Error
    }
  );
}

function clean_localmc_config(callbackFunction) {
  console.log("Removing '" + getTargetConfigPath() + "'");

  del([getTargetConfigPath()], {
    force: true,
  }).then(
    (value) => {
      callbackFunction(); // Success
    },
    (reason) => {
      callbackFunction(); // Error
    }
  );
}

function clean_dev_world(callbackFunction) {
  console.log("Removing '" + getDevWorldPath() + "'");

  del([getDevWorldPath()], {
    force: true,
  }).then(
    (value) => {
      callbackFunction(); // Success
    },
    (reason) => {
      callbackFunction(); // Error
    }
  );
}

function clean_localmc_world_backup(callbackFunction) {
  console.log("Removing backup'" + getTargetWorldBackupPath() + "'");

  del([getTargetWorldBackupPath()], {
    force: true,
  }).then(
    (value) => {
      callbackFunction(); // Success
    },
    (reason) => {
      callbackFunction(); // Error
    }
  );
}

function clean_dev_world_backup(callbackFunction) {
  console.log("Removing backup'" + getDevWorldBackupPath() + "'");

  del([getTargetWorldBackupPath()], {
    force: true,
  }).then(
    (value) => {
      callbackFunction(); // Success
    },
    (reason) => {
      callbackFunction(); // Error
    }
  );
}

function backup_dev_world() {
  console.log("Copying world '" + getDevWorldPath() + "' to '" + getDevWorldBackupPath() + "'");
  return gulp
    .src([getTargetWorldPath() + "/**/*"])
    .pipe(gulp.dest(getDevWorldBackupPath() + "/worlds/" + activeWorldFolderName));
}

function deploy_localmc_config() {
  console.log("Copying world 'config/' to '" + getTargetConfigPath() + "'");
  return gulp.src([getDevConfigPath() + "/**/*"]).pipe(gulp.dest(getTargetConfigPath()));
}

function deploy_localmc_world() {
  console.log("Copying world 'worlds/default/' to '" + getTargetWorldPath() + "'");
  return gulp.src([getDevWorldPath() + "/**/*"]).pipe(gulp.dest(getTargetWorldPath()));
}

function ingest_localmc_world() {
  console.log("Ingesting world '" + getTargetWorldPath() + "' to '" + getDevWorldPath() + "'");
  return gulp.src([getTargetWorldPath() + "/**/*"]).pipe(gulp.dest(getDevWorldPath()));
}

function backup_localmc_world() {
  console.log("Copying world '" + getTargetWorldPath() + "' to '" + getTargetWorldBackupPath() + "/'");
  return gulp
    .src([getTargetWorldPath() + "/**/*"])
    .pipe(gulp.dest(getTargetWorldBackupPath() + "/" + activeWorldFolderName));
}

const deploy_localmc = gulp.series(
  clean_localmc,
  function (callbackFunction) {
    callbackFunction();
  },
  gulp.parallel(deploy_localmc_behavior_packs, deploy_localmc_resource_packs)
);

function watch() {
  return gulp.watch(
    ["scripts/**/*.ts", "behavior_packs/**/*", "resource_packs/**/*"],
    gulp.series(build, deploy_localmc)
  );
}

function serve() {
  return gulp.watch(
    ["scripts/**/*.ts", "behavior_packs/**/*", "resource_packs/**/*"],
    gulp.series(stopServer, build, deploy_localmc, startServer)
  );
}

let activeServer = null;

function stopServer(callbackFunction) {
  if (activeServer) {
    activeServer.stdin.write("stop\n");
    activeServer = null;
  }

  callbackFunction();
}

function startServer(callbackFunction) {
  if (activeServer) {
    activeServer.stdin.write("stop\n");
    activeServer = null;
  }

  activeServer = spawn(dedicatedServerPath + "bedrock_server");

  let logBuffer = "";

  let serverLogger = function (buffer) {
    let incomingBuffer = buffer.toString();

    if (incomingBuffer.endsWith("\n")) {
      (logBuffer + incomingBuffer).split(/\n/).forEach(function (message) {
        if (message) {
          if (message.indexOf("Server started.") >= 0) {
            activeServer.stdin.write("script debugger listen 19144\n");
          }
          console.log("Server: " + message);
        }
      });
      logBuffer = "";
    } else {
      logBuffer += incomingBuffer;
    }
  };

  activeServer.stdout.on("data", serverLogger);
  activeServer.stderr.on("data", serverLogger);

  callbackFunction();
}

hasZipped = false;

function rename_zip_to_mcworld(cb) {
  gulp
    .src(["export/export.zip"])
    .pipe(rename(bpfoldername + ".mcworld"))
    .pipe(gulp.dest("export/"));
  cb();
}

function zip_world(cb) {
  let zip_path = "export/export.zip";
  console.log("export/" + bpfoldername + "/**/**");
  return gulp
    .src(["export/" + bpfoldername + "/**/**"], { base: "export/" + bpfoldername + "/" })
    .pipe(zip("export.zip", { compress: false, buffer: false }))
    .pipe(gulp.dest("export/"));
}

function get_version() {
  let currentTimeStamp = new Date().toISOString().replace(/T/, " ").replace(/\..+/, "");
  return currentTimeStamp;
}

function add_version_to_world_name(cb) {
  let worldNameFilePath = "export/" + bpfoldername + "/levelname.txt";
  let worldName = fs.readFileSync(worldNameFilePath, "utf8");
  currentTimeStamp = get_version();
  worldName = worldName + " " + currentTimeStamp;
  fs.writeFileSync(worldNameFilePath, worldName);
  console.log("Written " + worldName + " to " + worldNameFilePath);
  cb();
}

function copy_build_resource_pack_to_export_folder(cb) {
  gulp
    .src(["build/resource_packs/**/**"])
    .pipe(gulp.dest("export/" + bpfoldername + "/resource_packs/" + bpfoldername));
  console.log(
    "Copied build/resource_packs/" +
      bpfoldername +
      "/**/** to export/" +
      bpfoldername +
      "/resource_packs/" +
      bpfoldername
  );
  cb();
}

function copy_build_behavior_pack_to_export_folder(cb) {
  gulp
    .src(["build/behavior_packs/**/**"])
    .pipe(gulp.dest("export/" + bpfoldername + "/behavior_packs/" + bpfoldername));
  cb();
}

function copy_world_to_export_folder(cb) {
  if (exportWorldFolderPath == "") {
    var readLineSync = import("readline-sync").then((readLineSync) => {
      let data = get_worlds_paths_and_names(cb);
      let index = readLineSync.keyInSelect(
        data.map((d) => d.name),
        "Which world do you want to copy?"
      );
      if (index === -1) {
        cb();
        return;
      }
      const world = data[index];
      //console.log("Copying world '" + world.path + "' to 'build/worlds/export/'");
      return gulp.src([world.path + "/**/*"]).pipe(gulp.dest("export/" + bpfoldername));
    });
  } else {
    return gulp.src([exportWorldFolderPath + "/**/*"]).pipe(gulp.dest("export/" + bpfoldername));
  }
  //copy the build to the correct folder
  cb();
}

function get_worlds_paths_and_names(cb) {
  let data = [];
  const worldsPath = mcdir + worldsFolderName;
  const worlds = fs.readdirSync(worldsPath);
  worlds.forEach((world) => {
    let worldName = "UNDEFINED";
    try {
      worldName = fs.readFileSync(worldsPath + "/" + world + "/levelname.txt", "utf8");
    } catch (e) {
      console.log("Error reading levelname.txt for " + world);
      console.error(e);
    }
    data.push({
      path: worldsPath + "/" + world,
      name: worldName,
    });
  });
  cb();
  return data;
}

async function nbt_rename_world(cb) {
  const NBT = await import("nbtify");
  const { readFile, writeFile } = await import("fs/promises");
  const buffer = await readFile("export/" + bpfoldername + "/level.dat");
  const data = await NBT.read(buffer);
  let oldLevelName = data.data.LevelName;
  let newName = bpfoldername + " " + get_version();
  data.data.LevelName = newName;
  const result = await NBT.write(data);
  await writeFile("export/" + bpfoldername + "/level.dat", result);
  console.log("Renamed " + oldLevelName + " to " + newName);
  cb();
}

function levelnametxt_rename(cb) {
  const { readFile, writeFile } = import("fs/promises");
  const levelNameFilePath = "export/" + bpfoldername + "/levelname.txt";
  let levelName = fs.readFileSync(levelNameFilePath, "utf8");
  let newName = bpfoldername + " " + get_version();
  fs.writeFileSync(levelNameFilePath, newName);
  console.log("Renamed " + levelName + " to " + newName);
  cb();
}

// Setup functions

function setup_behaviour_pack(cb) {
  const manifestPath = "behavior_packs/manifest.json";
  const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));

  let header_uuid = get_behaviour_pack_uuid(cb);
  let module_uuid = generate_UUID(cb);
  let resource_pack_dependencies_uuid = get_resource_pack_uuid(cb);

  console.log("Header UUID: " + header_uuid);
  console.log("Module UUID: " + module_uuid);

  manifest.header.uuid = header_uuid;
  manifest.modules[0].uuid = module_uuid;
  manifest.dependencies[1].uuid = resource_pack_dependencies_uuid;

  manifest.metadata.authors = behavior_pack_authors;
  manifest.header.name = behavior_pack_name;
  manifest.header.description = behavior_pack_description;

  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 1));
  cb();
}
function setup_resource_pack(cb) {
  const manifestPath = "resource_packs/manifest.json";
  const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));

  let header_uuid = get_resource_pack_uuid(cb);
  let module_uuid = generate_UUID(cb);
  let dependencies_uuid = get_behaviour_pack_uuid(cb);

  manifest.header.uuid = header_uuid;
  manifest.modules[0].uuid = module_uuid;
  manifest.dependencies[0].uuid = dependencies_uuid;

  manifest.metadata.authors = resource_pack_authors;
  manifest.header.name = resource_pack_name;
  manifest.header.description = resource_pack_description;

  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 1));
  cb();
}

function generate_UUID(cb) {
  let uuid = crypto.randomUUID();
  return uuid;
}

var behavior_pack_uuid = null;
var resource_pack_uuid = null;

function get_uuid_for_type(cb, type) {
  let to_return = null;
  switch (type) {
    case "behaviour_pack":
      if (behavior_pack_uuid === null) {
        behavior_pack_uuid = generate_UUID(cb);
      }
      to_return = behavior_pack_uuid;
      break;
    case "resource_pack":
      if (resource_pack_uuid === null) {
        resource_pack_uuid = generate_UUID(cb);
      }
      to_return = resource_pack_uuid;
      break;
    default:
      return generate_UUID(cb);
  }
  return to_return;
}
let get_behaviour_pack_uuid = (cb) => get_uuid_for_type(cb, "behaviour_pack");
let get_resource_pack_uuid = (cb) => get_uuid_for_type(cb, "resource_pack");

exports.clean_build = clean_build;
exports.copy_behavior_packs = copy_behavior_packs;
exports.copy_resource_packs = copy_resource_packs;
exports.compile_scripts = compile_scripts;
exports.copy_content = copy_content;
exports.build = build;
exports.clean_localmc = clean_localmc;
exports.deploy_localmc = deploy_localmc;
exports.default = gulp.series(build, deploy_localmc);
exports.clean = gulp.series(clean_build, clean_localmc);
exports.watch = gulp.series(build, deploy_localmc, watch);
exports.serve = gulp.series(build, deploy_localmc, startServer, serve);
exports.updateworld = gulp.series(
  clean_localmc_world_backup,
  backup_localmc_world,
  clean_localmc_world,
  deploy_localmc_world
);
exports.ingestworld = gulp.series(clean_dev_world_backup, backup_dev_world, clean_dev_world, ingest_localmc_world);
exports.updateconfig = gulp.series(clean_localmc_config, deploy_localmc_config);
exports.compile_world = gulp.series(
  clean_export,
  build,
  copy_world_to_export_folder,
  gulp.parallel(copy_build_behavior_pack_to_export_folder, copy_build_resource_pack_to_export_folder),
  gulp.parallel(add_version_to_world_name, nbt_rename_world)
);
exports.zip = gulp.series(zip_world, rename_zip_to_mcworld);
exports.export = gulp.series(
  clean_export,
  copy_world_to_export_folder,
  build,
  gulp.parallel(copy_build_behavior_pack_to_export_folder, copy_build_resource_pack_to_export_folder),
  levelnametxt_rename,
  nbt_rename_world,
  zip_world,
  rename_zip_to_mcworld
);

exports.test = gulp.parallel(setup_behaviour_pack, setup_resource_pack);
exports.export_clean = gulp.series(clean_export);
