/**
Copyright (c) 2007-2013 Alysson Bessani, Eduardo Alchieri, Paulo Sousa, and the authors indicated in the @author tags

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package bftsmart.tom;

import bftsmart.consensus.messages.ConsensusMessage;
import bftsmart.tom.core.messages.TOMMessage;
import bftsmart.tom.core.messages.TOMMessageType;

import java.io.Serializable;
import java.util.Random;
import java.util.Set;

/**
 * This class represents the whole context of a request ordered in the system.
 * It stores all informations regarding the message sent by the client, as well as
 * the consensus instance in which it was ordered.
 * 
 */
public class MessageContext implements Serializable {
	
    private static final long serialVersionUID = -3757195646384786213L;

    // Client info
    private final int sender;
    private final int viewID;
    private final TOMMessageType type;
    private final int session;
    private final int sequence;
    private final int operationId;
    private final int replyServer;
    private final  byte[] signature;
    
    // Consensus info
    private final long timestamp;
    private final int regency;
    private final int leader;
    private final int consensusId;
    private final int numOfNonces;
    private final long seed;
    private final Set<ConsensusMessage> proof;
                
    private final TOMMessage firstInBatch; //to be replaced by a statistics class
    private boolean lastInBatch; // indicates that the command is the last in the batch. Used for logging
    private final boolean noOp;
    
    public boolean readOnly = false;
    
    private byte[] nonces;
    
    /**
     * Constructor 
     * 
     * @param sender The sender ID
     * @param viewID The view ID
     * @param type The request type
     * @param session Client's session
     * @param sequence Request's sequence number
     * @param operationId The operation ID
     * @param replyServer ID of the server expected to send a non-hashed reply when the clients sends an unordered hashed request.
     * @param signature Client's signature.
     * @param timestamp The timestamp as generated by the leader
     * @param numOfNonces The nonces as generated by the leader
     * @param seed The seed as generated by the leader
     * @param regency The current regency in which the message was ordered.
     * @param leader The leader with which the batch was decided
     * @param consensusId The ID of the consensus in which this request was ordered
     * @param proof The proof for the consensus
     * @param firstInBatch The first request in the ordered batch
     * @param noOp true if the consensus instance has no operations to deliver to the application
     */
    public MessageContext(int sender, int viewID, TOMMessageType type,
            int session, int sequence, int operationId, int replyServer, byte[] signature,
            long timestamp, int numOfNonces, long seed, int regency, int leader, int consensusId,
            Set<ConsensusMessage> proof, TOMMessage firstInBatch, boolean noOp) {
        
        this.nonces = null;
               
        this.sender = sender;
        this.viewID = viewID;
        this.type = type;
        this.session = session;
        this.sequence = sequence;
        this.operationId = operationId;
        this.replyServer = replyServer;
        this.signature = signature;
        
        this.timestamp = timestamp;
        this.regency = regency;
        this.leader = leader;
        this.consensusId = consensusId;
        this.numOfNonces = numOfNonces;
        this.seed = seed;
        
        this.proof = proof;
        this.firstInBatch = firstInBatch;
        this.noOp = noOp;
    }

    /**
     * Returns the view ID
     * @return The view ID
     */
    public int getViewID() {
        return viewID;
    }

    /**
     * Returns the request type
     * @return The request type
     */
    public TOMMessageType getType() {
        return type;
    }

    /**
     * Returns the client's session
     * @return Client's session
     */
    public int getSession() {
        return session;
    }

    /**
     * Returns the request's sequence number
     * @return Request's sequence number
     */
    public int getSequence() {
        return sequence;
    }

    /**
     * Returns the operation ID
     * @return The operation ID
     */
    public int getOperationId() {
        return operationId;
    }
    
    /**
     * Returns the ID of the server expected to send a non-hashed reply when the clients sends an unordered hashed request.
     * @return ID of the server expected to send a non-hashed reply when the clients sends an unordered hashed request.
     */
    public int getReplyServer() {
        return replyServer;
    }
    
    
    /**
     * Returns the client's signature.
     * @return Client's signature.
     */
    public byte[] getSignature() {
        return signature;
    }
    
    /**
     * Returns the ID of the client that sent the message
     * @return The sender ID
     */
    public int getSender() {
        return sender;
    }

    /**
     * Returns the timestamp as generated by the leader
     * @return The timestamp as generated by the leader
     */
    public long getTimestamp() {
        return timestamp;
    }

    /**
     * Returns the nonces as generated by the leader
     * @return The nonces as generated by the leader
     */
    public byte[] getNonces() {
        
        if (nonces == null) { //obtain the nonces to be delivered to the application          
            
            nonces = new byte[numOfNonces];
            if (nonces.length > 0) {
                Random rnd = new Random(seed);
                rnd.nextBytes(nonces);
            }
            
        }
        
        return nonces;
    }

   /**
     * Returns the number of nonces as generated by the leader
     * @return the number of nonces
     */
     public int getNumOfNonces() {
        return numOfNonces;
    }

   /**
     * Returns the seed as generated by the leader
     * @return The seed as generated by the leader
     */
     public long getSeed() {
        return seed;
    }
    
    /**
     * Returns the ID of the consensus in which this request was ordered (-1
     * if readonly).
     * @return The ID of the consensus in which this request was ordered
     */
    public int getConsensusId() {
        return consensusId;
    }
    
    /**
     * Returns the id of the leader replica in which this message was ordered 
     * (-1 if readonly).
     * @return The leader with which the batch was decided
     */
    public int getLeader() {
        return leader;
    }
    /**
     * Returns the proof for the consensus.
     * @return The proof for the consensus
     */
    public Set<ConsensusMessage> getProof() {
        return proof;
    }
    
    /**
     * Returns the current regency in which the message was ordered.
     * @return The current regency in which the message was ordered.
     */
    public int getRegency() {
        return regency;
    }
    
    /**
     * Returns the first request in the ordered batch
     * 
     * @return The first request in the ordered batch
     */
    public TOMMessage getFirstInBatch() {
        return firstInBatch;
    }

    public void setLastInBatch() {
    	lastInBatch = true;
    }
    
    /**
     * Returns the last request in the ordered batch
     * 
     * @return The last request in the ordered batch
     */
    public boolean isLastInBatch() {
    	return lastInBatch;
    }

    /**
     * Returns true if the consensus instance has no operations to deliver to the application
     * @return true if the consensus instance has no operations to deliver to the application
     */
    public boolean isNoOp() {
        return noOp;
    }
    
    /**
     * Generates a TOMMessage for its associated requests using the new info that it now supports since the previous commit.
     * It is assumed that the byte array passed to this method is the serialized request associated to the original TOMMessage.
     * 
     * @param content Serialized request associated to the original TOMMessage.
     * 
     * @return A TOMMessage object that is equal to the original object issued by the client
     */
    public TOMMessage recreateTOMMessage(byte[] content) {

        TOMMessage ret = new TOMMessage(sender, session, sequence, operationId, content, viewID, type);
        ret.setReplyServer(replyServer);
        ret.serializedMessageSignature = signature;
        ret.serializedMessage = TOMMessage.messageToBytes(ret);
        
        return ret;
    }

}