package net.md_5.bungee.api.connection;

import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ServerConnectRequest;
import net.md_5.bungee.api.ServerLink;
import net.md_5.bungee.api.SkinConfiguration;
import net.md_5.bungee.api.Title;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.dialog.Dialog;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.score.Scoreboard;
import org.jetbrains.annotations.ApiStatus;

/**
 * Represents a player whose connection is being connected to somewhere else,
 * whether it be a remote or embedded server.
 */
public interface ProxiedPlayer extends Connection, CommandSender
{

    /**
     * Represents the player's chat state.
     */
    public enum ChatMode
    {

        /**
         * The player will see all chat.
         */
        SHOWN,
        /**
         * The player will only see everything except messages marked as chat.
         */
        COMMANDS_ONLY,
        /**
         * The chat is completely disabled, the player won't see anything.
         */
        HIDDEN;

    }

    public enum MainHand
    {

        LEFT,
        RIGHT;
    }

    /**
     * Gets this player's display name.
     *
     * @return the players current display name
     */
    String getDisplayName();

    /**
     * Sets this player's display name to be used by proxy commands and plugins.
     *
     * @param name the name to set
     */
    void setDisplayName(String name);

    /**
     * Send a message to the specified screen position of this player.
     *
     * @param position the screen position
     * @param message the message to send
     */
    public void sendMessage(ChatMessageType position, BaseComponent... message);

    /**
     * Send a message to the specified screen position of this player.
     *
     * @param position the screen position
     * @param message the message to send
     */
    public void sendMessage(ChatMessageType position, BaseComponent message);

    /**
     * Send a message to this player.
     *
     * @param sender the sender of the message
     * @param message the message to send
     */
    public void sendMessage(UUID sender, BaseComponent... message);

    /**
     * Send a message to this player.
     *
     * @param sender the sender of the message
     * @param message the message to send
     */
    public void sendMessage(UUID sender, BaseComponent message);

    /**
     * Connects / transfers this user to the specified connection, gracefully
     * closing the current one. Depending on the implementation, this method
     * might return before the user has been connected.
     *
     * @param target the new server to connect to
     */
    void connect(ServerInfo target);

    /**
     * Connects / transfers this user to the specified connection, gracefully
     * closing the current one. Depending on the implementation, this method
     * might return before the user has been connected.
     *
     * @param target the new server to connect to
     * @param reason the reason for connecting to the new server
     */
    void connect(ServerInfo target, ServerConnectEvent.Reason reason);

    /**
     * Connects / transfers this user to the specified connection, gracefully
     * closing the current one. Depending on the implementation, this method
     * might return before the user has been connected.
     *
     * @param target the new server to connect to
     * @param callback the method called when the connection is complete, or
     * when an exception is encountered. The boolean parameter denotes success
     * (true) or failure (false).
     */
    void connect(ServerInfo target, Callback<Boolean> callback);

    /**
     * Connects / transfers this user to the specified connection, gracefully
     * closing the current one. Depending on the implementation, this method
     * might return before the user has been connected.
     *
     * @param target the new server to connect to
     * @param callback the method called when the connection is complete, or
     * when an exception is encountered. The boolean parameter denotes success
     * (true) or failure (false).
     * @param reason the reason for connecting to the new server
     */
    void connect(ServerInfo target, Callback<Boolean> callback, ServerConnectEvent.Reason reason);

    /**
     * Connects / transfers this user to the specified connection, gracefully
     * closing the current one. Depending on the implementation, this method
     * might return before the user has been connected.
     *
     * @param request request to connect with
     */
    void connect(ServerConnectRequest request);

    /**
     * Gets the server this player is connected to.
     *
     * @return the server this player is connected to
     */
    Server getServer();

    /**
     * Gets the ping time between the proxy and this connection.
     *
     * @return the current ping time
     */
    int getPing();

    /**
     * Send a plugin message to this player.
     *
     * In recent Minecraft versions channel names must contain a colon separator
     * and consist of [a-z0-9/._-]. This will be enforced in a future version.
     * The "BungeeCord" channel is an exception and may only take this form.
     *
     * @param channel the channel to send this data via
     * @param data the data to send
     */
    void sendData(String channel, byte[] data);

    /**
     * Get the pending connection that belongs to this player.
     *
     * @return the pending connection that this player used
     */
    PendingConnection getPendingConnection();

    /**
     * Make this player chat (say something), to the server he is currently on.
     *
     * @param message the message to say
     */
    void chat(String message);

    /**
     * Get the server which this player will be sent to next time the log in.
     *
     * @return the server, or null if default
     */
    ServerInfo getReconnectServer();

    /**
     * Set the server which this player will be sent to next time the log in.
     *
     * @param server the server to set
     */
    void setReconnectServer(ServerInfo server);

    /**
     * Get this connection's UUID, if set.
     *
     * @return the UUID
     * @deprecated In favour of {@link #getUniqueId()}
     */
    @Deprecated
    String getUUID();

    /**
     * Get this connection's UUID, if set.
     *
     * @return the UUID
     */
    UUID getUniqueId();

    /**
     * Gets this player's locale.
     *
     * @return the locale
     */
    Locale getLocale();

    /**
     * Gets this player's view distance.
     *
     * @return the view distance, or a reasonable default
     */
    byte getViewDistance();

    /**
     * Gets this player's chat mode.
     *
     * @return the chat flags set, or a reasonable default
     */
    ChatMode getChatMode();

