/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2004 Gregor Koukkoullis ( phex <at> kouk <dot> de )
 *
 *  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.
 *
 *  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
 * 
 *  --- CVS Information ---
 *  $Id: MsgHeader.java,v 1.15 2004/01/18 17:24:17 gregork Exp $
 */
package phex.msg;

import java.io.*;


import phex.common.*;
import phex.host.*;
import phex.utils.*;

/**
 * <p>The header of a Gnutella message.</p>
 */
public class MsgHeader
{
    /**
     * <p>The length of a Gnutella message header in bytes.</p>
     *
     * <p>This will be 7 bytes greater than the size of a GUID.</p>
     */
    public static final int DATA_LENGTH = GUID.DATA_LENGTH + 7;

    // Function type constants
  /** Ping request for discovering network nodes. */
    public static final byte PING_PAYLOAD = (byte)0x00;

  /** Pong response listing network nodes. */
    public static final byte PONG_PAYLOAD = (byte)0x01;

    /**
     * Route table update message.
     */
    public static final byte ROUTE_TABLE_UPDATE_PAYLOAD = (byte)0x30;

  /** Push */
    public static final byte PUSH_PAYLOAD = (byte)0x40;

  /** Make a query request */
    public static final byte QUERY_PAYLOAD = (byte)0x80;

  /** Response to a query */
    public static final byte QUERY_HIT_PAYLOAD = (byte)0x81;
    
    public static final byte VENDOR_MESSAGE_PAYLOAD = (byte)0x31;
    public static final byte STANDARD_VENDOR_MESSAGE_PAYLOAD = (byte)0x32;

    public static final byte sUnknown = (byte)0xFF;


    // Atributes
    private GUID msgID;

    /**
     * The message payload.
     */
    private byte payload;
    private byte ttl;
    private byte hopsTaken;
    private int mDataLen;

    private long			mArrivalTime;
    private Host			mFromHost = null;

  /**
   * <p>Create a new MsgHeader.</p>
   *
   * <p>The TTL property is set to the default value, read from
   * ServiceManager.sCfg.mNetTTL, and all other properties are set to zero or
   * zero-initialised values.</p>
   * @deprecated
   */
    public MsgHeader()
    {
        this( new GUID() );
    }

  /**
   * <p>Create a new MsgHeader with a GUID.</p>
   *
   * <p>This header takes ownership of the GUID. The function is set to unknown,
   * and all other fields are zero.</p>
   *
   * @param guid  the GUID to use for this message header
   * @deprecated
   */
    public MsgHeader( GUID guid )
    {
        msgID = guid;
        payload = sUnknown;
        ttl = (byte)ServiceManager.sCfg.ttl;
        hopsTaken = 0;
        mDataLen = 0;
    }

    public MsgHeader( GUID guid, byte payload, byte ttl, byte hops, int dataLength )
    {
        msgID = guid;
        this.payload = payload;
        this.ttl = ttl;
        hopsTaken = hops;
        mDataLen = dataLength;
    }

  /**
   * <p>Get the GUID of this message.</p>
   *
   * <p>Each message should have a different GUID. This allows messages
   * forwarded by different paths to be treated non-redundantly.</p>
   *
   * @return the GUID of the remote host
   */
    public GUID getMsgID()
    {
        return msgID;
    }

  /**
   * Set the GUID associated with this message.
   *
   * @param MsgID  the GUID of the servent originating this message
   */
    public void setMsgID(GUID MsgID)
    {
        this.msgID = MsgID;
    }

    /**
   * Get the function that defines how to interpret the payload of this message.
   *
   * @return an int encoding the function of this message
   */
    public byte getPayload()
    {
        return payload;
    }

  /**
   * Set the funciton that defines how to interpret the payload of this message.
   *
   * @param Function  an int encoding the new function for this message
   */
    public void setPayloadType(byte aPayload)
    {
        this.payload = aPayload;
    }

  /**
   * Produce the name of the function type of this header.
   *
   * @return  a human-readable String stating the type of function this header
   *          has associated
   */
    private String getPayloadName()
    {
        switch (payload)
        {
            case PING_PAYLOAD:
                return "Ping";
            case PONG_PAYLOAD:
                return "Pong";
            case ROUTE_TABLE_UPDATE_PAYLOAD:
                return "RouteTableUpdate";
            case PUSH_PAYLOAD:
                return "Push";
            case QUERY_PAYLOAD:
                return "Query";
            case QUERY_HIT_PAYLOAD:
                return "QueryHit";
            default:
                return "Unknown";
        }
    }

