/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * 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 version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */


package com.sun.cldchi.tools.memoryprofiler.jdwp;

import java.io.IOException;
import java.util.Vector;

/**
 * This abstract class represents a generic transport for JDWP. In KJDB it's
 * used its subclass <code>SocketTransport</code>.
 *
 * @see jdwp.SocketTransport
 */

abstract class Transport {

    /**
     * A buffer that contains data received via JDWP. KJDB doesn't
     * use this field.
     *
     * @see jdwp.SocketTransportImpl
     */
    private static ByteBuffer buffer = new ByteBuffer();

    /**
     * A maximum allowed JDWP packet size.
     */
    private final static int MaxPacketSize		= 0x20000;
    
    /**
     * A delay between attempts to get expected JDWP reply.
     */
    private static final int delta = 2;
    
    /**
     * A timeout for handshake.
     */
    private static final int handshakeTimeout = 50000;

    /**
     * A list of received JDWP repliesa and still not requested
     * by other parts of the code.
     */
    public Vector Replies = new Vector();

    /**
     * Returns the number of bytes that can be received immediately.
     *
     * @return the number of bytes that can be received immediately
     */
    public abstract int available() throws IOException;

    /**
     * Receives the next byte of data. The value byte is
     * returned as an int in the range 0 to 255. If no byte is
     * available, the value -1 is returned.
     *
     * @return the next byte that is read via JDWP
     */
    public abstract int read() throws IOException;

    /**
     * Sends the specified byte via JDWP.
     *
     * @param b a byte to be sent
     */
    public abstract void write(int b) throws IOException;

    /**
     * Sends the specified bytes via JDWP.
     *
     * @param b an array of bytes
     * @param off an offset of the first byte to be sent
     * @param len a number of bytes to be sent
     */
    public void write(byte[] b, int off, int len) throws IOException {
        for (int i = 0; i < len; i++)
			write(b[off + i]);	
    }

    /**
     * Performs JDWP "handshake" procedure (undocumented ?). It sends
     * a <code>"JDWP-Handshake"</code> string and waits for receiving the
     * same string. After handshaking the JDWP connection is considered
     * estblished.
     */
    public void Handshake() throws IOException {

		String hs = "JDWP-Handshake";
		byte[] hsb = hs.getBytes();

		write(hsb, 0, hsb.length);


		int handshakeTime = handshakeTimeout;
		do{
			Tools.wait(500);
			handshakeTime -= 500;
		}while(handshakeTime > 0 && available() < hsb.length);

		if (available() < hsb.length)
			throw new IOException("Target failed to handshake.");

        for (int i = 0; i < hsb.length; i++) {
            byte b = (byte) (read() & 0xFF);
            if (b != hsb[i])
                throw new IOException("Target failed to handshake.");
        }
    }

    /**
     * Sends JDWP command packet.
     *
     * @param c a command to be sent
     */
    public void sendCommand(Command c) throws IOException {
        c.setLength();
		write(c.bytes, 0, c.length());
    }

    /**
     * Sends JDWP command packet with wrong length field. This method is
     * never used by KJDB but it's used by CLDC-DI and JavaCard BackEnd
     * test suite.
     *
     * @param c a command to be sent
     * @param WrongSize a size of the packet
     */
    public void sendBadCommandPacket(Command c, int WrongSize) throws IOException {

   	    try {
			c.putInt(Packet.LengthOffset, WrongSize);
        }
        catch (BoundException e) {};
		write(c.bytes, 0, c.length());
    }


    /**
     * Receives all available JDWP reply packets and places them into the 
     * vector <code>Replies</code>. This method is overwritten in KJDB and the
     * code below is not used.
     *
     * @see jdwp.SocketTransport
     * @see jdwp.SocketTransportImpl
     */
    public void receive() throws IOException {

        while (available() > 0)
			buffer.addByte(read());

		while (buffer.length() >= Packet.PacketHeaderSize) {

			int size = 0;

			buffer.resetParser();
			try {
				size = buffer.getInt();
			}
			catch (BoundException e) {}

	        if ((size < 0) || (size > MaxPacketSize))
    	    	throw new IOException("Wrong JDWP packet size: " + size + ".");

	        if (size > buffer.length())
				return;

			Reply r = new Reply();

			r.resetBuffer();
			r.addBytes(buffer.bytes, 0, size);

			buffer.deleteBytes(size);

			Replies.add(r);
		}
    }

    /**
     * Receives JDWP reply packet.
     * If there is no reply packet available, function returns packet
     * with error code <code>Reply.errNotAvailable</code>.
     *
     * @return a JDWP reply or <code>Reply.errNotAvailable</code> if not
     * available
     */
    public Reply receiveReply() throws IOException {

        receive();

		if (Replies.size() == 0)
			return Reply.Error(Reply.errNotAvailable);

		return (Reply) Replies.remove(0);
    }

    /**
     * Tries to receive a JDWP reply packet with specified ID. It looks
     * into a list of already received packets and tries to locate it.
     * If the desired packet is not found, <code>Reply.errNotAvailable</code>
     * is returned.
     *
     * @param ReplyID an ID of the desired JDWP reply packet
     * @return a JDWP reply packet with specified ID or 
     * <code>Reply.errNotAvailable</code> if not found
     */
    public Reply receiveReply(int ReplyID) throws IOException {
        
        receive();

        for (int i = 0; i < Replies.size(); i++)
       	if (((Reply) Replies.elementAt(i)).getFlags() == 0x80 &&
		((Reply) Replies.elementAt(i)).getID() == ReplyID)
			return (Reply) Replies.remove(i);

		return Reply.Error(Reply.errNotAvailable);
    }

    /**
     * Tries to receive a JDWP reply packet with specified ID. It looks
     * into a list of already received packets and tries to locate it.
     * If the desired packet is not found, is makes a pause and then
     * tries to receive the deired reply again.
     * In case of timeout <code>Reply.errNotAvailable</code>
     * is returned.
     *
     * @param ReplyID an ID of the desired JDWP reply packet
     * @param Delay a timeout for this operation
     * @return a JDWP reply packet with specified ID or 
     * <code>Reply.errNotAvailable</code> if not found
     */
    public Reply receiveReply(int ReplyID, int Delay) throws IOException {
		Reply r = receiveReply(ReplyID);

		while ((r.getErrorCode() == Reply.errNotAvailable) && (Delay > 0)) {
			Tools.wait(delta);
			r = receiveReply(ReplyID);
			Delay = Delay - delta;
		}
		return r;
    }

    /**
     * Tries to receive a JDWP event. It looks
     * into a list of already received packets and tries to locate it.
     * If the desired packet is not found, is makes a pause and then
     * tries to receive the deired reply again.
     * In case of timeout <code>Reply.errNotAvailable</code>
     * is returned.
     *
     * @param command a command number for Event/Composite JDWP command
     * (should be always <code>0x4064</code>)
     * @param Delay a timeout for this operation
     * @return a JDWP event or <code>Reply.errNotAvailable</code> if not found
     */
    public Reply receiveEvent(int command, int Delay) throws IOException {

    	Reply r;

        while (Delay > 0) {
	        receive();
	        for (int i = 0; i < Replies.size(); i++) {
	        	r = (Reply) Replies.elementAt(i);
	    	   	if (r.getFlags() == 0 && r.getErrorCode() == command)
					return (Reply) Replies.remove(i);
			}
			Tools.wait(delta);
			Delay = Delay - delta;
		}
		return Reply.Error(Reply.errNotAvailable);
    }
}
