import * as mc from "@minecraft/server";
import * as vector from "../utils/vector";
import * as features from "./structureFeatures";

export const toStructureRot = new Map([
  [0, "None"],
  [90, "Rotate90"],
  [-270, "Rotate90"],
  [180, "Rotate180"],
  [-180, "Rotate180"],
  [-90, "Rotate270"],
  [270, "Rotate270"],
]);

const allowedBlocks = ["minecraft:grass_block", "minecraft:dirt", "minecraft:gravel", "minecraft:stone", "minecraft:sand", "minecraft:water"];

export const trapdoors = [
  "minecraft:dark_oak_trapdoor",
  "minecraft:trapdoor",
  "minecraft:acacia_trapdoor",
  "minecraft:bamboo_trapdoor",
  "minecraft:birch_trapdoor",
  "minecraft:cherry_trapdoor",
  "minecraft:copper_trapdoor",
  "minecraft:crimson_trapdoor",
  "minecraft:iron_trapdoor",
  "minecraft:jungle_trapdoor",
  "minecraft:mangrove_trapdoor",
  "minecraft:spruce_trapdoor",
  "minecraft:warped_trapdoor",
];

function wroughtnautStructure(entity) {
  const nearestWall = findNearestWall(entity);

  if (!nearestWall) {
    entity.remove();
    return;
  }

  // const rot = Math.round(entity.getRotation().y / 90) * 90;
  const rot = features.cardinalToRotation(nearestWall.direction);
  const loc = nearestWall.block.location;

  const spawnLoc = {
    x: loc.x + Math.sin((rot / 180) * Math.PI) * 13.5,
    y: loc.y,
    z: loc.z - Math.cos((rot / 180) * Math.PI) * 13.5,
  };

  const carver = entity.dimension.spawnEntity("hf_mzs:cave_carver", spawnLoc);
  carver.runCommand(`/tp @s ~ ~ ~ ${rot} 0`);
  mc.system.runTimeout(() => {
    entity.addEffect("invisibility", 9999, {
      showParticles: false,
    });
    const dir = {
      x: Math.sin((rot / 180) * Math.PI) * 13,
      y: 0,
      z: -Math.cos((rot / 180) * Math.PI) * 13,
    };
    //mc.world.sendMessage(`${rot}  ${dir.x}  ${dir.z}`)
    mc.world.structureManager.place(
      "hf_mzs:wroughtnaut_room",
      mc.world.getDimension("overworld"),
      {
        x: loc.x - 19 / 2 + dir.x,
        y: loc.y - 1,
        z: loc.z - 19 / 2 + dir.z,
      },
      {
        animationMode: mc.StructureAnimationMode.None,
        animationSeconds: 4,
        rotation: toStructureRot.get(rot),
      }
    );
    mc.system.runTimeout(() => {
      entity.dimension.spawnEntity("hf_mzs:wroughtnaut", spawnLoc).setRotation({
        x: 0,
        y: rot,
      });
      entity.remove();
    }, 20);
  }, 20);
}

function spawnFrostmaw(entity) {
  const f = entity.dimension.getEntities({ location: entity.location, type: "hf_mzs:frostmaw", maxDistance: 128 });
  const s = entity.dimension.getEntities({ location: entity.location, type: "hf_mzs:sculptor", maxDistance: 128 });
  if (f.length > 0 || s.length > 0) {
    entity.remove();
    return;
  }
  const pos = {
    x: entity.location.x,
    y: 200,
    z: entity.location.z,
  };
  const block = entity.dimension.getBlockFromRay(pos, vector.down, {
    maxDistance: 300,
    includeLiquidBlocks: false,
    includePassableBlocks: false,
    excludeTypes: ["minecraft:spruce_leaves", "minecraft:oak_leaves"/*, "hf_mzs:structure_gen_trigger"*/],
  });
  if (block === undefined) return;

  const frostmaw = entity.dimension.spawnEntity("hf_mzs:frostmaw", {
    x: block.block.location.x,
    y: block.block.location.y + 1,
    z: block.block.location.z,
  });
  frostmaw.runCommand("/execute rotated ~ 0 run fill ^-3^-3^-2 ^3^^4 air [] replace snow_layer");
  // layer 1
  frostmaw.runCommand("/execute rotated ~ 0 run fill ^-3^-1^4 ^2^-1^4 snow [] replace air");
  frostmaw.runCommand("/execute rotated ~ 0 run fill ^-3^-1^3 ^3^-1^1 snow [] replace air");
  frostmaw.runCommand("/execute rotated ~ 0 run fill ^-3^-1^ ^2^-1^ snow [] replace air");
  frostmaw.runCommand("/execute rotated ~ 0 run fill ^-3^-1^-1 ^1^-1^-1 snow [] replace air");
  frostmaw.runCommand("/execute rotated ~ 0 run fill ^-2^-1^-2 ^0^-1^-2 snow [] replace air");
  // layer 2
  frostmaw.runCommand("/execute rotated ~ 0 run fill ^-3^-2^4 ^3^-2^-1 snow [] replace air");
  frostmaw.runCommand("/execute rotated ~ 0 run fill ^-3^-2^-2 ^2^-2^-2 snow [] replace air");
  // layer 3..
  frostmaw.runCommand("/execute rotated ~ 0 run fill ^-3^-3^4 ^3^-5^-2 snow [] replace air");
  frostmaw.runCommand("/execute rotated ~ 0 run fill ^-3^^-2 ^3^^4 snow_layer [] replace air");
  entity.remove();
}

