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 java.io.IOException;
  import java.io.ObjectInputStream;
  import java.io.ObjectOutputStream;
  import java.lang.ref.WeakReference;
  import java.util.EventObject;
  
  import com.comphenix.protocol.PacketType;
  import com.comphenix.protocol.ProtocolLibrary;
  import com.comphenix.protocol.async.AsyncMarker;
  import com.comphenix.protocol.error.PluginContext;
  import com.comphenix.protocol.error.Report;
  import com.comphenix.protocol.error.ReportType;
  import com.comphenix.protocol.injector.temporary.TemporaryPlayer;
  import com.google.common.base.Objects;
  import com.google.common.base.Preconditions;
  import com.google.common.collect.HashMultimap;
  import com.google.common.collect.Multimaps;
  import com.google.common.collect.SetMultimap;
  
  import org.bukkit.Bukkit;
  import org.bukkit.entity.Player;
  import org.bukkit.event.Cancellable;
  
  import javax.annotation.Nullable;
  
  /**
   * Represents a packet sending or receiving event. Changes to the packet will be reflected in the final version to be
   * sent or received. It is also possible to cancel an event.
   *
   * @author Kristian
   */
  public class PacketEvent extends EventObject implements Cancellable {
  
  	public static final ReportType REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING = new ReportType(
  			"Plugin %s changed packet type from %s to %s in packet listener. This is confusing for other plugins! (Not an error, though!)");
  
  	private static final SetMultimap<PacketType, PacketType> CHANGE_WARNINGS =
  			Multimaps.synchronizedSetMultimap(HashMultimap.create());
  
  	/**
  	 * Automatically generated by Eclipse.
  	 */
  	private static final long serialVersionUID = -5360289379097430620L;
  	// Network input and output handlers
  	NetworkMarker networkMarker;
  	private transient WeakReference<Player> playerReference;
  	private PacketContainer packet;
  	private boolean serverPacket;
  	private boolean cancel;
  	private AsyncMarker asyncMarker;
  	private boolean asynchronous;
  	// Whether or not a packet event is read only
  	private boolean readOnly;
  	private boolean filtered;
  
  	@Nullable
  	private PacketEvent bundle;
  
  	/**
  	 * Use the static constructors to create instances of this event.
  	 *
  	 * @param source - the event source.
  	 */
  	public PacketEvent(Object source) {
  		super(source);
  		this.filtered = true;
  	}
  
  	private PacketEvent(Object source, PacketContainer packet, Player player, boolean serverPacket) {
  		this(source, packet, null, player, serverPacket, true, null);
  	}
  
  	private PacketEvent(Object source, PacketContainer packet, NetworkMarker marker, Player player, boolean serverPacket,
  			boolean filtered, @Nullable PacketEvent bundleEvent) {
  		super(source);
  		if(packet == null) {
  			throw new IllegalArgumentException("packet cannot be null");
  		}
  		this.packet = packet;
  		this.playerReference = new WeakReference<>(player);
  		this.networkMarker = marker;
  		this.serverPacket = serverPacket;
  		this.filtered = filtered;
  		this.bundle = bundleEvent;
  	}
  
  	private PacketEvent(PacketEvent original, AsyncMarker asyncMarker) {
  		super(original.source);
  		this.packet = original.packet;
  		this.playerReference = original.getPlayerReference();
  		this.cancel = original.cancel;
  		this.serverPacket = original.serverPacket;
  		this.filtered = original.filtered;
  		this.networkMarker = original.networkMarker;
  		this.asyncMarker = asyncMarker;
  		this.asynchronous = true;
  	}
  
  	/**
  	 * Creates an event representing a client packet transmission.
  	 *
  	 * @param source - the event source.
  	 * @param packet - the packet.
  	 * @param client - the client that sent the packet.
  	 * @return The event.
  	 */
  	public static PacketEvent fromClient(Object source, PacketContainer packet, Player client) {
  		return new PacketEvent(source, packet, client, false);
  	}
  
  	/**
  	 * Creates an event representing a client packet transmission.
  	 *
  	 * @param source - the event source.
  	 * @param packet - the packet.
  	 * @param marker - the network marker.
  	 * @param client - the client that sent the packet.
  	 * @return The event.
  	 */
  	public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client) {
  		return new PacketEvent(source, packet, marker, client, false, true, null);
  	}
  
  	/**
  	 * Creates an event representing a client packet transmission.
  	 * <p>
  	 * If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
  	 *
  	 * @param source   - the event source.
  	 * @param packet   - the packet.
  	 * @param marker   - the network marker.
  	 * @param client   - the client that sent the packet.
  	 * @param filtered - whether or not this packet event is processed by every packet listener.
  	 * @return The event.
  	 */
  	public static PacketEvent fromClient(Object source, PacketContainer packet, NetworkMarker marker, Player client,
  			boolean filtered) {
  		return new PacketEvent(source, packet, marker, client, false, filtered, null);
  	}
  
  	/**
  	 * Creates an event representing a server packet transmission.
  	 *
  	 * @param source    - the event source.
  	 * @param packet    - the packet.
  	 * @param recipient - the client that will receieve the packet.
  	 * @return The event.
  	 */
  	public static PacketEvent fromServer(Object source, PacketContainer packet, Player recipient) {
  		return new PacketEvent(source, packet, recipient, true);
  	}
  
  	/**
  	 * Creates an event representing a server packet transmission.
  	 *
  	 * @param source    - the event source.
  	 * @param packet    - the packet.
  	 * @param marker    - the network marker.
  	 * @param recipient - the client that will receieve the packet.
  	 * @return The event.
  	 */
  	public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient) {
  		return new PacketEvent(source, packet, marker, recipient, true, true, null);
  	}
  
  	/**
  	 * Creates an event representing a server packet transmission.
  	 * <p>
  	 * If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
  	 *
  	 * @param source    - the event source.
  	 * @param packet    - the packet.
  	 * @param marker    - the network marker.
  	 * @param recipient - the client that will receieve the packet.
  	 * @param filtered  - whether or not this packet event is processed by every packet listener.
  	 * @return The event.
  	 */
  	public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
  			boolean filtered) {
  		return fromServer(source, packet, marker, recipient, filtered, null);
  	}
  
  	/**
  	 * Creates an event representing a server packet transmission.
  	 * <p>
  	 * If <i>filtered</i> is FALSE, then this event is only processed by packet monitors.
  	 *
  	 * @param source    - the event source.
  	 * @param packet    - the packet.
  	 * @param marker    - the network marker.
  	 * @param recipient - the client that will receieve the packet.
  	 * @param filtered  - whether this packet event is processed by every packet listener.
  	 * @param bundle    - The corresponding packet event of the bundle if this packet is part of a bundle. Otherwise, null.
  	 * @return The event.
  	 */
  	public static PacketEvent fromServer(Object source, PacketContainer packet, NetworkMarker marker, Player recipient,
  			boolean filtered, @Nullable PacketEvent bundle) {
  		return new PacketEvent(source, packet, marker, recipient, true, filtered, bundle);
  	}
  
  	/**
  	 * Create an asynchronous packet event from a synchronous event and a async marker.
  	 *
  	 * @param event  - the original synchronous event.
  	 * @param marker - the asynchronous marker.
  	 * @return The new packet event.
  	 */
  	public static PacketEvent fromSynchronous(PacketEvent event, AsyncMarker marker) {
  		return new PacketEvent(event, marker);
  	}
  
  	/**
  	 * Determine if we are executing the packet event in an asynchronous thread.
  	 * <p>
  	 * If so, you must synchronize all calls to the Bukkit API.
  	 * <p>
  	 * Generally, most server packets are executed on the main thread, whereas client packets are all executed
  	 * asynchronously.
  	 *
  	 * @return TRUE if we are, FALSE otherwise.
  	 */
  	public boolean isAsync() {
  		return !Bukkit.isPrimaryThread();
  	}
  
  	/**
  	 * Retrieves the packet that will be sent to the player.
  	 *
  	 * @return Packet to send to the player.
  	 */
  	public PacketContainer getPacket() {
  		return packet;
  	}
  
  	/**
  	 * Replace the packet that will be sent to the player.
  	 *
  	 * @param packet - the packet that will be sent instead.
  	 */
  	public void setPacket(PacketContainer packet) {
  		if (readOnly) {
  			throw new IllegalStateException("The packet event is read-only.");
  		}
  		if (packet == null) {
  			throw new IllegalArgumentException("Cannot set packet to NULL. Use setCancelled() instead.");
  		}
  
  		// Change warnings
  		final PacketType oldType = this.packet.getType();
  		final PacketType newType = packet.getType();
  		if (this.packet != null && !Objects.equal(oldType, newType)) {
  			// Only report this once
  			if (CHANGE_WARNINGS.put(oldType, newType)) {
  				ProtocolLibrary.getErrorReporter().reportWarning(this,
  						Report.newBuilder(REPORT_CHANGING_PACKET_TYPE_IS_CONFUSING).
  								messageParam(PluginContext.getPluginCaller(new Exception()), oldType, newType).
  								build());
  			}
  		}
  		this.packet = packet;
  	}
  
  	/**
  	 * Retrieves the packet ID.
  	 * <p>
  	 * Deprecated: Use {@link #getPacketType()} instead.
  	 *
  	 * @return The current packet ID.
  	 */
  	@Deprecated
  	public int getPacketID() {
  		return packet.getId();
  	}
  
  	/**
  	 * Retrieve the packet type.
  	 *
  	 * @return The type.
  	 */
  	public PacketType getPacketType() {
  		return packet.getType();
  	}
  
  	/**
  	 * Retrieves whether or not the packet should be cancelled.
  	 *
  	 * @return TRUE if it should be cancelled, FALSE otherwise.
  	 */
  	@Override
  	public boolean isCancelled() {
  		return cancel;
  	}
  
  	/**
  	 * Sets whether or not the packet should be cancelled. Uncancelling is possible.
  	 * <p>
  	 * <b>Warning</b>: A cancelled packet should never be re-transmitted. Use the asynchronous
  	 * packet manager if you need to perform extensive processing. It should also be used if you need to synchronize with
  	 * the main thread.
  	 * <p>
  	 * This ensures that other plugins can work with the same packet.
  	 * <p>
  	 * An asynchronous listener can also delay a packet indefinitely without having to block its thread.
  	 *
  	 * @param cancel - TRUE if it should be cancelled, FALSE otherwise.
  	 */
  	@Override
  	public void setCancelled(boolean cancel) {
  		if (readOnly) {
  			throw new IllegalStateException("The packet event is read-only.");
  		}
  		this.cancel = cancel;
  	}
  
  	/**
  	 * Retrieve the object responsible for managing the serialized input and output of a packet.
  	 * <p>
  	 * Note that the serialized input data is only available for client-side packets, and the output handlers can only be
  	 * applied to server-side packets.
  	 *
  	 * @return The network manager.
  	 */
  	public NetworkMarker getNetworkMarker() {
  		if (networkMarker == null) {
  			if (isServerPacket()) {
  				networkMarker = new NetworkMarker(serverPacket ? ConnectionSide.SERVER_SIDE : ConnectionSide.CLIENT_SIDE, this.getPacketType());
  			} else {
  				throw new IllegalStateException("Add the option ListenerOptions.INTERCEPT_INPUT_BUFFER to your listener.");
  			}
  		}
  		return networkMarker;
  	}
  
  	/**
  	 * Update the network manager.
  	 * <p>
  	 * This method is internal - do not call.
  	 *
  	 * @param networkMarker - the new network manager.
  	 */
  	public void setNetworkMarker(NetworkMarker networkMarker) {
  		this.networkMarker = Preconditions.checkNotNull(networkMarker, "marker cannot be NULL");
  	}
  
  	private WeakReference<Player> getPlayerReference() {
  		Player player = playerReference.get();
  
  		if (player instanceof TemporaryPlayer) {
  			Player updated = player.getPlayer();
  			if (updated != null && !(updated instanceof TemporaryPlayer)) {
  				playerReference.clear();
  				playerReference = new WeakReference<>(updated);
  			}
  		}
  
  		return playerReference;
  	}
  
  	/**
  	 * Retrieves the player that has sent the packet or is receiving it.
  	 *
  	 * @return The player associated with this event.
  	 */
  	public Player getPlayer() {
  		return getPlayerReference().get();
  	}
  
  	/**
  	 * Whether or not the player in this PacketEvent is temporary (i.e. they don't have a true player instance yet).
  	 * Temporary players have a limited subset of methods that may be used:
  	 * <ul>
  	 *     <li>getPlayer</li>
  	 *     <li>getAddress</li>
  	 *     <li>getServer</li>
  	 *     <li>chat</li>
  	 *     <li>sendMessage</li>
  	 *     <li>kickPlayer</li>
  	 *     <li>isOnline</li>
  	 * </ul>
  	 * <p>
  	 * Anything else will throw an UnsupportedOperationException. Use this check before calling other methods when
  	 * dealing with packets early in the login sequence or if you get the aforementioned exception.
  	 *
  	 * @return True if the player is temporary, false if not.
  	 */
  	public boolean isPlayerTemporary() {
  		return getPlayer() instanceof TemporaryPlayer;
  	}
  
  	/**
  	 * Determine if this packet is filtered by every packet listener.
  	 * <p>
  	 * If not, it will only be intercepted by monitor packets.
  	 *
  	 * @return TRUE if it is, FALSE otherwise.
  	 */
  	public boolean isFiltered() {
  		return filtered;
  	}
  
  	/**
  	 * Whether or not this packet was created by the server.
  	 * <p>
  	 * Most listeners can deduce this by noting which listener method was invoked.
  	 *
  	 * @return TRUE if the packet was created by the server, FALSE if it was created by a client.
  	 */
  	public boolean isServerPacket() {
  		return serverPacket;
  	}
  
  	/**
  	 * Retrieve the asynchronous marker.
  	 * <p>
  	 * If the packet is synchronous, this marker will be used to schedule an asynchronous event. In the following
  	 * asynchronous event, the marker is used to correctly pass the packet around to the different threads.
  	 * <p>
  	 * Note that if there are no asynchronous events that can receive this packet, the marker is NULL.
  	 *
  	 * @return The current asynchronous marker, or NULL.
  	 */
  	public AsyncMarker getAsyncMarker() {
  		return asyncMarker;
  	}
  
  	/**
  	 * Set the asynchronous marker.
  	 * <p>
  	 * If the marker is non-null at the end of an synchronous event processing, the packet will be scheduled to be
  	 * processed asynchronously with the given settings.
  	 * <p>
  	 * Note that if there are no asynchronous events that can receive this packet, the marker should be NULL.
  	 *
  	 * @param asyncMarker - the new asynchronous marker, or NULL.
  	 * @throws IllegalStateException If the current event is asynchronous.
  	 */
  	public void setAsyncMarker(AsyncMarker asyncMarker) {
  		if (isAsynchronous()) {
  			throw new IllegalStateException("The marker is immutable for asynchronous events");
  		}
  		if (readOnly) {
  			throw new IllegalStateException("The packet event is read-only.");
  		}
  		this.asyncMarker = asyncMarker;
  	}
  
  	/**
  	 * Determine if the current packet event is read only.
  	 * <p>
  	 * This is used to ensure that a monitor listener doesn't accidentally alter the state of the event. However, it is
  	 * still possible to modify the packet itself, as it would require too many resources to verify its integrity.
  	 * <p>
  	 * Thus, the packet is considered immutable if the packet event is read only.
  	 *
  	 * @return TRUE if it is, FALSE otherwise.
  	 */
  	public boolean isReadOnly() {
  		return readOnly;
  	}
  
  	/**
  	 * Set the read-only state of this packet event.
  	 * <p>
  	 * This will be reset for every packet listener.
  	 *
  	 * @param readOnly - TRUE if it is read-only, FALSE otherwise.
  	 */
  	public void setReadOnly(boolean readOnly) {
  		this.readOnly = readOnly;
  	}
  
  	/**
  	 * Determine if the packet event has been executed asynchronously or not.
  	 *
  	 * @return TRUE if this packet event is asynchronous, FALSE otherwise.
  	 */
  	public boolean isAsynchronous() {
  		return asynchronous;
  	}
  
  	/**
  	 * Schedule a packet for sending or receiving after the current packet event is successful.
  	 * <p>
  	 * The packet will be added to {@link NetworkMarker#getScheduledPackets()}.
  	 *
  	 * @param scheduled - the packet to transmit or receive.
  	 */
  	public void schedule(ScheduledPacket scheduled) {
  		getNetworkMarker().getScheduledPackets().add(scheduled);
  	}
  
  	/**
  	 * Unschedule a specific packet.
  	 *
  	 * @param scheduled - the scheduled packet.
  	 * @return TRUE if it was unscheduled, FALSE otherwise.
  	 */
  	public boolean unschedule(ScheduledPacket scheduled) {
  		if (networkMarker != null) {
  			return networkMarker.getScheduledPackets().remove(scheduled);
  		}
  		return false;
  	}
  
  	private void writeObject(ObjectOutputStream output) throws IOException {
  		// Default serialization
  		output.defaultWriteObject();
  
  		// Write the name of the player (or NULL if it's not set)
  		Player player = getPlayer();
  		output.writeObject(player != null ? new SerializedOfflinePlayer(player) : null);
  	}
  
  	private void readObject(ObjectInputStream input) throws ClassNotFoundException, IOException {
  		// Default deserialization
  		input.defaultReadObject();
  
  		final SerializedOfflinePlayer serialized = (SerializedOfflinePlayer) input.readObject();
  
  		// Better than nothing
  		if (serialized != null) {
  			// Store it, to prevent weak reference from cleaning up the reference
  			Player offlinePlayer = serialized.getPlayer();
  			playerReference = new WeakReference<>(offlinePlayer);
  		}
  	}
  
  	/**
  	 * Returns the packet event corresponding to the bundle if this packet is sent as a part of a bundle, t. Otherwise, null.
  	 * @return Corresponding packet event or null.
  	 */
  	@Nullable
  	public PacketEvent getBundle() {
  		return bundle;
  	}
  
  	@Override
  	public String toString() {
  		return "PacketEvent[player=" + getPlayer() + ", packet=" + packet + "]";
  	}
  }
  