package org.inventivetalent.mapmanager;

import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.inventivetalent.mapmanager.manager.MapManager;
import org.inventivetalent.mapmanager.wrapper.MapWrapper;

import java.awt.image.BufferedImage;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;

class DefaultMapManager implements MapManager {

	protected final Set<Short>       OCCUPIED_IDS = new HashSet<>();
	private final   List<MapWrapper> MANAGED_MAPS = new CopyOnWriteArrayList<>();

	@Override
	public MapWrapper wrapImage(BufferedImage image) {
		return wrapImage(new ArrayImage(image));
	}

	@Override
	public MapWrapper wrapImage(ArrayImage image) {
		if (Options.CHECK_DUPLICATES) {
			for (int i = 0; i < MANAGED_MAPS.size(); i++) {
				MapWrapper wrapper = MANAGED_MAPS.get(i);
				if (image.equals(wrapper.getContent())) { return wrapper; }
			}
		}
		return wrapNewImage(image);
	}

	@Override
	public MapWrapper wrapMultiImage(BufferedImage image, int rows, int columns) {
		//Don't add the wrapper to the MANAGED_MAPS, since we're already registering all the single wrapped maps
		return new MultiMapWrapper(image, rows, columns);
	}

	@Override
	public MapWrapper wrapMultiImage(ArrayImage image, int rows, int columns) {
		//Don't add the wrapper to the MANAGED_MAPS, since we're already registering all the single wrapped maps
		return new MultiMapWrapper(image, rows, columns);
	}

	@Override
	public MapWrapper wrapMultiImage(ArrayImage[][] images) {
		return new MultiMapWrapper(images);
	}

	public MapWrapper wrapNewImage(ArrayImage image) {
		MapWrapper wrapper = new DefaultMapWrapper(image);
		MANAGED_MAPS.add(wrapper);
		return wrapper;
	}

	@Override
	public void unwrapImage(MapWrapper wrapper) {
		if (wrapper instanceof DefaultMapWrapper) {
			for (short s : ((DefaultMapWrapper) wrapper).viewers.values()) {
				MapSender.cancelIDs(new short[] { s });
			}
		}
		wrapper.getController().clearViewers();
		MANAGED_MAPS.remove(wrapper);
		if (wrapper instanceof MultiMapWrapper) {
			((MultiMapWrapper) wrapper).unwrap();
		}
	}

	@Override
	public Set<MapWrapper> getMapsVisibleTo(OfflinePlayer player) {
		Set<MapWrapper> visible = new HashSet<>();
		for (MapWrapper wrapper : MANAGED_MAPS) {
			if (wrapper.getController().isViewing(player)) {
				visible.add(wrapper);
			}
		}
		return visible;
	}

	@Override
	public MapWrapper getWrapperForId(OfflinePlayer player, short id) {
		for (MapWrapper wrapper : getMapsVisibleTo(player)) {
			if (wrapper.getController().getMapId(player) == id) { return wrapper; }
		}
		return null;
	}

	@Override
	public void registerOccupiedID(short id) {
		if (!OCCUPIED_IDS.contains(id)) { OCCUPIED_IDS.add(id); }
	}

	@Override
	public void unregisterOccupiedID(short id) {
		OCCUPIED_IDS.remove(id);
	}

	@Override
	public Set<Short> getOccupiedIdsFor(OfflinePlayer player) {
		Set<Short> ids = new HashSet<>();
		for (MapWrapper wrapper : MANAGED_MAPS) {
			short s;
			if ((s = wrapper.getController().getMapId(player)) >= 0) {
				ids.add(s);
			}
		}
		return ids;
	}

	@Override
	public boolean isIdUsedBy(OfflinePlayer player, short id) {
		return id > Options.FORCED_OFFSET && getOccupiedIdsFor(player).contains(id);
	}

	@Override
	public short getNextFreeIdFor(Player player) throws MapLimitExceededException {
		Set<Short> occupied = getOccupiedIdsFor(player);
		//Add the 'default' occupied IDs
		occupied.addAll(OCCUPIED_IDS);

		int largest = Options.FORCED_OFFSET;
		for (Short s : occupied) {
			if (s > largest) { largest = s; }
		}

		//Simply increase the maximum id if it's still small enough
		if (largest + 1 < Short.MAX_VALUE) { return (short) (largest + 1); }

		//Otherwise iterate through all options until there is an unused id
		for (short s = 0; s < Short.MAX_VALUE; s++) {
			if (!occupied.contains(s)) {
				return s;
			}
		}

		//If we end up here, this player has no more free ids. Let's hope nobody uses this many Maps.
		throw new MapLimitExceededException("'" + player + "' reached the maximum amount of available Map-IDs");
	}

	@Override
	public void clearAllMapsFor(OfflinePlayer player) {
		for (MapWrapper wrapper : getMapsVisibleTo(player)) {
			wrapper.getController().removeViewer(player);
		}
	}

	public MapWrapper getDuplicate(ArrayImage image) {
		for (int i = 0; i < MANAGED_MAPS.size(); i++) {
			MapWrapper wrapper = MANAGED_MAPS.get(i);
			if (image.equals(wrapper.getContent())) { return wrapper; }
		}
		return null;
	}

}