let structureFeatures = [];

function umvuthiStructure(entity) {
  entity.addEffect("invisibility", 9999, {
    showParticles: false,
  });
  entity.dimension
    .getEntities({
      includeTypes: "minecraft:player",
      maxDistance: 32,
      location: entity.location,
    })
    .forEach((element) => {
      if (element.isValid()) structureFeatures.push({ location: element.location, radius: 1 });
    });

  const rot = Math.floor(Math.random() * 4) * 90;
  //mc.world.sendMessage(`${rot}`)
  const structureRot = toStructureRot.get(rot);

  const chunkCentre = {
    x: entity.location.x,
    y: 200,
    z: entity.location.z,
  };

  // Big firepit - placed at the centre of the current chunk
  const firepitPos = entity.dimension.getBlockFromRay(chunkCentre, vector.down, {
    maxDistance: 500,
    includePassableBlocks: false,
    includeTypes: allowedBlocks,
  })?.block.location;
  entity.dimension.spawnEntity("hf_mzs:structure_umvuthana_village", firepitPos);
  const boundry1 = {
    x: firepitPos.x - 19,
    y: firepitPos.y - 2,
    z: firepitPos.z - 19,
  };
  const boundry2 = {
    x: firepitPos.x + 19,
    y: firepitPos.y + 14,
    z: firepitPos.z + 19,
  };
  //mc.world.sendMessage(`/fill ${vector.string(boundry1)} ${vector.string(boundry2)} air replace acacia_leaves`);
  // entity.runCommand(`/fill ${vector.string(boundry1)} ${vector.string(boundry2)} air replace acacia_leaves`);
  // entity.runCommand(`/fill ${vector.string(boundry1)} ${vector.string(boundry2)} air replace acacia_log`);
  // entity.runCommand(`/fill ${vector.string(boundry1)} ${vector.string(boundry2)} air replace oak_leaves`);
  // entity.runCommand(`/fill ${vector.string(boundry1)} ${vector.string(boundry2)} air replace oak_log`);

  //mc.world.sendMessage(`${chunkCentre.x}     ${chunkCentre.y}     ${chunkCentre.z}`)
  // mc.world.structureManager.place("hf_mzs:umvuthana_big_firepit", entity.dimension, centerStructure(firepitPos, "hf_mzs:umvuthana_big_firepit", -2), {
  //   animationMode: mc.StructureAnimationMode.None,
  //   animationSeconds: 1,
  //   rotation: structureRot,
  // });
  structureFeatures.push({ location: firepitPos, radius: 3 });

  // Umvuthi throne - placed 9 blocks away from the edge of the firepit
  const staircasePos = entity.dimension.getBlockFromRay(
    {
      x: chunkCentre.x - Math.sin((rot / 180) * Math.PI) * 9,
      y: chunkCentre.y,
      z: chunkCentre.z + Math.cos((rot / 180) * Math.PI) * 9,
    },
    vector.down,
    {
      maxDistance: 500,
      includePassableBlocks: false,
      includeLiquidBlocks: true,
      includeTypes: allowedBlocks,
    }
  )?.block.location;
  let thronePos = entity.dimension.getBlockFromRay(
    {
      x: chunkCentre.x - Math.sin((rot / 180) * Math.PI) * 20,
      y: chunkCentre.y,
      z: chunkCentre.z + Math.cos((rot / 180) * Math.PI) * 20,
    },
    vector.down,
    {
      maxDistance: 500,
      includePassableBlocks: false,
      includeLiquidBlocks: true,
      includeTypes: allowedBlocks,
    }
  )?.block.location;
  thronePos.y = staircasePos.y;

  mc.world.structureManager.place("hf_mzs:umvuthi_throne", entity.dimension, centerStructure(thronePos, "hf_mzs:umvuthi_throne", 0, rot), {
    animationMode: mc.StructureAnimationMode.None,
    animationSeconds: 1,
    rotation: structureRot,
  });
  const throneDir = {
    x: - Math.sin((rot / 180) * Math.PI),
    y: 0,
    z: Math.cos((rot / 180) * Math.PI),
  }
  structureFeatures.push({ location: vector.add(vector.multiply(vector.normalize(throneDir), 28), chunkCentre), radius: 8 });
  structureFeatures.push({ location: vector.add(vector.multiply(vector.normalize(throneDir), 24), chunkCentre), radius: 8 });
  structureFeatures.push({ location: vector.add(vector.multiply(vector.normalize(throneDir), 20), chunkCentre), radius: 8 });
  structureFeatures.push({ location: vector.add(vector.multiply(vector.normalize(throneDir), 16), chunkCentre), radius: 8 });

  // Spawn 2-3 platforms, 15 to 22 blocks away from centre
  generateMultiple("ramp", [2, 3], [15, 22], chunkCentre, 5, 2, [2, 2], entity.dimension);

  // Spawn 2-4 trees, 13 to 26 blocks away from centre
  generateMultiple("tree", [2, 4], [13, 26], chunkCentre, 4, 3, [1, 1], entity.dimension);

  // Spawn 2-4 small firepits, 8 to 22 blocks away from centre
  generateMultiple("small_firepit", [2, 4], [8, 22], chunkCentre, 3, 2, [0, 1], entity.dimension);

  // Spawn 7-16 poles, 8 to 22 blocks away from centre
  generateMultiple("pole", [12, 16], [8, 22], chunkCentre, 1.5, 4, [1, 2], entity.dimension);

  // Spawn some umvuthana so that the village does not feel completely empty
  mc.system.runTimeout(() => {
    for (let i = 0; i <= 6; i++) {
      for (let attempts = 0; attempts <= 10; attempts++) {
        const block = entity.dimension.getBlockFromRay(
          {
            x: chunkCentre.x + (Math.random() - 0.5) * 32,
            y: chunkCentre.y,
            z: chunkCentre.z + (Math.random() - 0.5) * 32,
          },
          vector.down,
          {
            maxDistance: 500,
            includePassableBlocks: false,
            includeTypes: allowedBlocks,
          }
        );
        if (block === undefined) continue;
        const e = entity.dimension.spawnEntity("hf_mzs:umvuthana<spawned_in_village>", vector.add(block.block.location, vector.up));
        e.setProperty("hf_mzs:from_village", true);
        break;
      }
    }
    entity.remove();
  }, 25);
}

