import {
  world,
  system,
  MolangVariableMap,
  BlockPermutation,
  Player,
  EntityDamageCause,
  WeatherType,
  ItemStack,
} from "@minecraft/server";
import { tornadoSaverId } from "./main";
import { get_PlayerGroups } from "./utilities";
const excludeBlocks = [
  "minecraft:chest",
  "minecraft:trapped_chest",
  "minecraft:end_portal_frame",
  "minecraft:air",
  "minecraft:barrel",
  "minecraft:barrier",
  "minecraft:flowing_water",
  "minecraft:flowing_lava",
  "minecraft:water",
];
const fragile_blocks = [
  "sunflower",
  "grass",
  "acacia_sapling",
  "birch_sapling",
  "cherry_sapling",
  "dark_oak_sapling",
  "jungle_sapling",
  "oak_sapling",
  "spruce_sapling",
  "baamboo_sapling",
  "lava",
  "leaves",
  "glass",
  "glass_pane",
  "fence",
  "fence_gate",
  "poppy",
  "tulip",
  "orchid",
  "dandelion",
  "allium",
  "bluet",
  "oxeye",
  "cornflower",
  "lily_of_the_valley",
  "waterlily",
  "fern",
  "lilac",
  "bush",
  "peony",
  "petals",
  "golden_rail",
  "detector_rail",
  "web",
  "door",
  "snow",
  "tall_grass",
  "deadbush",
  "yellow_flower",
  "red_flower",
  "brown_mushroom",
  "red_mushroom",
  "tnt",
  "torch",
  "fire",
  "redstone_wire",
  "wheat",
  "ladder",
  "rail",
  "lever",
  "unlit_redstone_torch",
  "redstone_torch",
  "snow_layer",
  "ice",
  "cactus",
  "reeds",
  "glowstone",
  "portal",
  "unpowered_repeater",
  "powered_repeater",
  "pumpkin_stem",
  "melon_stem",
  "vine",
  "waterlily",
  "nether_wart",
  "activator_rail",
  "cocoa",
  "tripwire_hook",
  "tripWire",
  "flower_pot",
  "carrots",
  "potatoes",
  "skull",
  "unpowered_comparator",
  "powered_comparator",
  "leaves2",
  "seaLantern",
  "carpet",
  "double_plant",
  "glass",
  "glass_pane",
  "white_stained_glass",
  "red_stained_glass",
  "pink_stained_glass",
  "orange_stained_glass",
  "blue_stained_glass",
  "light_blue_stained_glass",
  "light_gray_stained_glass",
  "gray_stained_glass",
  "black_stained_glass",
  "brown_stained_glass",
  "green_stained_glass",
  "lime_stained_glass",
  "cyan_stained_glass",
  "purple_stained_glass",
  "magenta_stained_glass",
  "yellow_stained_glass",
  "white_stained_glass_pane",
  "red_stained_glass_pane",
  "pink_stained_glass_pane",
  "orange_stained_glass_pane",
  "blue_stained_glass_pane",
  "light_blue_stained_glass_pane",
  "light_gray_stained_glass_pane",
  "gray_stained_glass_pane",
  "black_stained_glass_pane",
  "brown_stained_glass_pane",
  "green_stained_glass_pane",
  "lime_stained_glass_pane",
  "cyan_stained_glass_pane",
  "purple_stained_glass_pane",
  "magenta_stained_glass_pane",
  "yellow_stained_glass_pane",
  "white_wool",
  "red_wool",
  "pink_wool",
  "orange_wool",
  "blue_wool",
  "light_blue_wool",
  "light_gray_wool",
  "gray_wool",
  "black_wool",
  "brown_wool",
  "green_wool",
  "lime_wool",
  "cyan_wool",
  "purple_wool",
  "magenta_wool",
  "yellow_wool",
  "frame",
  "colored_torch_rg",
  "colored_torch_bp",
  "frosted_ice",
  "end_rod",
  "beetroot",
  "kelp",
  "sea_pickle",
  "bamboo",
  "scaffolding",
  "sweet_berry_bush",
  "campfire",
  "wither_rose",
  "crimson_roots",
  "warped_roots",
  "crimson_fungus",
  "warped_fungus",
  "nether_sprouts",
  "oak_door",
  "cherry_door",
  "spruce_door",
  "birch_door",
  "acacia_door",
  "jungle_door",
  "bamboo_door",
  "tall_dry_grass",
  "short_dry_grass",
  "leaf_litter",
  "firefly_bush",
  "pale_oak_sapling",
  "wildflowers",
  "cactus_flower",
  "pale_hanging_moss",
  "pale_oak_leaves",
  "reeds",
];
const replace_dirt_blocks = [
  "minecraft:grass_block",
  "minecraft:podzol",
  "minecraft:mycelium",
];
export function tornado_MAIN(dimension) {
  system.runInterval(() => {
    dimension
      .getEntities({
        families: ["tornado"],
      })
      .forEach((ent) => {
        if (dimension.getBlock(ent.location) != undefined) {
          //write world prop
          const prop = world.getDynamicProperty(tornadoSaverId);
          let data = JSON.parse(prop);
          data[ent.id] = ent.location;
          data[ent.id].biomeType = ent.getProperty("property:biome_type");
          world.setDynamicProperty(tornadoSaverId, JSON.stringify(data));
          if (Math.floor(Math.random() * 100) == 1) lightning(ent);
          magnet(dimension, ent);
          follow_path(ent);
          y_control(dimension, ent);
        }
      });
    tornado_farRener(dimension);
  }, 1);
  griefing(dimension);
  age(dimension);
}
function tornado_farRener(dimension) {
  const data = JSON.parse(world.getDynamicProperty(tornadoSaverId));
  for (const [tornadoId, tornadoData] of Object.entries(data)) {
    //const entityNear = dimension.getEntities({ families: ['tornado'], location: tornadoData, maxDistance: 2 })
    //console.warn(entityNear[0])
    get_PlayerGroups(dimension, 1000).forEach((group) => {
      let map = new MolangVariableMap();
      map.setFloat("global_offset_x", tornadoData.x - group.location.x);
      map.setFloat("global_offset_y", tornadoData.y - group.location.y);
      map.setFloat("global_offset_z", tornadoData.z - group.location.z);
      const biome_type = tornadoData.biomeType;
      let biome_variant = 0; // normal
      if (biome_type == "desert") biome_variant = 1;
      else if (biome_type == "snow") biome_variant = 2;
      else if (biome_type == "water") biome_variant = 3;
      map.setFloat("biome_variant", biome_variant);
      try {
        if (biome_variant == 0) {
          dimension.spawnParticle(
            "tvf_randev:tornado_core_normal",
            group.location,
            map
          );
        } else if (biome_variant == 1) {
          dimension.spawnParticle(
            "tvf_randev:tornado_core_desert",
            group.location,
            map
          );
        } else if (biome_variant == 2) {
          dimension.spawnParticle(
            "tvf_randev:tornado_core_snow",
            group.location,
            map
          );
        } else if (biome_variant == 3) {
          dimension.spawnParticle(
            "tvf_randev:tornado_core_water",
            group.location,
            map
          );
        }
        dimension.spawnParticle(
          "tvf_randev:block_spiral_tornado",
          group.location,
          map
        );
      } catch {}
    });
  }
}
function magnet(dimension, tornado) {
  const tornadoLocation = tornado.location;
  const maxRadiusXZ = 20;
  const maxY = 100;
  const minY = -10;
  for (const mob of dimension.getEntities({
    excludeFamilies: ["decor", "npc", "black_hole", "tornado", "technical"],
  })) {
    if (
      Math.sqrt(
        (tornadoLocation.x - mob.location.x) *
          (tornadoLocation.x - mob.location.x) +
          (tornadoLocation.z - mob.location.z) *
            (tornadoLocation.z - mob.location.z)
      ) >= maxRadiusXZ
    )
      continue;
    if (!mob.isValid) continue;
    if (mob.location.y - tornadoLocation.y < minY) continue;
    const startLocation = mob.location;
    const currPoint = {
      x: startLocation.x - tornadoLocation.x,
      y: startLocation.y,
      z: startLocation.z - tornadoLocation.z,
    };
    const newPoint = cyl_system(currPoint, -0.05, 0.05, 0.1);
    const vector = {
      x: newPoint.x - currPoint.x,
      y: newPoint.y - currPoint.y,
      z: newPoint.z - currPoint.z,
    };
    const vector_length = Math.sqrt(
      vector.x * vector.x + vector.y * vector.y + vector.z * vector.z
    );
    let r =
      Math.sqrt(currPoint.x * currPoint.x + currPoint.z * currPoint.z) - 0.1;
    let force =
      (maxRadiusXZ - r) * (maxRadiusXZ - r) * (1.0 / maxRadiusXZ / maxRadiusXZ);
    const vector_norm = {
      x: vector.x / vector_length,
      y: vector.y / vector_length,
      z: vector.z / vector_length,
    };
    if (
      Math.sqrt(
        (tornadoLocation.y - mob.location.y) *
          (tornadoLocation.y - mob.location.y)
      ) > maxY
    ) {
      vector_norm.y = 0;
      force *= 130;
    }
    //apply movement
    if (mob instanceof Player) {
      if (mob.getGameMode() == "Creative") continue;
      if (mob.isJumping && !dimension.getBlock(mob.location).below(1).isAir)
        continue;
      mob.applyKnockback(
        {
          x: vector_norm.x * force,
          z: vector_norm.z * force,
        },
        vector_norm.y * force
      );
      mob.runCommand("camerashake add @s " + 0.05 + " 0.05 ");
    } else {
      mob.clearVelocity();
      mob.applyImpulse({
        x: vector_norm.x * force * 5,
        y: vector_norm.y * force * 5,
        z: vector_norm.z * force * 5,
      });
    }
    //damage
    if (Math.floor(Math.random() * 20) == 1) damage(tornadoLocation, mob);
  }
}
function cyl_system(inputPoint, rStep, phiStep, hStep) {
  let r = Math.sqrt(inputPoint.x * inputPoint.x + inputPoint.z * inputPoint.z);
  let phi = Math.atan2(inputPoint.z, inputPoint.x);
  let h = inputPoint.y;
  r += rStep;
  phi += phiStep;
  h += hStep;
  const outputPoint = {
    x: r * Math.cos(phi),
    y: h,
    z: r * Math.sin(phi),
  };
  return outputPoint;
}
function cyl2decart(r, phi, h) {
  const outputPoint = {
    x: r * Math.cos(phi),
    y: h,
    z: r * Math.sin(phi),
  };
  return outputPoint;
}
function damage(tornadoLocation, mob) {
  if (mob.location.y < tornadoLocation.y) return;
  if (mob instanceof Player) {
    if (
      Math.sqrt(
        (tornadoLocation.x - mob.location.x) *
          (tornadoLocation.x - mob.location.x) +
          (tornadoLocation.z - mob.location.z) *
            (tornadoLocation.z - mob.location.z)
      ) <= 10
    ) {
      mob.applyDamage(1, {
        cause: EntityDamageCause.contact,
      });
    }
  } else {
    if (
      Math.sqrt(
        (tornadoLocation.x - mob.location.x) *
          (tornadoLocation.x - mob.location.x) +
          (tornadoLocation.z - mob.location.z) *
            (tornadoLocation.z - mob.location.z)
      ) <= 20
    ) {
      mob.applyDamage(1, {
        cause: EntityDamageCause.contact,
      });
    }
  }
}
function lightning(ent) {
  let location = ent.location;
  location = {
    x: location.x + Math.random() * 5 - Math.random() * 5,
    y: location.y,
    z: location.z + Math.random() * 5 - Math.random() * 5,
  };
  ent.runCommand(
    "summon lightning_bolt " + location.x + " " + location.y + " " + location.z
  );
}
function y_control(dimension, tornado) {
  if (dimension.getBlock(tornado.location).isLiquid) return;
  const topMostY = dimension.getTopmostBlock({
    x: tornado.location.x,
    z: tornado.location.z,
  }).location.y;
  if (topMostY - Math.floor(tornado.location.y) == -1) {
    //console.warn(topMostY - Math.floor(tornado.location.y))
    return;
  }
  let vector;
  vector = {
    x: tornado.location.x,
    y: topMostY + 1,
    z: tornado.location.z,
  };
  tornado.teleport(vector, {
    dimension: dimension,
    keepVelocity: true,
  });
}
function follow_path(tornado) {
  const a = tornado.location;
  const followForce = 0.005;
  let path;
  path = {
    x: tornado.getProperty("property:path_x"),
    y: 0,
    z: tornado.getProperty("property:path_z"),
  };
  if (path.x == 0.0 && path.z == 0.0) {
    //initilize
    const nearPlayer = tornado.dimension.getPlayers({
      location: a,
      closest: 1,
    });
    if (nearPlayer.length == 0) return 0;
    let b = nearPlayer[0].location;
    b.x = b.x + Math.floor(Math.random() * 50);
    b.z = b.z + Math.floor(Math.random() * 50);
    const vector = {
      x: b.x - a.x,
      y: 0,
      z: b.z - a.z,
    };
    const vector_length = Math.sqrt(
      vector.x * vector.x + vector.y * vector.y + vector.z * vector.z
    );
    const vector_norm = {
      x: (vector.x * 10) / vector_length,
      y: 0,
      z: (vector.z * 10) / vector_length,
    };
    tornado.setProperty("property:path_x", vector_norm.x);
    tornado.setProperty("property:path_z", vector_norm.z);
    //console.warn(vector_norm.x + "   " + vector_norm.z)
  }
  tornado.applyImpulse({
    x: path.x * followForce,
    y: path.y * followForce,
    z: path.z * followForce,
  });
}
function griefing(dimension) {
  system.runInterval(() => {
    dimension
      .getEntities({
        type: "tvf_randev:tornado",
      })
      .forEach((ent) => {
        const center = ent.location;
        const maxBlockBreak = 20;
        let waitMax = 1000;
        system.runJob(
          (function* () {
            let i = 0;
            let temp = 0;
            for (let blockCounter = 0; blockCounter < maxBlockBreak; ) {
              if (blockCounter == temp) i++;
              else i = 0;
              if (i > waitMax) {
                break;
              }
              temp = blockCounter;
              const random_r = Math.random() * 10;
              const random_phi = Math.random() * 360;
              const location_cyl = cyl2decart(random_r, random_phi, 0);
              let location = {
                x: center.x + location_cyl.x,
                y: dimension.getTopmostBlock({
                  x: center.x + location_cyl.x,
                  z: center.z + location_cyl.z,
                }).y,
                z: center.z + location_cyl.z,
              };
              const block = dimension.getBlock(location);
              if (block == undefined) continue;
              if (excludeBlocks.includes(block.typeId)) continue;
              replace_to_dirt(block);
              replace_to_air(dimension, block);
              /*let map = new MolangVariableMap()
                    map.setFloat("x", center.x - location.x)
                    map.setFloat("y", center.y - location.y)
                    map.setFloat("z", center.z - location.z)
                    //dimension.spawnParticle('tvf_randev:black_hole_broke_block', location, map)*/ blockCounter++;
              if (blockCounter > maxBlockBreak) break;
              yield;
            }
          })()
        );
      });
  }, 10);
}
function replace_to_dirt(block) {
  const block2dirt = BlockPermutation.resolve("dirt");
  if (!excludeBlocks.includes(block.above(1).typeId)) return;
  if (block.typeId == "minecaft:dirt") return;
  if (replace_dirt_blocks.includes(block.typeId))
    block.setPermutation(block2dirt);
}
function replace_to_air(dimension, block) {
  for (let i = 0; i < 10; i++) {
    let blokNew = dimension.getBlock({
      x: block.location.x,
      y: block.location.y + i,
      z: block.location.z,
    });
    for (let str of fragile_blocks)
      if (blokNew.typeId.endsWith(str)) {
        dimension.runCommand(
          "fill " +
            blokNew.x +
            " " +
            blokNew.y +
            " " +
            blokNew.z +
            " " +
            blokNew.x +
            " " +
            blokNew.y +
            " " +
            blokNew.z +
            " air destroy"
        );
        if (Math.floor(Math.random() * 10))
          dimension.spawnParticle("minecraft:wind_explosion_emitter", blokNew);
        return;
      }
  }
}
function age(dimension) {
  system.runInterval(() => {
    dimension
      .getEntities({
        type: "tvf_randev:tornado",
      })
      .forEach((ent) => {
        const age = ent.getProperty("property:age");
        if (age < 75) {
          if (age == 0) {
            dimension.setWeather(WeatherType.Rain);
          }
          ent.setProperty("property:age", age + 1);
          ent.getComponent("minecraft:health").setCurrentValue(75 - age);
        } else {
          ent.kill();
        }
      });
  }, 100);
}

