/* Message - A protocol message which can be send through a DataOutputStream.
   Copyright (C) 2003 Mark J. Wielaard
 
   This file is part of Snark.
 
   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, 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 org.klomp.snarkxl;

import java.io.DataOutputStream;
import java.io.IOException;

import net.i2p.util.SimpleTimer;

// Used to queue outgoing connections
// sendMessage() should be used to translate them to wire format.
class Message implements Comparable {
    final static byte KEEP_ALIVE   = -1;
    final static byte CHOKE        = 0;
    final static byte UNCHOKE      = 1;
    final static byte INTERESTED   = 2;
    final static byte UNINTERESTED = 3;
    final static byte HAVE         = 4;
    final static byte BITFIELD     = 5;
    final static byte REQUEST      = 6;
    final static byte PIECE        = 7;
    final static byte CANCEL       = 8;
    
    // XL    
    final static byte BT_KEEP_ALIVE      = 9;
    final static byte BT_HANDSHAKE       = 10;
    
    // Not all fields are used for every message.
    // KEEP_ALIVE doesn't have a real wire representation
    byte type;
    
    // Used for HAVE, REQUEST, PIECE and CANCEL messages.
    int piece;
    
    // Used for REQUEST, PIECE and CANCEL messages.
    int begin;
    int length;
    
    // Used for PIECE and BITFIELD messages
    byte[] data;
    int off;
    int len;
    
    SimpleTimer.TimedEvent expireEvent;
    
    /** Utility method for sending a message through a DataStream. */
    void sendMessage(DataOutputStream dos) throws IOException {
        // KEEP_ALIVE is special.
        if (type == KEEP_ALIVE) {
            dos.writeInt(0);
            return;
        }
        
        // Calculate the total length in bytes
        
        // Type is one byte.
        int datalen = 1;
        
        // piece is 4 bytes.
        if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
            datalen += 4;
        
        // begin/offset is 4 bytes
        if (type == REQUEST || type == PIECE || type == CANCEL)
            datalen += 4;
        
        // length is 4 bytes
        if (type == REQUEST || type == CANCEL)
            datalen += 4;
        
        // add length of data for piece or bitfield array.
        if (type == BITFIELD || type == PIECE)
            datalen += len;
        
        // Send length
        dos.writeInt(datalen);
        dos.writeByte(type & 0xFF);
        
        // Send additional info (piece number)
        if (type == HAVE || type == REQUEST || type == PIECE || type == CANCEL)
            dos.writeInt(piece);
        
        // Send additional info (begin/offset)
        if (type == REQUEST || type == PIECE || type == CANCEL)
            dos.writeInt(begin);
        
        // Send additional info (length); for PIECE this is implicit.
        if (type == REQUEST || type == CANCEL)
            dos.writeInt(length);
        
        // Send actual data
        if (type == BITFIELD || type == PIECE)
            dos.write(data, off, len);
    }
    
    public String toString() {
        switch (type) {
            case KEEP_ALIVE:
                return "KEEP_ALIVE";
            case CHOKE:
                return "CHOKE";
            case UNCHOKE:
                return "UNCHOKE";
            case INTERESTED:
                return "INTERESTED";
            case UNINTERESTED:
                return "UNINTERESTED";
            case HAVE:
                return "HAVE(" + piece + ")";
            case BITFIELD:
                return "BITFIELD";
            case REQUEST:
                return "REQUEST(" + piece + "," + begin + "," + length + ")";
            case PIECE:
                return "PIECE(" + piece + "," + begin + "," + length + ")";
            case CANCEL:
                return "CANCEL(" + piece + "," + begin + "," + length + ")";
            default:
                return "<UNKNOWN>";
        }
    }
    
    /**
     * equal Message?
     */
    public boolean equals(Object o) {
        Message p = (Message)o;
        
        boolean isType = type == p.type;
        boolean isBegin = begin == p.begin;
        boolean isPiece = piece == p.piece;
        // boolean isOff = off == p.off;
        boolean isLength = length == p.length;
        
        
        if(isType )
            if(type == Message.PIECE && isPiece && isBegin && isLength) return true;
            else if(type == Message.HAVE && isPiece) return true;
            else if(type == Message.BITFIELD) return true;
            else if(type == Message.CANCEL && isPiece && isBegin && isLength) return true;
            else if(type == Message.CHOKE) return true;
            else if(type == Message.INTERESTED ) return true;
            else if(type == Message.KEEP_ALIVE) return true;
            else if(type == Message.REQUEST  && isPiece && isBegin && isLength) return true;
            else if(type == Message.UNCHOKE) return true;
            else if(type == Message.UNINTERESTED ) return true;
            else if(type == Message.BT_KEEP_ALIVE) return true;
            else if(type == Message.BT_HANDSHAKE) return true;//todo change to futured neccessaries
        return false;
    }
    
    /**
     * Compares the Messages.
     */
    public int compareTo(Object o) {
        Message p = (Message)o;
        
         boolean isType = type == p.type;
        boolean isBegin = begin == p.begin;
        boolean isPiece = piece == p.piece;
        // boolean isOff = off == p.off;
        boolean isLength = length == p.length;
        
        
        if(isType )
            if(type == Message.PIECE && isPiece && isBegin && isLength) return 0;
            else if(type == Message.HAVE && isPiece) return 0;
            else if(type == Message.BITFIELD) return 0;
            else if(type == Message.CANCEL && isPiece && isBegin && isLength) return 0;
            else if(type == Message.CHOKE) return 0;
            else if(type == Message.INTERESTED ) return 0;
            else if(type == Message.KEEP_ALIVE) return 0;
            else if(type == Message.REQUEST  && isPiece && isBegin && isLength) return 0;
            else if(type == Message.UNCHOKE) return 0;
            else if(type == Message.UNINTERESTED ) return 0;
        return 1;
        
        
    }
}