export function centerStructure(position, structureName, yOffset, rot = 0, yPos = 0) {
  const y = yPos === 0 ? position.y : yPos;
  if (rot === 0 || rot === 180)
    return {
      x: position.x - mc.world.structureManager.get(structureName).size.x / 2,
      y: y + yOffset,
      z: position.z - mc.world.structureManager.get(structureName).size.z / 2,
    };
  return {
    x: position.x - mc.world.structureManager.get(structureName).size.z / 2,
    y: y + yOffset,
    z: position.z - mc.world.structureManager.get(structureName).size.x / 2,
  };
}

function getMaxY(structureName, position, rot = 0, dimension) {
  const structureSize = rot === 0 || rot === 180 ? mc.world.structureManager.get(structureName).size : vector.cross(mc.world.structureManager.get(structureName).size, vector.up);
  const nPos = [
    {
      x: structureSize.x / 2 + position.x,
      y: 300,
      z: structureSize.z / 2 + position.z,
    },
    {
      x: -structureSize.x / 2 + position.x,
      y: 300,
      z: -structureSize.z / 2 + position.z,
    },
    {
      x: -structureSize.x / 2 + position.x,
      y: 300,
      z: structureSize.z / 2 + position.z,
    },
    {
      x: structureSize.x / 2 + position.x,
      y: 300,
      z: -structureSize.z / 2 + position.z,
    },
  ];
  let max = 0;
  let count = 0;
  for (let i = 0; i < 4; i++) {
    const ray = dimension.getBlockFromRay(nPos[i], vector.down, {
      maxDistance: 800,
      includePassableBlocks: false,
      includeLiquidBlocks: true,
      includeTypes: allowedBlocks
    });
    //max = ray.location.y > max ? ray.location.y : max;
    if (ray !== undefined) {
      //mc.world.sendMessage(`${max}    ${ray.block.location.y}`);
      max += ray.block.location.y;
      count++;
    }
  }
  return max / count;
}