// Превент спавна
world.afterEvents.entitySpawn.subscribe((tornadoSpawn) => {
  const tornado = tornadoSpawn.entity;
  if (!(tornado.typeId == "tvf_randev:tornado" && tornado.isValid)) return;
  if (tornado.dimension.id !== "minecraft:overworld") {
    // Если торнадо не в оверворлде, то просто убиваем
    const item = new ItemStack("tvf_randev:tornado_projectile", 1);
    tornado.dimension.spawnItem(item, tornado.location);
    const loc = tornado.location;
    const dim = tornado.dimension;
    const wc = dim.spawnEntity("minecraft:wind_charge_projectile", loc);
    wc.applyImpulse({ x: 0, y: -1, z: 0 });

    tornado.remove();
    return;
  }
  // Если торнадо в оверворлде, то проверяем на наличие генератора
  if (
    tornado.dimension.getEntities({
      location: tornado.location,
      maxDistance: 20,
      type: "tvf_randev:high_pressure_generator_fake",
    }).length > 0
  ) {
    tornado.kill(); // обязательно kill а не remove, иначе не будет работать world.getDynamicProperty(tornadoSaverId)
  }
});

//хранит координаты торнадо, чистится при смерти торнадо
world.afterEvents.entityDie.subscribe((tornadoKill) => {
  if (tornadoKill.deadEntity.typeId == "tvf_randev:tornado") {
    let data = JSON.parse(world.getDynamicProperty(tornadoSaverId));
    delete data[tornadoKill.deadEntity.id];
    world.setDynamicProperty(tornadoSaverId, JSON.stringify(data));
  }
});
