package org.apollo.game.sync.task;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apollo.game.message.impl.ClearRegionMessage;
import org.apollo.game.message.impl.GroupedRegionUpdateMessage;
import org.apollo.game.message.impl.RegionChangeMessage;
import org.apollo.game.message.impl.RegionUpdateMessage;
import org.apollo.game.model.Position;
import org.apollo.game.model.area.Region;
import org.apollo.game.model.area.RegionCoordinates;
import org.apollo.game.model.area.RegionRepository;
import org.apollo.game.model.entity.Player;

/**
 * A {@link SynchronizationTask} which does pre-synchronization work for the specified {@link Player}.
 *
 * @author Graham
 * @author Major
 */
public final class PrePlayerSynchronizationTask extends SynchronizationTask {

	/**
	 * The Map of RegionCoordinates to Sets of RegionUpdateMessages, which contain all of the Entity information for a
	 * Region.
	 */
	private final Map<RegionCoordinates, Set<RegionUpdateMessage>> encodes;

	/**
	 * The player.
	 */
	private final Player player;

	/**
	 * The Map of RegionCoordinates to Sets of RegionUpdateMessages, which contain the updates for a Region a Player
	 * can already view.
	 */
	private final Map<RegionCoordinates, Set<RegionUpdateMessage>> updates;

	/**
	 * Creates the {@link PrePlayerSynchronizationTask} for the specified {@link Player}.
	 *
	 * @param player The Player.
	 * @param encodes The Map containing Region encodes.
	 * @param updates The {@link Map} containing {@link Region} updates.
	 */
	public PrePlayerSynchronizationTask(Player player, Map<RegionCoordinates, Set<RegionUpdateMessage>> encodes,
	                                    Map<RegionCoordinates, Set<RegionUpdateMessage>> updates) {
		this.player = player;
		this.updates = updates;
		this.encodes = encodes;
	}

	@Override
	public void run() {
		Position old = player.getPosition();
		player.getWalkingQueue().pulse();

		if (player.isTeleporting()) {
			player.resetViewingDistance();
		}

		Position position = player.getPosition();
		if (!player.hasLastKnownRegion() || isRegionUpdateRequired()) {
			player.setRegionChanged(true);

			player.setLastKnownRegion(position);
			player.send(new RegionChangeMessage(position));
		}

		RegionRepository repository = player.getWorld().getRegionRepository();
		Set<RegionCoordinates> oldViewable = repository.fromPosition(old).getSurrounding();
		Set<RegionCoordinates> newViewable = repository.fromPosition(position).getSurrounding();

		Set<RegionCoordinates> differences = new HashSet<>(newViewable);
		differences.retainAll(oldViewable);

		Set<RegionCoordinates> full = new HashSet<>(newViewable);
		full.removeAll(oldViewable);

		sendUpdates(player.getLastKnownRegion(), differences, full);
	}

	/**
	 * Checks if a region update is required.
	 *
	 * @return {@code true} if a Region update is required, {@code false} if not.
	 */
	private boolean isRegionUpdateRequired() {
		Position current = player.getPosition();
		Position last = player.getLastKnownRegion();

		int deltaX = current.getLocalX(last);
		int deltaY = current.getLocalY(last);

		return deltaX <= Position.MAX_DISTANCE || deltaX >= Region.VIEWPORT_WIDTH - Position.MAX_DISTANCE - 1
				|| deltaY <= Position.MAX_DISTANCE || deltaY >= Region.VIEWPORT_WIDTH - Position.MAX_DISTANCE - 1;
	}

	/**
	 * Sends the updates for a {@link Region}
	 *
	 * @param position The {@link Position} of the last known region.
	 * @param differences The {@link Set} of {@link RegionCoordinates} of Regions that changed.
	 * @param full The {@link Set} of {@link RegionCoordinates} of Regions that require a full update.
	 */
	private void sendUpdates(Position position, Set<RegionCoordinates> differences, Set<RegionCoordinates> full) {
		RegionRepository repository = player.getWorld().getRegionRepository();
		int height = position.getHeight();

		for (RegionCoordinates coordinates : differences) {
			Set<RegionUpdateMessage> messages = updates.computeIfAbsent(coordinates,
					coords -> repository.get(coords).getUpdates(height));

			if (!messages.isEmpty()) {
				player.send(new GroupedRegionUpdateMessage(position, coordinates, messages));
			}
		}

		for (RegionCoordinates coordinates : full) {
			Set<RegionUpdateMessage> messages = encodes.computeIfAbsent(coordinates,
					coords -> repository.get(coords).encode(height));

			if (!messages.isEmpty()) {
				player.send(new ClearRegionMessage(position, coordinates));
				player.send(new GroupedRegionUpdateMessage(position, coordinates, messages));
			}
		}
	}

}
@@ -1932,16 +1932,41 @@
 lse();%0A%0A
+%09%09boolean local = true;%0A%0A
 %09%09if (pl
@@ -1985,24 +1985,24 @@
 orting()) %7B%0A
-
 %09%09%09player.re
@@ -2023,16 +2023,34 @@
 ance();%0A
+%09%09%09local = false;%0A
 %09%09%7D%0A%0A%09%09P
@@ -2090,16 +2090,17 @@
 tion();%0A
+%0A
 %09%09if (!p
@@ -2190,16 +2190,34 @@
 d(true);
+%0A%09%09%09local = false;
 %0A%0A%09%09%09pla
@@ -2723,16 +2723,32 @@
 wable);%0A
+%09%09if (local) %7B%0A%09
 %09%09full.r
@@ -2765,24 +2765,28 @@
 ldViewable);
+%0A%09%09%7D
 %0A%0A%09%09sendUpda
@@ -3298,17 +3298,16 @@
 NCE - 1%0A
-%09
 %09%09%09%7C%7C de
@@ -3400,16 +3400,16 @@
 CE - 1;%0A
+
 %09%7D%0A%0A%09/**
@@ -4066,33 +4066,32 @@
 nt(coordinates,%0A
-%09
 %09%09%09%09coords -%3E re
@@ -4124,32 +4124,32 @@
 dates(height));%0A
+
 %0A%09%09%09if (!message
@@ -4378,17 +4378,16 @@
 inates,%0A
-%09
 %09%09%09%09coor

Fix #136