export function checkIfValidPosition(position, radius) {
  for (let i = 0; i < structureFeatures.length; i++) {
    const dist = vector.magnitude(vector.flatten(vector.subtract(position, structureFeatures[i].location), "y", false));
    //mc.world.sendMessage(`${dist}`);
    //mc.world.sendMessage(`${dist}     ${structureFeatures[i].radius}`)
    if (dist < structureFeatures[i].radius + radius) return false;
  }
  return true;
}

export function registerFeaturePosition(featureData) {
  structureFeatures.push(featureData);
}
function isAllowedBlock(id) {
  for (const block of allowedBlocks) {
    if (id == block)
      return true;
  }
  return false;
}

function generateMultiple(name, countRange, distanceRange, centre, radius, variations, offset, dimension) {
  const count = Math.floor(Math.random() * (countRange[1] - countRange[0])) + countRange[0];
  for (let i = 0; i < count; i++) {
    for (let attempts = 0; attempts < 10; attempts++) {
      const angle = Math.random() * 2 * Math.PI;
      const distance = Math.floor(Math.random() * (distanceRange[1] - distanceRange[0])) + distanceRange[0];
      const block = dimension.getBlockFromRay(
        {
          x: centre.x - Math.sin(angle) * distance,
          y: centre.y,
          z: centre.z + Math.cos(angle) * distance,
        },
        vector.down,
        {
          maxDistance: 500,
          includePassableBlocks: false,
          includeLiquidBlocks: true,
          includeTypes: allowedBlocks,
        }
      );
      if (block === undefined || (block && block.block.isLiquid)) 
        continue;
      if (!isAllowedBlock(block.block.typeId)) {
        continue;
      }
      const pos = block.block.location;

      if (!checkIfValidPosition(pos, radius)) 
        continue;
      if (!isTerrainFlat(pos, radius, dimension)) 
        continue;
      const type = "hf_mzs:umvuthana_" + name + (Math.round(Math.random() * (variations - 1)) + 1);
      const instanceRot = Math.floor(Math.random() * 4) * 90;
      const yPos = getMaxY(type, pos, instanceRot, dimension);
      mc.world.structureManager.place(type, dimension, centerStructure(pos, type, Math.random() > 0.5 ? offset[0] : offset[1], instanceRot, yPos), {
        animationMode: mc.StructureAnimationMode.None,
        animationSeconds: 1,
        rotation: toStructureRot.get(instanceRot),
      });
      registerFeaturePosition({ location: pos, radius: radius });
      break;
    }
  }
}
function isTerrainFlat(pos, delta, dimension) {
  // cast a ray at each corner of a delta by delta square, centered at pos
  let sum = 0;
  let count = 0;

  const deltas = [
    {x: delta, z: delta},
    {x: delta, z: -delta},
    {x: -delta, z: delta},
    {x: -delta, z: -delta}
  ]
  let heights = [];

  for (let i = 0; i < 4; i++) {
    const rayHit = dimension.getBlockFromRay(
      {
        x: pos.x + deltas[i].x,
        y: 300,
        z: pos.z - deltas[i].z,
      },
      vector.down,
      {
        maxDistance: 1000,
        includePassableBlocks: false,
        includeLiquidBlocks: true,
        includeTypes: allowedBlocks,
      }
    );
    if (rayHit) {
      count++;
      sum += rayHit.block.location.y;
      heights.push(rayHit.block.location.y);
    }
  }
  for (let h of heights) {
    if (Math.abs(h-sum/count) >= 3)
      return false;
  }
  return true;
}

// SCULPTOR STRUCTURE
/**
 *
 * @param {mc.Entity} entity
 */
