content: /*
   *  ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
   *  Copyright (C) 2012 Kristian S. Stangeland
   *
   *  This program is free software; you can redistribute it and/or modify it under the terms of the
   *  GNU General Public License as published by the Free Software Foundation; either version 2 of
   *  the License, or (at your option) any later version.
   *
   *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
   *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
   *  See the GNU General Public License for more details.
   *
   *  You should have received a copy of the GNU General Public License along with this program;
   *  if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   *  02111-1307 USA
   */
  
  package com.comphenix.protocol.events;
  
  import javax.annotation.Nonnull;
  import java.util.Set;
  
  import com.comphenix.protocol.PacketType;
  import com.comphenix.protocol.injector.GamePhase;
  import com.google.common.base.Preconditions;
  import com.google.common.collect.Iterables;
  import com.google.common.collect.Sets;
  
  import org.bukkit.plugin.Plugin;
  
  /**
   * Represents a packet listener with useful constructors.
   * <p>
   * Remember to override onPacketReceiving() and onPacketSending(), depending on the ConnectionSide.
   *
   * @author Kristian
   */
  public abstract class PacketAdapter implements PacketListener {
  
  	protected Plugin plugin;
  	protected ConnectionSide connectionSide;
  	protected ListeningWhitelist receivingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
  	protected ListeningWhitelist sendingWhitelist = ListeningWhitelist.EMPTY_WHITELIST;
  
  	/**
  	 * Initialize a packet adapter using a collection of parameters. Use {@link #params()} to get an instance to this
  	 * builder.
  	 *
  	 * @param params - the parameters.
  	 */
  	public PacketAdapter(@Nonnull AdapterParameteters params) {
  		this(
  				checkValidity(params).plugin, params.connectionSide, params.listenerPriority,
  				params.gamePhase, params.options, params.packets
  		);
  	}
  
  	/**
  	 * Initialize a packet listener with the given parameters.
  	 *
  	 * @param plugin - the plugin.
  	 * @param types  - the packet types.
  	 */
  	public PacketAdapter(Plugin plugin, PacketType... types) {
  		this(plugin, ListenerPriority.NORMAL, types);
  	}
  
  	/**
  	 * Initialize a packet listener with the given parameters.
  	 *
  	 * @param plugin - the plugin.
  	 * @param types  - the packet types.
  	 */
  	public PacketAdapter(Plugin plugin, Iterable<? extends PacketType> types) {
  		this(params(plugin, Iterables.toArray(types, PacketType.class)));
  	}
  
  	/**
  	 * Initialize a packet listener with the given parameters.
  	 *
  	 * @param plugin           - the plugin.
  	 * @param listenerPriority - the priority.
  	 * @param types            - the packet types.
  	 */
  	public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types) {
  		this(params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority));
  	}
  
  	/**
  	 * Initialize a packet listener with the given parameters.
  	 *
  	 * @param plugin           - the plugin.
  	 * @param listenerPriority - the priority.
  	 * @param types            - the packet types.
  	 * @param options          - the options.
  	 */
  	public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, Iterable<? extends PacketType> types,
  			ListenerOptions... options) {
  		this(
  				params(plugin, Iterables.toArray(types, PacketType.class)).listenerPriority(listenerPriority).options(options));
  	}
  
  	/**
  	 * Initialize a packet listener with the given parameters.
  	 *
  	 * @param plugin           - the plugin.
  	 * @param listenerPriority - the priority.
  	 * @param types            - the packet types.
  	 */
  	public PacketAdapter(Plugin plugin, ListenerPriority listenerPriority, PacketType... types) {
  		this(params(plugin, types).listenerPriority(listenerPriority));
  	}
  
  	// For internal use only
  	private PacketAdapter(
  			Plugin plugin, ConnectionSide connectionSide, ListenerPriority listenerPriority,
  			GamePhase gamePhase, ListenerOptions[] options, PacketType... packets) {
  
  		if (plugin == null) {
  			throw new IllegalArgumentException("plugin cannot be null");
  		}
  		if (connectionSide == null) {
  			throw new IllegalArgumentException("connectionSide cannot be null");
  		}
  		if (listenerPriority == null) {
  			throw new IllegalArgumentException("listenerPriority cannot be null");
  		}
  		if (gamePhase == null) {
  			throw new IllegalArgumentException("gamePhase cannot be NULL");
  		}
  		if (packets == null) {
  			throw new IllegalArgumentException("packets cannot be null");
  		}
  		if (options == null) {
  			throw new IllegalArgumentException("options cannot be null");
  		}
  
  		// Add whitelists
  		if (connectionSide.isForServer()) {
  			sendingWhitelist = ListeningWhitelist.newBuilder()
  					.priority(listenerPriority)
  					.types(packets)
  					.gamePhase(gamePhase)
  					.options(options)
  					.build();
  		}
  
  		if (connectionSide.isForClient()) {
  			receivingWhitelist = ListeningWhitelist.newBuilder()
  					.priority(listenerPriority)
  					.types(packets)
  					.gamePhase(gamePhase)
  					.options(options)
  					.build();
  		}
  
  		this.plugin = plugin;
  		this.connectionSide = connectionSide;
  	}
  
  	/**
  	 * Retrieves the name of the plugin that has been associated with the listener.
  	 *
  	 * @param listener - the listener.
  	 * @return Name of the associated plugin.
  	 */
  	public static String getPluginName(PacketListener listener) {
  		return getPluginName(listener.getPlugin());
  	}
  
  	/**
  	 * Retrieves the name of the given plugin.
  	 *
  	 * @param plugin - the plugin.
  	 * @return Name of the given plugin.
  	 */
  	public static String getPluginName(Plugin plugin) {
  		if (plugin == null) {
  			return "UNKNOWN";
  		}
  
  		try {
  			return plugin.getName();
  		} catch (NoSuchMethodError e) {
  			return plugin.toString();
  		}
  	}
  
  	/**
  	 * Construct a helper object for passing parameters to the packet adapter.
  	 * <p>
  	 * This is often simpler and better than passing them directly to each constructor.
  	 *
  	 * @return Helper object.
  	 */
  	public static AdapterParameteters params() {
  		return new AdapterParameteters();
  	}
  
  	/**
  	 * Construct a helper object for passing parameters to the packet adapter.
  	 * <p>
  	 * This is often simpler and better than passing them directly to each constructor.
  	 *
  	 * @param plugin  - the plugin that spawned this listener.
  	 * @param packets - the packet types the listener is looking for.
  	 * @return Helper object.
  	 */
  	public static AdapterParameteters params(Plugin plugin, PacketType... packets) {
  		return new AdapterParameteters().plugin(plugin).types(packets);
  	}
  
  	/**
  	 * Determine if the required parameters are set.
  	 */
  	private static AdapterParameteters checkValidity(AdapterParameteters params) {
  		if (params == null) {
  			throw new IllegalArgumentException("params cannot be NULL.");
  		}
  		if (params.plugin == null) {
  			throw new IllegalStateException("Plugin was never set in the parameters.");
  		}
  		if (params.connectionSide == null) {
  			throw new IllegalStateException("Connection side was never set in the parameters.");
  		}
  		if (params.packets == null) {
  			throw new IllegalStateException("Packet IDs was never set in the parameters.");
  		}
  		return params;
  	}
  
  	@Override
  	public void onPacketReceiving(PacketEvent event) {
  		// Lets prevent some bugs
  		throw new IllegalStateException("Override onPacketReceiving to get notifcations of received packets!");
  	}
  
  	@Override
  	public void onPacketSending(PacketEvent event) {
  		// Lets prevent some bugs
  		throw new IllegalStateException("Override onPacketSending to get notifcations of sent packets!");
  	}
  
  	@Override
  	public ListeningWhitelist getReceivingWhitelist() {
  		return receivingWhitelist;
  	}
  
  	@Override
  	public ListeningWhitelist getSendingWhitelist() {
  		return sendingWhitelist;
  	}
  
  	@Override
  	public Plugin getPlugin() {
  		return plugin;
  	}
  
  	@Override
  	public String toString() {
  		// This is used by the error reporter
  		return String.format("PacketAdapter[plugin=%s, sending=%s, receiving=%s]",
  				getPluginName(this),
  				sendingWhitelist,
  				receivingWhitelist);
  	}
  
  	/**
  	 * Represents a builder for passing parameters to the packet adapter constructor.
  	 * <p>
  	 * Note: Never make spelling mistakes in a public API!
  	 *
  	 * @author Kristian
  	 */
  	public static class AdapterParameteters {
  
  		private Plugin plugin;
  		private ConnectionSide connectionSide;
  		private PacketType[] packets;
  
  		// Parameters with default values
  		private GamePhase gamePhase = GamePhase.PLAYING;
  		private ListenerOptions[] options = new ListenerOptions[0];
  		private ListenerPriority listenerPriority = ListenerPriority.NORMAL;
  
  		/**
  		 * Set the plugin that spawned this listener. This parameter is required.
  		 *
  		 * @param plugin - the plugin.
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters plugin(@Nonnull Plugin plugin) {
  			this.plugin = Preconditions.checkNotNull(plugin, "plugin cannot be NULL.");
  			return this;
  		}
  
  		/**
  		 * Set the packet types this listener is looking for. This parameter is required.
  		 *
  		 * @param connectionSide - the new packet type.
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters connectionSide(@Nonnull ConnectionSide connectionSide) {
  			this.connectionSide = Preconditions.checkNotNull(connectionSide, "connectionside cannot be NULL.");
  			return this;
  		}
  
  		/**
  		 * Set this adapter to also look for client-side packets.
  		 *
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters clientSide() {
  			return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.CLIENT_SIDE));
  		}
  
  		/**
  		 * Set this adapter to also look for server-side packets.
  		 *
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters serverSide() {
  			return connectionSide(ConnectionSide.add(connectionSide, ConnectionSide.SERVER_SIDE));
  		}
  
  		/**
  		 * Set the the event priority, where the execution is in ascending order from lowest to highest.
  		 * <p>
  		 * Default is {@link ListenerPriority#NORMAL}.
  		 *
  		 * @param listenerPriority - the new event priority.
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters listenerPriority(@Nonnull ListenerPriority listenerPriority) {
  			this.listenerPriority = Preconditions.checkNotNull(listenerPriority, "listener priority cannot be NULL.");
  			return this;
  		}
  
  		/**
  		 * Set which game phase this listener is active under. This is a hint for ProtocolLib to start intercepting login
  		 * packets.
  		 * <p>
  		 * Default is {@link GamePhase#PLAYING}, which will not intercept login packets.
  		 *
  		 * @param gamePhase - the new game phase.
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters gamePhase(@Nonnull GamePhase gamePhase) {
  			this.gamePhase = Preconditions.checkNotNull(gamePhase, "gamePhase cannot be NULL.");
  			return this;
  		}
  
  		/**
  		 * Set the game phase to {@link GamePhase#LOGIN}, allowing ProtocolLib to intercept login packets.
  		 *
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters loginPhase() {
  			return gamePhase(GamePhase.LOGIN);
  		}
  
  		/**
  		 * Set listener options that decide whether or not to intercept the raw packet data as read from the network
  		 * stream.
  		 * <p>
  		 * The default is to disable this raw packet interception.
  		 *
  		 * @param options - every option to use.
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters options(@Nonnull ListenerOptions... options) {
  			this.options = Preconditions.checkNotNull(options, "options cannot be NULL.");
  			return this;
  		}
  
  		/**
  		 * Set listener options that decide whether or not to intercept the raw packet data as read from the network
  		 * stream.
  		 * <p>
  		 * The default is to disable this raw packet interception.
  		 *
  		 * @param options - every option to use.
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters options(@Nonnull Set<? extends ListenerOptions> options) {
  			Preconditions.checkNotNull(options, "options cannot be NULL.");
  			this.options = options.toArray(new ListenerOptions[0]);
  			return this;
  		}
  
  		/**
  		 * Add a given option to the current builder.
  		 *
  		 * @param option - the option to add.
  		 * @return This builder, for chaining.
  		 */
  		private AdapterParameteters addOption(ListenerOptions option) {
  			if (options == null) {
  				return options(option);
  			} else {
  				final Set<ListenerOptions> current = Sets.newHashSet(options);
  				current.add(option);
  				return options(current);
  			}
  		}
  
  		/**
  		 * Set the listener option to {@link ListenerOptions#ASYNC}, indicating that our outbound listener is thread safe.
  		 * <p>
  		 * This allows ProtocolLib to perform certain optimizations.
  		 *
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters optionAsync() {
  			return addOption(ListenerOptions.ASYNC);
  		}
  
  		/**
  		 * Set the listener option to {@link ListenerOptions#SYNC}, indicating that our inbound listener must be executed
  		 * on the main server thread.
  		 *
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters optionSync() {
  			return addOption(ListenerOptions.SYNC);
  		}
  
  		/**
  		 * Set the packet types the listener is looking for.
  		 * <p>
  		 * This parameter is required.
  		 *
  		 * @param packets - the packet types to look for.
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters types(@Nonnull PacketType... packets) {
  			// Set the connection side as well
  			if (connectionSide == null) {
  				for (PacketType type : packets) {
  					this.connectionSide = ConnectionSide.add(this.connectionSide, type.getSender().toSide());
  				}
  			}
  			this.packets = Preconditions.checkNotNull(packets, "packets cannot be NULL");
  
  			if (packets.length == 0) {
  				throw new IllegalArgumentException("Passed an empty packet type array.");
  			}
  			return this;
  		}
  
  		/**
  		 * Set the packet types the listener is looking for.
  		 * <p>
  		 * This parameter is required.
  		 *
  		 * @param packets - a set of packet types to look for.
  		 * @return This builder, for chaining.
  		 */
  		public AdapterParameteters types(@Nonnull Set<PacketType> packets) {
  			return types(packets.toArray(new PacketType[0]));
  		}
  	}
  }
  