package land.chipmunk.chipmunkmod.modules;

import land.chipmunk.chipmunkmod.ChipmunkMod;
import land.chipmunk.chipmunkmod.listeners.Listener;
import land.chipmunk.chipmunkmod.listeners.ListenerManager;
import net.minecraft.block.Block;
import net.minecraft.block.CommandBlock;
import net.minecraft.block.entity.CommandBlockBlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.client.world.ClientWorld;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.network.ClientConnection;
import net.minecraft.network.packet.c2s.play.UpdateCommandBlockC2SPacket;
import net.minecraft.util.math.BlockBox;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.dimension.DimensionType;

import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CompletableFuture;

public class CommandCore {
  private final MinecraftClient client;
  public boolean ready = false;
  public BlockPos origin;
  public BlockBox noPos;
  public BlockPos block;
  public BlockBox withPos;

  private Timer timer;

  private boolean shouldRefill = false;

  private DimensionType oldDimension;

  public boolean runFillCommand = true;

  public boolean clientPlayerEntityFilled = false;

  public static CommandCore INSTANCE = new CommandCore(MinecraftClient.getInstance());

  public CommandCore (MinecraftClient client) {
    this.client = client;
    reloadRelativeArea();
  }

  public void init () {
    if (timer != null) cleanup();

    final TimerTask task = new TimerTask() {
      public void run () {
        tick();
      }
    };

    final TimerTask refillTask = new TimerTask() {
      @Override
      public void run() {
        if (clientPlayerEntityFilled) {
          clientPlayerEntityFilled = false;
          return;
        }

        check();

        if (!shouldRefill) return;

        refill();

        shouldRefill = false;
      }
    };

    timer = new Timer();

    timer.schedule(task, 50, 50);

    timer.schedule(refillTask, 50, 1000);

    move(client.player.getPos());
  }

  private void tick () {
    final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();

    if (networkHandler == null) {
      cleanup();

      return;
    }

    reloadRelativeArea();
  }

  public void reloadRelativeArea () {
    noPos = ChipmunkMod.CONFIG.core.relativeArea;
  }

  public void check () {
    final ClientPlayNetworkHandler networkHandler = client.getNetworkHandler();

    if (networkHandler == null || withPos == null || !ready) return;

    final ClientPlayerEntity player = client.player;
    final ClientWorld world = client.world;

    if (player == null || world == null) return;

    if (oldDimension != null && !oldDimension.equals(world.getDimension())) move(client.player.getPos());

    oldDimension = world.getDimension();

    try {
      for (int x = withPos.getMinX(); x <= withPos.getMaxX(); x++) {
        for (int y = withPos.getMinY(); y <= withPos.getMaxY(); y++) {
          for (int z = withPos.getMinZ(); z <= withPos.getMaxZ(); z++) {
            final BlockPos pos = new BlockPos(x, y, z);

            final Block block = world.getBlockState(pos).getBlock();

            if (block instanceof CommandBlock) continue;

            shouldRefill = true;

            return;
          }
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public void move (Vec3d position) {
    final ClientWorld world = client.world;
    if (world == null || noPos == null) return;

    final DimensionType dimension = world.getDimension();

    final int dimMinY = dimension.minY();
    final int dimMaxY = dimension.height() + dimMinY - 1; // -1 accounts for block at Y=0
    int yOffset = 0;
    if (noPos.getMinY() < dimMinY) {
      yOffset = dimMinY - noPos.getMinY();
    } else if (noPos.getMaxY() > dimMaxY) {
      yOffset = dimMaxY - noPos.getMaxY();
    }

    origin = new BlockPos(
            ((int) position.getX() / 16) * 16,
            noPos.getMinY() + yOffset,
            ((int) position.getZ() / 16) * 16
    );

    withPos = noPos.offset(origin.getX(), yOffset, origin.getZ());
    block = new BlockPos(withPos.getMinX(), withPos.getMinY(), withPos.getMinZ());
    refill();

    for (Listener listener : ListenerManager.listeners) listener.coreMoved();
    if (!ready) {
      ready = true;

      for (Listener listener : ListenerManager.listeners) listener.coreReady();
    }
  }

  public void refill () {
    if (!runFillCommand || withPos == null) return;

    final String command = String.format(
            KaboomCheck.INSTANCE.isKaboom ?
                    "fill %s %s %s %s %s %s repeating_command_block replace" :
                    "fill %s %s %s %s %s %s command_block",
            withPos.getMinX(),
            withPos.getMinY(),
            withPos.getMinZ(),

            withPos.getMaxX(),
            withPos.getMaxY(),
            withPos.getMaxZ()
    );

    client.getNetworkHandler().sendChatCommand(command);
  }

  public void incrementCurrentBlock () {
    if (withPos == null) return;
    int x = block.getX();
    int y = block.getY();
    int z = block.getZ();

    x++;

    if (x > withPos.getMaxX()) {
      x = withPos.getMinX();
      z++;
    }

    if (z > withPos.getMaxZ()) {
      z = withPos.getMinZ();
      y++;
    }

    if (y > withPos.getMaxY()) {
      x = withPos.getMinX();
      y = withPos.getMinY();
      z = withPos.getMinZ();
    }

    block = new BlockPos(x, y, z);
  }

  public void run (String command) {
    final ClientConnection connection = client.getNetworkHandler().getConnection();

    if (block == null) return;

    System.out.println(command);

    if (KaboomCheck.INSTANCE.isKaboom) {
      connection.send(
              new UpdateCommandBlockC2SPacket(
                      block,
                      command,
                      CommandBlockBlockEntity.Type.AUTO,
                      false,
                      false,
                      true
              )
      );
    } else {
      connection.send(
              new UpdateCommandBlockC2SPacket(
                      block,
                      "",
                      CommandBlockBlockEntity.Type.REDSTONE,
                      false,
                      false,
                      false
              )
      );

      connection.send(
              new UpdateCommandBlockC2SPacket(
                      block,
                      command,
                      CommandBlockBlockEntity.Type.REDSTONE,
                      false,
                      false,
                      true
              )
      );
    }

    incrementCurrentBlock();
  }

  public CompletableFuture<NbtCompound> runTracked (String command) {
    final ClientConnection connection = client.getNetworkHandler().getConnection();

    if (block == null) return new CompletableFuture<>();

    if (KaboomCheck.INSTANCE.isKaboom) {
      connection.send(
              new UpdateCommandBlockC2SPacket(
                      block,
                      command,
                      CommandBlockBlockEntity.Type.AUTO,
                      true,
                      false,
                      true
              )
      );
    } else {
      connection.send(
              new UpdateCommandBlockC2SPacket(
                      block,
                      "",
                      CommandBlockBlockEntity.Type.REDSTONE,
                      true,
                      false,
                      false
              )
      );

      connection.send(
              new UpdateCommandBlockC2SPacket(
                      block,
                      command,
                      CommandBlockBlockEntity.Type.REDSTONE,
                      true,
                      false,
                      true
              )
      );
    }

    incrementCurrentBlock();

    CompletableFuture<NbtCompound> future = new CompletableFuture<>();

    final Timer timer = new Timer();

    final TimerTask queryTask = new TimerTask() {
      public void run () {
        client.getNetworkHandler().getDataQueryHandler().queryBlockNbt(block, future::complete);

        timer.cancel(); // ? Is this necesary?
        timer.purge();
      }
    };

    timer.schedule(queryTask, 50);

    return future;
  }

  public void cleanup () {
    if (timer == null) return;

    timer.cancel();
    timer.purge();

    withPos = null;
    block = null;
    ready = false;
  }
}