function sculptorStructure(entity) {
  const structure = mc.world.structureManager.get("hf_mzs/monastery/monastery_center_full");
  const start = {
    x: Math.ceil(entity.location.x) - structure.size.x / 2,
    y: Math.ceil(entity.location.y) - 2,
    z: Math.ceil(entity.location.z) - structure.size.z / 2,
  };

  mc.world.structureManager.place(structure, mc.world.getDimension("overworld"), start, {
    animationMode: mc.StructureAnimationMode.None,
    rotation: "None",
    includeEntities: true,
  });

  entity.runCommand(
    `/event entity @e[type=hf_mzs:custom_jigsaw_util,x=${start.x},y=${start.y},z=${start.z},dx=${structure.size.x},dy=${structure.size.y},dz=${structure.size.z}] hf_mzs:trigger`
  );
  const sculptorLoc = {
    x: Math.floor(entity.location.x) + 0.5,
    y: entity.location.y,
    z: Math.floor(entity.location.z) + 0.5,
  };
  entity.runCommand(`/summon hf_mzs:sculptor ${vector.string(sculptorLoc)} 0 0 minecraft:entity_spawned "Tongbi, the Sculptor"`);
  entity.remove();
}

export function triggerStructureGen(event) {
  if (event.id === "hf_mzs:trigger_structure_gen") {
    const entity = event.sourceEntity;

    if (entity.typeId === "hf_mzs:wroughtnaut_gen_trigger") wroughtnautStructure(entity);
    if (entity.typeId === "hf_mzs:umvuthi_gen_trigger") umvuthiStructure(entity);
    if (entity.typeId === "hf_mzs:monastery_gen_trigger") sculptorStructure(entity);
    if (entity.typeId === "hf_mzs:frostmaw_gen_trigger") spawnFrostmaw(entity);
  }
}

export function findNearestWall(entity) {
  const center = entity.location;
  center.x = Math.floor(center.x) + 0.5;
  center.y = Math.floor(center.y) + 0.5;
  center.z = Math.floor(center.z) + 0.5;

  const north = { x: 0, y: 0, z: -1 };
  const east = { x: 1, y: 0, z: 0 };
  const south = { x: 0, y: 0, z: 1 };
  const west = { x: -1, y: 0, z: 0 };

  const options = { includePassableBlocks: false, includeLiquidBlocks: false, maxDistance: 16 };

  const northHit = entity.dimension.getBlockFromRay(center, north, options);
  const eastHit = entity.dimension.getBlockFromRay(center, east, options);
  const southHit = entity.dimension.getBlockFromRay(center, south, options);
  const westHit = entity.dimension.getBlockFromRay(center, west, options);

  let minDistance = Infinity;
  let nearest = undefined;
  let direction = "";

  if (northHit && northHit.block && northHit.block.isValid()) {
    let loc = northHit.block.location;
    loc.x = Math.floor(loc.x) + 0.5;
    loc.y = Math.floor(loc.y) + 0.5;
    loc.z = Math.floor(loc.z) + 0.5;
    let dist = vector.distance(loc, center);
    if (dist < minDistance) {
      minDistance = dist;
      nearest = northHit.block;
      direction = "north";
    }
  }
  if (eastHit && eastHit.block && eastHit.block.isValid()) {
    let loc = eastHit.block.location;
    loc.x = Math.floor(loc.x) + 0.5;
    loc.y = Math.floor(loc.y) + 0.5;
    loc.z = Math.floor(loc.z) + 0.5;
    let dist = vector.distance(loc, center);
    if (dist < minDistance) {
      minDistance = dist;
      nearest = eastHit.block;
      direction = "east";
    }
  }
  if (southHit && southHit.block && southHit.block.isValid()) {
    let loc = southHit.block.location;
    loc.x = Math.floor(loc.x) + 0.5;
    loc.y = Math.floor(loc.y) + 0.5;
    loc.z = Math.floor(loc.z) + 0.5;
    let dist = vector.distance(loc, center);
    if (dist < minDistance) {
      minDistance = dist;
      nearest = southHit.block;
      direction = "south";
    }
  }
  if (westHit && westHit.block && westHit.block.isValid()) {
    let loc = westHit.block.location;
    loc.x = Math.floor(loc.x) + 0.5;
    loc.y = Math.floor(loc.y) + 0.5;
    loc.z = Math.floor(loc.z) + 0.5;
    let dist = vector.distance(loc, center);
    if (dist < minDistance) {
      minDistance = dist;
      nearest = westHit.block;
      direction = "west";
    }
  }

  return nearest ? { block: nearest, direction } : undefined;
}
