package net.glowstone;

import net.glowstone.entity.GlowPlayer;
import net.glowstone.net.message.play.game.WorldBorderMessage;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.WorldBorder;

public class GlowWorldBorder implements WorldBorder {

    private final World world;
    private double size, futureSize, step;
    private Location center;
    private double damageBuffer, damagePerBlock;
    private int warningTime, warningDistance;
    private long time;
    private long lastWorldTick;

    /**
     * Initializes a new {@link WorldBorder} for the given world.
     *
     * @param world the world to initialize a new {@link WorldBorder} for.
     */
    public GlowWorldBorder(World world) {
        this.world = world;
        lastWorldTick = world.getFullTime();
        size = 60000000;
        time = 0;
        futureSize = size;
        step = 0;
        center = new Location(world, 0, 0, 0);
        damageBuffer = 5;
        damagePerBlock = 0.2;
        warningTime = 15;
        warningDistance = 5;
    }

    /**
     * Creates a {@link WorldBorderMessage} containing information to initialize the world border on the client-side.
     *
     * @return a new {@link WorldBorderMessage} for this world border.
     */
    public WorldBorderMessage createMessage() {
        return new WorldBorderMessage(WorldBorderMessage.Action.INITIALIZE, center.getX(), center.getZ(), size, futureSize, time * 1000, 29999984, warningTime, warningDistance);
    }

    /**
     * Pulses the world border for each tick.
     * <p>
     * Attempts to call this method more than once per tick will be ignored.
     */
    public void pulse() {
        if (lastWorldTick >= world.getFullTime()) {
            // The pulse method is being called more than once per tick; abort.
            return;
        }
        if (step != 0) {
            size += step;
            if (Math.abs(size - futureSize) < 1) {
                // completed
                size = futureSize;
                time = 0;
                step = 0;
            }
        }
    }

    @Override
    public void reset() {
        setSize(60000000);
        time = 0;
        futureSize = size;
        step = 0;
        setCenter(new Location(world, 0, 0, 0));
        setDamageBuffer(5);
        setDamageAmount(0.2);
        setWarningTime(15);
        setWarningDistance(5);
    }

    @Override
    public double getSize() {
        return size;
    }

    @Override
    public void setSize(double size) {
        this.size = size;
        this.futureSize = size;
        broadcast(new WorldBorderMessage(WorldBorderMessage.Action.SET_SIZE, size));
    }

    @Override
    public void setSize(double size, long seconds) {
        if (seconds <= 0) {
            setSize(size);
            return;
        }
        long ticks = seconds * 20;
        step = (size - this.size) / (double) ticks;
        futureSize = size;
        time = seconds;
        broadcast(new WorldBorderMessage(WorldBorderMessage.Action.LERP_SIZE, this.size, futureSize, time * 1000));
    }

    @Override
    public Location getCenter() {
        return center;
    }

    @Override
    public void setCenter(double x, double z) {
        setCenter(new Location(world, x, 0, z));
    }

    @Override
    public void setCenter(Location location) {
        center = location.clone();
        broadcast(new WorldBorderMessage(WorldBorderMessage.Action.SET_CENTER, center.getX(), center.getZ()));
    }

    @Override
    public double getDamageBuffer() {
        return damageBuffer;
    }

    @Override
    public void setDamageBuffer(double blocks) {
        this.damageBuffer = blocks;
    }

    @Override
    public double getDamageAmount() {
        return damagePerBlock;
    }

    @Override
    public void setDamageAmount(double damage) {
        this.damagePerBlock = damage;
    }

    @Override
    public int getWarningTime() {
        return warningTime;
    }

    @Override
    public void setWarningTime(int seconds) {
        this.warningTime = seconds;
        broadcast(new WorldBorderMessage(WorldBorderMessage.Action.SET_WARNING_TIME, seconds));
    }

    @Override
    public int getWarningDistance() {
        return warningDistance;
    }

    @Override
    public void setWarningDistance(int distance) {
        this.warningDistance = distance;
        broadcast(new WorldBorderMessage(WorldBorderMessage.Action.SET_WARNING_BLOCKS, distance));
    }

    @Override
    public boolean isInside(Location location) {
        Location max = center.clone().add(size / 2, 0, size / 2), min = center.clone().subtract(size / 2, 0, size / 2);
        return location.getX() <= max.getX() && location.getZ() <= max.getZ() && location.getX() >= min.getX() && location.getZ() >= min.getZ();
    }

    /**
     * The target side length the world border is being resized to, in blocks.
     *
     * @return the target side length the world border is being resized to.
     */
    public double getSizeLerpTarget() {
        return futureSize;
    }

    /**
     * The delay in ticks until the world border's sides should reach the target length.
     *
     * @return the delay until the world border's sides should reach the target length.
     */
    public long getSizeLerpTime() {
        return time;
    }

    private void broadcast(WorldBorderMessage message) {
        world.getPlayers().forEach(player -> ((GlowPlayer) player).getSession().send(message));
    }
}

@@ -1821,32 +1821,77 @@
 turn;%0A        %7D%0A
+        lastWorldTick = world.getFullTime();%0A
         if (step

Update lastWorldTick every tick (WorldBorder).