/* $Id: Message.java,v 1.21 2010/07/24 18:50:25 nhnb Exp $ */
/***************************************************************************
 *                   (C) Copyright 2003-2010 - Marauroa                    *
 ***************************************************************************
 ***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/
package marauroa.common.net.message;

import marauroa.common.net.InputSerializer;
import marauroa.common.net.NetConst;
import marauroa.common.net.OutputSerializer;
import marauroa.common.net.Serializable;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.channels.SocketChannel;

/**
 * Message is a class to represent all the kind of messages that are possible to
 * exist in marauroa.
 */
public class Message implements Serializable {

    /**
     * Invalid client identificator constant
     */
    public final static int CLIENTID_INVALID = -1;

    /**
     * Type of message
     */
    public enum MessageType {
        C2S_ACTION,
        C2S_CHOOSECHARACTER,
        C2S_LOGIN_REQUESTKEY,
        C2S_LOGIN_SENDNONCENAMEANDPASSWORD,
        C2S_LOGIN_SENDPROMISE,
        C2S_LOGOUT,
        C2S_OUTOFSYNC,
        C2S_TRANSFER_ACK,
        C2S_KEEPALIVE,
        S2C_CHARACTERLIST,
        S2C_CHOOSECHARACTER_ACK,
        S2C_CHOOSECHARACTER_NACK,
        S2C_INVALIDMESSAGE,
        S2C_LOGIN_ACK,
        S2C_LOGIN_NACK,
        S2C_LOGIN_SENDKEY,
        S2C_LOGIN_SENDNONCE,
        S2C_LOGOUT_ACK,
        S2C_LOGOUT_NACK,
        S2C_PERCEPTION,
        S2C_SERVERINFO,
        S2C_TRANSFER,
        S2C_TRANSFER_REQ,
        C2S_CREATEACCOUNT,
        S2C_CREATEACCOUNT_ACK,
        S2C_CREATEACCOUNT_NACK,
        C2S_CREATECHARACTER,
        S2C_CREATECHARACTER_ACK,
        S2C_CREATECHARACTER_NACK,
        S2C_CONNECT_NACK,
        C2S_LOGIN_SENDNONCENAMEPASSWORDANDSEED,
        S2C_LOGIN_MESSAGE_NACK,
        P2S_CREATECHARACTER,
        P2S_CREATEACCOUNT
    }

    /**
     * Type of the message
     */
    protected MessageType type;

    /**
     * Clientid of the player that generated the message
     */
    protected int clientid;

    /**
     * Timestamp about when the message was created
     */
    protected int timestampMessage;
    /**
     * version of this message
     */
    protected int protocolVersion = NetConst.NETWORK_PROTOCOL_VERSION;

    /**
     * The socket channel that the message will use to be send or from where it
     * was received
     */
    protected SocketChannel channel;

    private InetAddress inetAddres;

    /**
     * Constructor with a TCP/IP source/destination of the message
     *
     * @param type    the type of the message
     * @param channel The TCP/IP address associated to this message
     */
    protected Message(MessageType type, SocketChannel channel) {
        this.type = type;
        this.clientid = CLIENTID_INVALID;
        this.channel = channel;
        if (channel != null) {
            Socket socket = channel.socket();
            inetAddres = socket.getInetAddress();
        }
        timestampMessage = (int) (System.currentTimeMillis());
    }

    /**
     * Sets the TCP/IP source/destination of the message
     *
     * @param channel The TCP/IP socket associated to this message
     */
    public void setSocketChannel(SocketChannel channel) {
        this.channel = channel;
        if (channel != null) {
            Socket socket = channel.socket();
            inetAddres = socket.getInetAddress();
        } else {
            inetAddres = null;
        }
    }

    /**
     * Returns the TCP/IP socket associatted with this message
     *
     * @return the TCP/IP socket associatted with this message
     */
    public SocketChannel getSocketChannel() {
        return channel;
    }

    /**
     * Returns the address of the channel associated.
     *
     * @return the address of the channel associated.
     */
    public InetAddress getAddress() {
        return inetAddres;
    }

    /**
     * Returns the type of the message
     *
     * @return the type of the message
     */
    public MessageType getType() {
        return type;
    }

    /**
     * Set the clientID so that we can identify the client to which the message
     * is target, as only IP is easy to Fake
     *
     * @param clientid a int that reprents the client id.
     */
    public void setClientID(int clientid) {
        this.clientid = clientid;
    }

    /**
     * Returns the clientID of the Message.
     *
     * @return the ClientID
     */
    public int getClientID() {
        return clientid;
    }

    /**
     * Returns the timestamp of the message. Usually milliseconds
     *
     * @return the timestamp of the message. Usually milliseconds
     */
    public int getMessageTimestamp() {
        return timestampMessage;
    }

    /**
     * gets the protocol version
     *
     * @return protocol version
     */
    public int getProtocolVersion() {
        return protocolVersion;
    }

    /**
     * sets the protocol version, limited to the max supported version
     *
     * @param protocolVersion protocol versoin
     */
    public void setProtocolVersion(int protocolVersion) {
        this.protocolVersion = Math.min(NetConst.NETWORK_PROTOCOL_VERSION, protocolVersion);
    }


    /**
     * Serialize the object into an ObjectOutput
     *
     * @param out the output serializer.
     * @throws IOException if the serializations fails
     */
    public void writeObject(OutputSerializer out) throws IOException {
        out.write((byte) protocolVersion);
        out.write((byte) type.ordinal());
        out.write(clientid);
        out.write(timestampMessage);
    }

    /**
     * Serialize the object from an ObjectInput
     *
     * @param in the input serializer
     * @throws IOException if the serializations fails
     */
    public void readObject(InputSerializer in) throws IOException {
        int protocolVersion = in.readByte();
        if (protocolVersion < NetConst.NETWORK_PROTOCOL_VERSION_MIN
                || protocolVersion > NetConst.NETWORK_PROTOCOL_VERSION_MAX) {
            throw new IOException("Unsupported protocol version.");
        }

        type = MessageType.values()[in.readByte()];
        clientid = in.readInt();
        timestampMessage = in.readInt();
    }

    /**
     * generates a list of attributes suitable to be used in toString()
     *
     * @param sb StringBuilder to append the attribute string to
     */
    protected void internalToString(StringBuilder sb) {
        sb.append(", channel=");
        sb.append(channel);
        sb.append(", clientid=");
        sb.append(clientid);
        sb.append(", timestampMessage=");
        sb.append(timestampMessage);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(this.getClass().getName());
        sb.append("[");
        sb.append("type=");
        sb.append(type);
        internalToString(sb);
        sb.append("]");
        return sb.toString();
    }


}