package s3.services.bootimage;

import java.io.BufferedOutputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import ovm.core.services.memory.MemoryManager;
import ovm.core.services.memory.VM_Address;
import ovm.util.ByteBuffer;
import ovm.util.OVMRuntimeException;
import ovm.util.logging.Logger;
import s3.core.domain.MachineSizes;

/**
 * Represent the BootImage data structure. <p>
 *
 * 9/10/02: Refactored BootImage to remove a number of inner classes and
 * simplify the code. Naming scheme as well as separation of duties between
 * this class and the driver have been revisited.
 * NB: the code for generating a relocation bitmap has been removed, since
 *     we assume that mmap can load the image at the proper base address.<p>
 *
 * 9/17/02: WARNING: The boot image is now a singleton.
 *
 * @author Grothoff, Vitek
 **/
public final class BootImage extends BootBase {

    /** Current version number.  **/
    private static final Integer VERSION = new Integer(0x00010000);

    ///** Size of the image header in bytes (up to length field) (will need
    // * to be increased to 36 when base address is included)  **/
    //private static final int MAGICLENGTH = 4;
    //private static final int VERSIONLENGTH = 4;

    /** The magic number.  **/
    private static final Integer MAGIC = new Integer(0x494E2086);


    /** The memory that we are dumping.  **/
    private final ByteBuffer memory_;

    private static boolean headerFrozen = false;
    private static BootImage thisBootimage;

    /** The HeaderObjects used to initialize the image header section.  **/
    private HeaderObject[] headers_ = new HeaderObject[0];

     final HeaderObject magicHeader_ =  addHeader("OVM_MAGIC",   MAGIC);
     final HeaderObject versionHeader_ = addHeader("OVM_VERSION", VERSION);
     final HeaderObject usedMemoryHeader_ = addHeader("usedMemory",
                                                             new Integer(0));
      final HeaderObject baseAddressHeader_ =
	  addHeader("baseAddress",
		    new Integer(MemoryManager.the().getImageBaseAddress()));

    // -------------- Constructor ------------------------------

    public BootImage(ByteBuffer mem) {
        OVMassert(thisBootimage == null);
        this.memory_ = mem;
        thisBootimage = this;
    }

    // --------------- instance methods ------------------------

    public static HeaderObject addHeaderS(String name, Object target) {
	return thisBootimage.addHeader(name, target);
    }

    final HeaderObject addHeader(String name, Object target) {
	ImageObserver._.addHeader(name, target);
	
        HeaderObject header = new HeaderObject(target, name);

        OVMassert( headers_ != null );
        int length = headers_.length;
        HeaderObject[] newheaders = new HeaderObject[length+1];
        for ( int i = 0; i < length; i++ ) newheaders[i] = headers_[i];
        newheaders[length] = header;
        headers_ = newheaders;
        return header;
    }

    static public void freezeHeader() {
	VM_Address.setImageHeaderSize(thisBootimage.headers_.length * 
				      MachineSizes.BYTES_IN_WORD);
        headerFrozen = true;
    }

    // *********************** Inner classes **************************

    /** Class representing fields in the image header.  **/
    /**
     * Keeps track of header entries which can be either addresses or
     * integer values. In previous design we had three classes, an abstract
     * Entry and two concrete classes. But apart from the added complexity
     * there was little gain. [Yes this version has its warts.]
     **/
    private class HeaderObject extends BootBase {
        final private String name;
        private boolean isPrimitive = false;
        private Object target;

        HeaderObject(Object o, String n) {
            name = n;
            target = o; // an Int indicates a boxed integer ...
            if (o instanceof Integer) isPrimitive = true;
        }

        final boolean debug = true;

        // we are writing sequentially in the begining of the image
        // references to later objects.
        void write(ByteBuffer buf) {
            if ( ! isPrimitive ) {
                VM_Address address = VM_Address.fromObject(target);
                int addressAsInt = address.asInt();
                if ( addressAsInt == 0 )  {
		    buf.position(buf.position() + VM_Address.widthInBytes());
		    return;
		}
                buf.putInt( addressAsInt);
                if (debug) {
                    if (name.equals("mainMethod"))
                        Logger.global.finer(" Main method location: 0x"
		         + Integer.toHexString( addressAsInt));
                    if (name.equals("mainObject")) {
//                         Logger.global.finer(" Image Base Address: 0x"
//                           + Integer.toHexString(  baseAddress()));
                        Logger.global.finer(" Main object location: 0x"
                          + Integer.toHexString(addressAsInt));
                    }
                }
            } else {
                buf.putInt( ((Integer) target).intValue());
            }
        }
        String toCField() {
            return ((!isPrimitive) ? "void * " : "int ") + name + ";";
        }
        void setValue(int val) {
            OVMassert(isPrimitive);
            target = new Integer(val);
        }
    } // End of HeaderObject

    /** Save the image to an output stream and close it.
     * @param os the stream to which the image will be saved.
     **/
    public void save(final String fname) 
        throws IOException {

	final OutputStream os = new FileOutputStream(fname);	
        final DataOutputStream dataOutStream =
            new DataOutputStream(new BufferedOutputStream(os));
        final int byteOrder_ = memory_.order();

        usedMemoryHeader_.setValue( getImageSize() );

	final ByteBuffer headerBuf =
	    ByteBuffer.allocate(headers_.length * MachineSizes.BYTES_IN_WORD);
	headerBuf.order(byteOrder_);
	
	for ( int i = 0; i < headers_.length ; i++ )
	    headers_[i].write(headerBuf);
	
	writeBufferToOutput(headerBuf, dataOutStream);
	
        writeBufferToOutput(memory_, dataOutStream);

        // sanity check at end of image
	ByteBuffer magic =  ByteBuffer.allocate(MachineSizes.BYTES_IN_WORD);
	magic.order(byteOrder_);
	magic.putInt(MAGIC.intValue());
	writeBufferToOutput(magic, dataOutStream);
	dataOutStream.flush();
	dataOutStream.close();
    }

    /** Get the size of the image (total filesize)  in bytes **/
    int getImageSize() {
        return memory_.position() + 4; // for the magic int at end of image
    }

    /** Write the buffer up to and including the current byte to the data
     * output stream. Leaves the buffer unchanged. **/
    static private void writeBufferToOutput(ByteBuffer buf, DataOutput dos)
        throws IOException {
	dos.write(buf.array(), buf.arrayOffset(), buf.position());
    }

    public void writeHeadersToC_h_File(PrintWriter file) {
        file.print("#define IMAGE_MAGIC 0x"+
                   Integer.toHexString(MAGIC.intValue())
                   +"\n typedef struct {\n");
        for (int i = 0; i < headers_.length; i++)
            file.print("\t" + headers_[i].toCField() +"\n");
        file.print("\t char data[0];\n} ImageFormat;\n");
    }
} // end of BootImage
