/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jdi.internal.jdwp;


import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * This class implements the corresponding Java Debug Wire Protocol (JDWP) packet
 * declared by the JDWP specification.
 *
 */
public abstract class JdwpPacket {
	/** General JDWP constants. */
	public static final byte FLAG_REPLY_PACKET = (byte)0x80;
	protected static final int MIN_PACKET_LENGTH = 11;
	
	/** Map with Strings for flag bits. */
	private static String[] fgFlagStrings = null;

	/** Header fields. */
	protected int fId = 0;
	protected byte fFlags = 0;
	protected byte[] fDataBuf = null;
	
	/**
	 * Set Id.
	 */
	/*package*/ void setId(int id) {
		fId = id;
	}

	/**
	 * @return Returns Id.
	 */
	public int getId() {
		return fId;
	}
	
	/**
	 * Set Flags.
	 */
	/*package*/ void setFlags(byte flags) {
		fFlags = flags;
	}
	
	/**
	 * @return Returns Flags.
	 */
	public byte getFlags() {
		return fFlags;
	}

	/**
	 * @return Returns total length of packet.
	 */
	public int getLength() {
		return MIN_PACKET_LENGTH + getDataLength();
	}
	
	/**
	 * @return Returns length of data in packet.
	 */
	public int getDataLength() {
		return fDataBuf == null ? 0 : fDataBuf.length;
	}
	
	/**
	 * @return Returns data of packet.
	 */
	public byte[] data() {
		return fDataBuf;
	}

	/**
	 * @return Returns DataInputStream with reply data, or an empty stream if there is none.
	 */
	public DataInputStream dataInStream() {
		if (fDataBuf != null) {
			return new DataInputStream(new ByteArrayInputStream(fDataBuf));
		}
		
		return new DataInputStream(new ByteArrayInputStream(new byte[0]));
	}

	/**
	 * Assigns data to packet.
	 */
	public void setData(byte[] data) {
		fDataBuf = data;
	}
	
	/**
	 * Reads header fields that are specific for a type of packet.
	 */
	protected abstract int readSpecificHeaderFields(byte[] bytes, int index) throws IOException;
	
	/**
	 * Writes header fields that are specific for a type of packet.
	 */
	protected abstract int writeSpecificHeaderFields(byte[] bytes, int index) throws IOException;
	
	/**
	 * Constructs a JdwpPacket from a byte[].
	 */
	public static JdwpPacket build(byte[] bytes) throws IOException {
		// length (int)
        int a = (bytes[0]&0xff) << 24;
        int b = (bytes[1]&0xff) << 16;
        int c = (bytes[2]&0xff) << 8;
        int d = (bytes[3]&0xff) << 0;
		int packetLength = a+b+c+d;

		// id (int)
		a = (bytes[4]&0xff) << 24;
		b = (bytes[5]&0xff) << 16;
		c = (bytes[6]&0xff) << 8;
		d = (bytes[7]&0xff) << 0;
		int id = a+b+c+d;

		// flags (byte)
		byte flags = bytes[8];

		// Determine type: command or reply.
		JdwpPacket packet;
		if ((flags & FLAG_REPLY_PACKET) != 0)
			packet = new JdwpReplyPacket();
		else
			packet = new JdwpCommandPacket();
			
		// Assign generic header fields.
		packet.setId(id);
		packet.setFlags(flags);
			
		// Read specific header fields and data.
		int index = 9;
		index += packet.readSpecificHeaderFields(bytes, 9);
		if (packetLength - MIN_PACKET_LENGTH > 0) {
			packet.fDataBuf = new byte[packetLength - MIN_PACKET_LENGTH];
			System.arraycopy(bytes, index, packet.fDataBuf, 0, packet.fDataBuf.length);
		}
		
		return packet;
	}
	
	public byte[] getPacketAsBytes() throws IOException {
	    int len = getLength();
	    byte[] bytes = new byte[len];
	    
	    //convert len to bytes
        bytes[0] = (byte) (len >>> 24);
        bytes[1] = (byte) (len >>> 16);
        bytes[2] = (byte) (len >>>  8);
        bytes[3] = (byte) (len >>>  0);
        
        //convert id to bytes
        int id = getId();
        bytes[4] = (byte) (id >>> 24);
        bytes[5] = (byte) (id >>> 16);
        bytes[6] = (byte) (id >>>  8);
        bytes[7] = (byte) (id >>>  0);
        
        //flags
        bytes[8] = getFlags();
        
        //convert specific header fields
        int index = 9;
        index += writeSpecificHeaderFields(bytes, index);
        
        if (index < len && fDataBuf!=null) {
            //copy data
            System.arraycopy(fDataBuf, 0, bytes, index, fDataBuf.length);
        }
        return bytes;
	}

	/**
	 * Retrieves constant mappings.
	 */
	public static void getConstantMaps() {
		if (fgFlagStrings != null) {
			return;
		}
		
		Field[] fields = JdwpPacket.class.getDeclaredFields();
		fgFlagStrings = new String[8];
		
		for (int i = 0; i < fields.length; i++) {
			Field field = fields[i];
			if ((field.getModifiers() & Modifier.PUBLIC) == 0 || (field.getModifiers() & Modifier.STATIC) == 0 || (field.getModifiers() & Modifier.FINAL) == 0) {
				continue;
			}
				
			String name = field.getName();
			if (!name.startsWith("FLAG_")) {//$NON-NLS-1$
				continue;
			}
				
			name = name.substring(5);
			
			try {
				byte value = field.getByte(null);
				
				for (int j = 0; j < fgFlagStrings.length; j++) {
					if ((1 << j & value) != 0) {
						fgFlagStrings[j]= name;
						break;
					}
				}
			} catch (IllegalAccessException e) {
				// Will not occur for own class.
			} catch (IllegalArgumentException e) {
				// Should not occur.
				// We should take care that all public static final constants
				// in this class are bytes.
			}
		}
	}
	
	/**
	 * @return Returns a mapping with string representations of flags.
	 */
	public static String[] getFlagMap() {
		getConstantMaps();
		return fgFlagStrings;
	}
}