    /**
   * <p>Get the time to live for this message.</p>
   *
   * <p>This states the number of network hops this message can make before
   * being dropped from the network. Each time a message passes through a
   * network node, the ttl value will be decremented. Once zero, it will not be
   * forwarded, but will be silently dropped instead.</p>
   *
   * @return the time to live of this message
   */
    public byte getTTL()
    {
        return ttl;
    }

  /**
   * Set the time to live value for this message.
   *
   * @param the new ttl value
   */
    public void setTTL(byte TTL)
    {
        this.ttl = TTL;
    }

    /**
   * <p>The number of hops this message has taken so far through the network.
   * </p>
   *
   * <p>This value will be incremented each time that the message is
   * propogated.</p>
   *
   * @return the number of hops this message has taken so far
   */
    public byte getHopsTaken()
    {
        return hopsTaken;
    }

  /**
   * Set the number of hops taken by this message.
   *
   * @param HopsTaken  the number of hops this message has taken through the
   *                   network
   */
    public void setHopsTaken(byte HopsTaken)
    {
        this.hopsTaken = HopsTaken;
    }

    /**
     * Counts a hop by incrementing hops and decrementing ttl.
     */
    public void countHop( )
    {
        if ( ttl > 0 )
        {
            ttl--;
        }
        hopsTaken ++;
    }

    /**
   * <p>The length of the data following this message in bytes.</p>
   *
   * <p>This is the payload length. It shouldn't realy be greater than 4k. The
   * next message, if there is one, is located exactly this many bytes from
   * here, and will begin like this message with a message header.</p>
   *
   * @return the length of the data in this message
   */
    public int getDataLen()
    {
        return mDataLen;
    }

  /**
   * <p>Set the length of the data associated with this header.</p>
   *
   * @param DataLen  the exact length (in bytes) of the message associated with
   *                 this header
   */
    public void setDataLen(int DataLen)
    {
        this.mDataLen = DataLen;
    }

  /**
   * The time this message arrived.
   *
   * @return the arrival time
   */
    public long getArrivalTime()
    {
        return mArrivalTime;
    }

  /**
   * Set the time this message arrived.
   *
   * @param arrivalTime  the new time of arrival
   */
    public void setArrivalTime(long arrivalTime)
    {
        this.mArrivalTime = arrivalTime;
    }

  /**
   * The host this message arrived from.
   *
   * @return the Host
   */
    public Host getFromHost()
    {
        return mFromHost;
    }

  /**
   * Set the host this message arrived from.
   *
   * @param fromHost  the Host the message was from
   */
    public void setFromHost(Host fromHost)
    {
        this.mFromHost = fromHost;
    }

  /**
   * Get the size of this portion of the message in bytes. This is equivalent to
   * the value of the static variable sDataLength.
   *
   * @return the size in bytes of this header
   */
    public int getSize()
    {
        return DATA_LENGTH;
    }

  /**
   * Copy all the properties of a message header into this header.
   *
   * @param b  the MsgHeader instance to copy
   */
    public void copy(MsgHeader b)
    {
        msgID = b.msgID;
        payload = b.payload;
        ttl = b.ttl;
        hopsTaken = b.hopsTaken;
        mDataLen = b.mDataLen;
    }
    
    public void writeHeader( GnutellaOutputStream outStream )
        throws IOException
    {
        // build array to improve performance
        byte[] tmpArray = new byte[DATA_LENGTH];
        byte[] guid = msgID.getGuid();
        System.arraycopy( guid, 0, tmpArray, 0, GUID.DATA_LENGTH );
        tmpArray[16] = (byte)payload;
        tmpArray[17] = (byte)ttl;
        tmpArray[18] = (byte)hopsTaken;
        IOUtil.serializeIntLE(mDataLen, tmpArray, 19);
        outStream.write( tmpArray );
    }

  /**
   * Read the header information from a buffer.
   *
   * @param inbuf  the byte array to read from
   * @param offset the index of the first byte to read from
   * @return  the index of the first byte not yet read
   */
    public int deserialize(byte[] inbuf, int offset)
        throws IOException
    {
        offset = msgID.deserialize(inbuf, offset);
        payload = inbuf[offset++];
        ttl = inbuf[offset++];
        hopsTaken = inbuf[offset++];
        mDataLen = IOUtil.deserializeIntLE(inbuf, offset);
        offset += 4;

        return offset;
    }


    public String getDebugString()
    {
        return "Header[ MsgID=" + msgID +
            ", Function=" + payload + "-" + getPayloadName() +
            ", TTL=" + ttl +
            ", HopsTaken=" + hopsTaken +
            ", DataLen=" + mDataLen +
            " ]";
    }
}