    /**
     * Gets if this player has chat colors enabled or disabled.
     *
     * @return if chat colors are enabled
     */
    boolean hasChatColors();

    /**
     * Gets this player's skin settings.
     *
     * @return the players skin setting
     */
    SkinConfiguration getSkinParts();

    /**
     * Gets this player's main hand setting.
     *
     * @return main hand setting
     */
    MainHand getMainHand();

    /**
     * Set the header and footer displayed in the tab player list.
     *
     * @param header The header for the tab player list, null to clear it.
     * @param footer The footer for the tab player list, null to clear it.
     */
    void setTabHeader(BaseComponent header, BaseComponent footer);

    /**
     * Set the header and footer displayed in the tab player list.
     *
     * @param header The header for the tab player list, null to clear it.
     * @param footer The footer for the tab player list, null to clear it.
     */
    void setTabHeader(BaseComponent[] header, BaseComponent[] footer);

    /**
     * Clears the header and footer displayed in the tab player list.
     */
    void resetTabHeader();

    /**
     * Sends a {@link Title} to this player. This is the same as calling
     * {@link Title#send(ProxiedPlayer)}.
     *
     * @param title The title to send to the player.
     * @see Title
     */
    void sendTitle(Title title);

    /**
     * Gets whether this player is using a FML client.
     * <p>
     * This method is only reliable if BungeeCord links Minecraft 1.8 servers
     * together, as Bungee can pick up whether a user is a Forge user with the
     * initial handshake. If this is used for a 1.7 network, this might return
     * <code>false</code> even if the user is a FML user, as Bungee can only
     * determine this information if a handshake successfully completes.
     * </p>
     *
     * @return <code>true</code> if it is known that the user is using a FML
     * client, <code>false</code> otherwise.
     */
    boolean isForgeUser();

    /**
     * Gets this player's Forge Mod List, if the player has sent this
     * information during the lifetime of their connection to Bungee. There is
     * no guarantee that information is available at any time, as it is only
     * sent during a FML handshake. Therefore, this will only contain
     * information for a user that has attempted joined a Forge server.
     * <p>
     * Consumers of this API should be aware that an empty mod list does
     * <em>not</em> indicate that a user is not a Forge user, and so should not
     * use this API to check for this. See the {@link #isForgeUser()
     * isForgeUser} method instead.
     * </p>
     * <p>
     * Calling this when handling a
     * {@link net.md_5.bungee.api.event.ServerConnectedEvent} may be the best
     * place to do so as this event occurs after a FML handshake has completed,
     * if any has occurred.
     * </p>
     *
     * @return A {@link Map} of mods, where the key is the name of the mod, and
     * the value is the version. Returns an empty list if the FML handshake has
     * not occurred for this {@link ProxiedPlayer} yet.
     */
    Map<String, String> getModList();

    /**
     * Get the {@link Scoreboard} that belongs to this player.
     *
     * @return this player's {@link Scoreboard}
     * @deprecated for internal use only, setters will not have the expected
     * effect, will not update client state, and may corrupt proxy state
     */
    @Deprecated
    Scoreboard getScoreboard();

    /**
     * Retrieves a cookie from this player.
     *
     * @param cookie the resource location of the cookie, for example
     * "bungeecord:my_cookie"
     * @return a {@link CompletableFuture} that will be completed when the
     * Cookie response is received. If the cookie is not set in the client, the
     * {@link CompletableFuture} will complete with a null value
     * @throws IllegalStateException if the player's version is not at least
     * 1.20.5
     */
    @ApiStatus.Experimental
    CompletableFuture<byte[]> retrieveCookie(String cookie);

    /**
     * Stores a cookie in this player's client.
     *
     * @param cookie the resource location of the cookie, for example
     * "bungeecord:my_cookie"
     * @param data the data to store in the cookie
     * @throws IllegalStateException if the player's version is not at least
     * 1.20.5
     */
    @ApiStatus.Experimental
    void storeCookie(String cookie, byte[] data);

    /**
     * Requests this player to connect to a different server specified by host
     * and port.
     *
     * This is a client-side transfer - host and port should not specify a
     * BungeeCord backend server.
     *
     * @param host the host of the server to transfer to
     * @param port the port of the server to transfer to
     * @throws IllegalStateException if the players version is not at least
     * 1.20.5
     */
    @ApiStatus.Experimental
    void transfer(String host, int port);

    /**
     * Gets the client brand of this player.
     *
     * If the player has not sent a brand packet yet, it will return null.
     *
     * @return the brand of the client, or null if not received yet
     */
    String getClientBrand();

    /**
     * Clear the player's open dialog.
     *
     * @throws IllegalStateException if the players version is not at least
     * 1.21.6
     */
    @ApiStatus.Experimental
    void clearDialog();

    /**
     * Show a dialog to the player.
     *
     * @param dialog the dialog to show
     * @throws IllegalStateException if the players version is not at least
     * 1.21.6
     */
    @ApiStatus.Experimental
    void showDialog(Dialog dialog);

    /**
     * Sends server links to the player.
     *
     * Note: The links already sent to the player will be overwritten. Also, the
     * backend server is able to override links sent by the proxy.
     *
     * @param serverLinks the server links to send
     * @throws IllegalStateException if the player's version is not at least
     * 1.21
     */
    void sendServerLinks(List<ServerLink> serverLinks);
}
