package protocolsupportpocketstuff.api.util;

import protocolsupport.api.Connection;
import protocolsupport.api.ProtocolSupportAPI;
import protocolsupport.api.ProtocolType;
import protocolsupport.protocol.ConnectionImpl;
import protocolsupport.protocol.serializer.MiscSerializer;
import protocolsupport.protocol.typeremapper.pe.PESkinModel;
import protocolsupportpocketstuff.api.modals.Modal;
import protocolsupportpocketstuff.api.modals.ModalCallback;
import protocolsupportpocketstuff.api.modals.ModalType;
import protocolsupportpocketstuff.api.modals.ModalUtils;
import protocolsupportpocketstuff.api.skins.PocketSkinModel;
import protocolsupportpocketstuff.packet.PEPacket;
import protocolsupportpocketstuff.packet.play.ModalRequestPacket;
import protocolsupportpocketstuff.packet.play.SkinPacket;
import protocolsupportpocketstuff.packet.play.TransferPacket;
import protocolsupportpocketstuff.storage.Modals;
import protocolsupportpocketstuff.util.StuffUtils;

import java.util.Collection;
import java.util.HashMap;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/***
 * Utility to send and retrieve pocket-specific things to a {@link Connection}.
 */
public class PocketCon {
	
    //=====================================================\\
    //						Getting						   \\
    //=====================================================\\

	/***
	 * Checks if a connection is a pocket connection.
	 * @param player
	 * @return the truth.
	 */
	public static boolean isPocketConnection(Connection connection) {
		return connection.getVersion().getProtocolType().equals(ProtocolType.PE);
	}
	
	/**
	 * Gets all pocket connections on the server.
	 * @return all pocket connections.
	 */
	public static Collection<? extends Connection> getPocketConnections() {
		return ProtocolSupportAPI.getConnections().stream().filter(pocketFilter()).collect(Collectors.toList());
	}
	
	/***
	 * Filter to filter PE connections.
	 * @return the truth is a predicate.
	 */
	public static Predicate<Connection> pocketFilter() {
		return c -> isPocketConnection(c);
	}
	
    //=====================================================\\
    //						Packets						   \\
    //=====================================================\\
	
	/***
	 * Sends a modal and gets the corresponding id.
	 * The id can be used to track response of this modal in events.
	 * @param modal
	 * @return the id of the modal.
	 */
	public static int sendModal(Connection connection, Modal modal) {
		return sendModal(connection, Modals.getInstance().takeId(), modal.toJSON(), null);
	}

	/***
	 * Sends a modal and gets the corresponding id.
	 * The id can be used to track response of this modal in events.
	 * The callback is called when the player completes a modal
	 * and all modaleventhandlers are done. 
	 * @param connection
	 * @param modal
	 * @param callback
	 * @return the id of the modal.
	 */
	public static int sendModal(Connection connection, Modal modal, ModalCallback callback) {
		return sendModal(connection, Modals.getInstance().takeId(), modal.toJSON(), callback);
	}

	/***
	 * Sends a modal with an id specified.
	 * <em>Nonono, don't use custom ids!</em>
	 * If you like you can use this function in combination with
	 * {@link Modals.INSTANCE.takeId} to send custom JSON to the player.
	 * @param id
	 * @param modalJSON
	 * @return the id of the modal.
	 */
	public static int sendModal(Connection connection, int id, String modalJSON) {
		sendModal(connection, id, modalJSON, null);
		return id;
	}

	/***
	 * Sends a modal with an id specified.
	 * <em>Nonono, don't use custom ids!</em>
	 * If you like you can use this function in combination with
	 * {@link Modals.INSTANCE.takeId} to send custom JSON to the player.
	 * This method also registers specified callback (if not null)
	 * which is called after modal is completed and events are handled.
	 * @param id
	 * @param modalJSON
	 * @param callback
	 * @return the id of the modal.
	 */
	public static int sendModal(Connection connection, int id, String modalJSON, ModalCallback callback) {
		if (callback != null) { ModalUtils.setCallback(connection, callback); }
		ModalUtils.setSendType(connection, ModalType.fromModal(modalJSON));
		sendPocketPacket(connection, new ModalRequestPacket(id, modalJSON));
		return id;
	}

	/**
	 * Sends a pc-like skin to a pocket connection.
	 * @param connection
	 * @param uuid
	 * @param skin
	 * @param isSlim
	 */
	public static void sendSkin(Connection connection, UUID uuid, byte[] skin, boolean isSlim) {
		sendSkin(connection, uuid, skin, PocketSkinModel.fromPEModel(PESkinModel.getSkinModel(isSlim)));
	}

	/***
	 * Sends a PocketSkin to a pocket connection.
	 * @param connection
	 * @param uuid
	 * @param skin
	 * @param skinModel
	 */
	public static void sendSkin(Connection connection, UUID uuid, byte[] skin, PocketSkinModel skinModel) {
		//TODO: "Steve" is actually a hack. The name send should be the previous skin name. Not sure if this matters though. Works for now :S"
		sendPocketPacket(connection, new SkinPacket(uuid, skinModel.getSkinId(), skinModel.getSkinName(), "Steve", skin, new byte[0], skinModel.getGeometryId(), skinModel.getGeometryData()));
	}

	/***
	 * Transfers a player to another server
	 * @param connection
	 * @param address
	 * @param port
	 */
	public static void transfer(Connection connection, String address, short port) {
		sendPocketPacket(connection, new TransferPacket(address, port));
	}

	/***
	 * Sends a packet to pocket.
	 * @param connection
	 * @param packet
	 */
	public static void sendPocketPacket(Connection connection, PEPacket packet) {
		connection.sendRawPacket(MiscSerializer.readAllBytes(packet.encode((ConnectionImpl) connection)));
	}

    //=====================================================\\
    //						Client-Info					   \\
    //=====================================================\\

	/***
	 * Gets the client's UUID. Used for updating skin of the self player or other UUID specific things.
	 * @param connection
	 * @return
	 */
	public static UUID getClientUniqueId(Connection connection) {
		return (UUID) getClientInformationMap(connection).get("UUID");
	}

	/***
	 * Gets the client random ID assigned upon install to the user. This can be edited by the client, so beware!
	 * @param connection
	 * @return client's random id
	 */
	public static long getClientRandomId(Connection connection) {
		return (Long) getClientInformationMap(connection).get("ClientRandomId");
	}

	/***
	 * Gets the client device model
	 * @param connection
	 * @return client's device model
	 */
	public static String getDeviceModel(Connection connection) {
		return (String) getClientInformationMap(connection).get("DeviceModel");
	}

	/***
	 * Gets the client operating system
	 * @param connection
	 * @return client's operating system
	 */
	public static DeviceOperatingSystem getOperatingSystem(Connection connection) {
		return DeviceOperatingSystem.getOperatingSystemById((int) getClientInformationMap(connection).get("DeviceOS"));
	}

	/***
	 * Gets the client version
	 * @param connection
	 * @return client version
	 */
	public static String getClientVersion(Connection connection) {
		return (String) getClientInformationMap(connection).get("GameVersion");
	}

	/***
	 * Gets the client's information map.
	 * @param connection
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static HashMap<String, Object> getClientInformationMap(Connection connection) {
		return (HashMap<String, Object>) connection.getMetadata(StuffUtils.CLIENT_INFO_KEY);
	}

}