package lsr.paxos.messages;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;

/**
 * Base class for all messages. Every message requires to know the view number
 * of the sender.
 * <p>
 * To implement new message, it is required to override the
 * <code>byteSize()</code> method and implement <code>write()</code> method. See
 * subclasses implementation for details how they should be implemented.
 */
public abstract class Message implements Serializable {
    private static final long serialVersionUID = 1L;
    protected final int view;
    private long sentTime;

    /**
     * Creates message from specified view number and current time.
     * 
     * @param view - current view number
     */
    protected Message(int view) {
        this(view, System.currentTimeMillis());
    }

    /**
     * Creates new message from specified view number and sent time.
     * 
     * @param view - current view number
     * @param sentTime - time when the message was sent
     */
    protected Message(int view, long sentTime) {
        this.view = view;
        this.sentTime = sentTime;
    }

    /**
     * Creates new message from input stream with serialized message inside.
     * 
     * @param input - the input stream with serialized message
     * @throws IOException if I/O error occurs
     */
    protected Message(DataInputStream input) throws IOException {
        view = input.readInt();
        sentTime = input.readLong();
    }

    /**
     * Sets the time when the message was sent.
     * 
     * @param sentTime - the time when the message was sent in milliseconds
     */
    public void setSentTime(long sentTime) {
        this.sentTime = sentTime;
    }

    /**
     * Returns the time when the message was sent.
     * 
     * @return the time when the message was sent in milliseconds.
     */
    public long getSentTime() {
        return sentTime;
    }

    /**
     * Sets the sent time to the current time.
     */
    public void setSentTime() {
        sentTime = System.currentTimeMillis();
    }

    /**
     * Returns the view number stored in this message.
     * 
     * @return the view number
     */
    public int getView() {
        return view;
    }

    /**
     * The size of the message after serialization in bytes.
     * 
     * @return the size of the message in bytes
     */
    public int byteSize() {
        return 1 + 4 + 8;
    }

    /**
     * Returns a message as byte array. The size of the array is equal to
     * <code>byteSize()</code>.
     * 
     * @return serialized message to byte array
     */
    public final byte[] toByteArray() {
        // Create with the byte array of the exact size,
        // to prevent internal resizes
        ByteBuffer bb = ByteBuffer.allocate(byteSize());
        bb.put((byte) getType().ordinal());
        bb.putInt(view);
        bb.putLong(sentTime);
        write(bb);

        assert bb.remaining() == 0 : "Wrong sizes. Limit=" + bb.limit() + ",capacity=" +
                                     bb.capacity() + ",position=" + bb.position();
        return bb.array();
    }
    
    public final void writeTo(ByteBuffer bb) {
        bb.put((byte) getType().ordinal());
        bb.putInt(view);
        bb.putLong(sentTime);
        write(bb);
    }


    /**
     * Returns the type of the message. This method is implemented by subclasses
     * and should return correct message type.
     * 
     * @return the type of the message
     */
    public abstract MessageType getType();

    public String toString() {
        return "v:" + getView();
    }

    /**
     * When serializing message to byte array, this function is called on the
     * message. Implementation of message-specific fields serialization must go
     * there.
     * 
     * @param bb - the byte buffer to serialize fields to
     */
    protected abstract void write(ByteBuffer bb);
}
