/*
    JPC: A x86 PC Hardware Emulator for a pure Java Virtual Machine
    Release Version 2.0

    A project from the Physics Dept, The University of Oxford

    Copyright (C) 2007 Isis Innovation Limited

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 2 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 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.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
    Details (including contact information) can be found at: 

    www.physics.ox.ac.uk/jpc
*/

package org.jpc.emulator.memory.codeblock.bytecodecompiler;

import java.io.*;
import java.util.*;

import org.jpc.emulator.memory.codeblock.*;
import org.jpc.classfile.*;
import org.jpc.emulator.processor.Processor;

public class ByteCodeCompiler implements CodeBlockCompiler
{
    public static final int PROCESSOR_ELEMENT_EAX = 0;
    public static final int PROCESSOR_ELEMENT_ECX = 1;
    public static final int PROCESSOR_ELEMENT_EDX = 2;
    public static final int PROCESSOR_ELEMENT_EBX = 3;
    public static final int PROCESSOR_ELEMENT_ESP = 4;
    public static final int PROCESSOR_ELEMENT_EBP = 5;
    public static final int PROCESSOR_ELEMENT_ESI = 6;
    public static final int PROCESSOR_ELEMENT_EDI = 7;

    public static final int PROCESSOR_ELEMENT_EIP = 8;

    public static final int PROCESSOR_ELEMENT_ZFLAG = 9;
    public static final int PROCESSOR_ELEMENT_SFLAG = 10;
    public static final int PROCESSOR_ELEMENT_PFLAG = 11;

    public static final int PROCESSOR_ELEMENT_OFLAG = 12;
    public static final int PROCESSOR_ELEMENT_CFLAG = 13;
    public static final int PROCESSOR_ELEMENT_AFLAG = 14;

    public static final int PROCESSOR_ELEMENT_DFLAG = 15;
    public static final int PROCESSOR_ELEMENT_IFLAG = 16;

    public static final int PROCESSOR_ELEMENT_ES = 17;
    public static final int PROCESSOR_ELEMENT_CS = 18;
    public static final int PROCESSOR_ELEMENT_SS = 19;
    public static final int PROCESSOR_ELEMENT_DS = 20;
    public static final int PROCESSOR_ELEMENT_FS = 21;
    public static final int PROCESSOR_ELEMENT_GS = 22;

    public static final int PROCESSOR_ELEMENT_ADDR0 = 23;


    public static final int PROCESSOR_ELEMENT_REG0 = 24;
    public static final int PROCESSOR_ELEMENT_REG1 = 25;
    public static final int PROCESSOR_ELEMENT_REG2 = 26;

    public static final int PROCESSOR_ELEMENT_SEG0 = 27;

    public static final int PROCESSOR_ELEMENT_MEMORY_WRITE = 28;
    public static final int PROCESSOR_ELEMENT_IOPORT_WRITE = 29;

    protected static final int PROCESSOR_ELEMENT_COUNT = 24;
    protected static final int POPABLE_ELEMENT_COUNT = 28;
    protected static final int ELEMENT_COUNT = 30;

    private static final int VARIABLE_EXECUTE_COUNT_INDEX = 10;
    private static final int VARIABLE_OFFSET = 11;

    private static int classIndex = 0;

    private static final int MAX_METHOD_CODE_SIZE = 64 * 1024;  //java's limit is actually 65536

    private int[] byteCodes = new int[MAX_METHOD_CODE_SIZE];
    private int bcPos = 0;


    public ProtectedModeCodeBlock getProtectedModeCodeBlock(InstructionSource source)
    {
        throw new IllegalStateException("Can't do protected in the ByteCodeCompiler!!");
    }

    public RealModeCodeBlock getRealModeCodeBlock(InstructionSource source)
    {
        MicrocodeNode[] microcodes = MicrocodeNode.getMicrocodes(source);
        ClassFile newClass = null;
        try 
        {
            newClass = ClassFileBuilder.createNewSkeletonClass();
            MicrocodeNode last = microcodes[microcodes.length-1];
            int x86CountIndex = newClass.addToConstantPool(new Integer(last.getX86Index()));
            int x86LengthIndex = newClass.addToConstantPool(new Integer(last.getX86Position()));
            
            compileX86CountMethod(newClass, x86CountIndex);
            compileX86LengthMethod(newClass, x86LengthIndex);
            
            compileExecuteMethod(microcodes, newClass, x86CountIndex);
            newClass.setClassName("RR_"+(classIndex++));
 
            return (RealModeCodeBlock) ClassFileBuilder.instantiateClass(newClass);
        } 
        catch (IOException e) 
        { 
            throw new IllegalStateException(e); 
        }
        catch (ArrayIndexOutOfBoundsException e) 
        { 
            throw new IllegalStateException(e); 
        }
        catch (Error e)
        {
            int[] ints = newClass.getMethodCode("execute");
            for (int i = 0; i < ints.length; i += JavaOpcode.getOpcodeLength(ints, i))
            {
                System.err.print(JavaOpcode.toString(ints[i]));
                for (int j = 1; j < JavaOpcode.getOpcodeLength(ints, i); j++)
                    System.err.print(", " + ints[i+j]);
                System.err.print("\n");
            }

            e.printStackTrace();
            for (int i=0; i<microcodes.length; i++)
                System.err.println(microcodes[i]);
            throw new IllegalStateException("Failed to compile RR: ", e);
        }
    }
    
    private boolean isRefElement(int element)
    {
        return ((element >= PROCESSOR_ELEMENT_ES) && (element <= PROCESSOR_ELEMENT_GS) || (element == PROCESSOR_ELEMENT_SEG0));
    }

    private int loadPopCodeFor(int[] byteCodes, int bcPos, int element, ClassFile cf) throws IOException
    {
        bcPos = loadOntoStack(byteCodes, bcPos, element);
        bcPos = writeBytecodes(byteCodes, bcPos, cf, BytecodeFragments.popCode(element));
        return bcPos;
    }

    private int loadPushCodeFor(int[] byteCodes, int bcPos, int element, ClassFile cf) throws IOException
    {
        bcPos = writeBytecodes(byteCodes, bcPos, cf, BytecodeFragments.pushCode(element));
        bcPos = popFromStack(byteCodes, bcPos, element);
        return bcPos;
    }

    private int loadOntoStack(int[] byteCodes, int bcPos, int element)
    {
        if (isRefElement(element))
            byteCodes[bcPos++] = JavaOpcode.ALOAD;
        else
            byteCodes[bcPos++] = JavaOpcode.ILOAD;
        byteCodes[bcPos++] = (VARIABLE_OFFSET + element);
        return bcPos;
    }

    private int popFromStack(int[] byteCodes, int bcPos, int element)
    {
        if (isRefElement(element))
            byteCodes[bcPos++] = JavaOpcode.ASTORE;
        else
            byteCodes[bcPos++] = JavaOpcode.ISTORE;
        byteCodes[bcPos++] = (VARIABLE_OFFSET + element);
        return bcPos;
    }

    public int[] promoteArray(byte[] src)
    {
        int[] ints = new int[src.length];
        for(int i = 0; i < src.length; i++)
            ints[i] = 0xff & src[i];
        
        return ints;
    }

    private void compileExecuteMethod(MicrocodeNode[] microcodes, ClassFile cf, int x86CountIndex) throws IOException
    {
        bcPos = 0;

        byteCodes[bcPos++] = JavaOpcode.ICONST_0;
        byteCodes[bcPos++] = JavaOpcode.ISTORE;
        byteCodes[bcPos++] = VARIABLE_EXECUTE_COUNT_INDEX;

        for (int i = 0; i < PROCESSOR_ELEMENT_COUNT; i++)
            bcPos = loadPushCodeFor(byteCodes, bcPos, i, cf);

        for (int i=0; i < microcodes.length; i++)
        {
            MicrocodeNode node = microcodes[i];
            int uCode = node.getMicrocode();
            Object[] codes = BytecodeFragments.getTargetsOf(uCode);
            if (codes == null)
                throw new IllegalStateException("Unimplemented microcode: " + MicrocodeNode.getName(uCode));

            for(int j = 0; j < ELEMENT_COUNT; j++)
            {
                if (codes[j] == null)
                    continue;
                
                int[] argIds = BytecodeFragments.getOperands(j, uCode);
                for (int k = 0; k < argIds.length; k++)
                    bcPos = loadOntoStack(byteCodes, bcPos, argIds[k]);
                
                if (node.hasImmediate())
                    bcPos = writeBytecodes(byteCodes, bcPos, cf, BytecodeFragments.getOperation(j, uCode, node.getX86Position(), node.getImmediate()));
                else
                    bcPos = writeBytecodes(byteCodes, bcPos, cf, BytecodeFragments.getOperation(j, uCode, node.getX86Position()));
            }

            for (int j = POPABLE_ELEMENT_COUNT-1; j >= 0; j--)
            {
                if (codes[j] == null)
                    continue;

                bcPos = popFromStack(byteCodes, bcPos, j);
            }
        }

        for (int i=0; i<PROCESSOR_ELEMENT_COUNT; i++)
            bcPos = loadPopCodeFor(byteCodes, bcPos, i, cf);

        byteCodes[bcPos++] = JavaOpcode.LDC;
        byteCodes[bcPos++] = x86CountIndex;
        byteCodes[bcPos++] = JavaOpcode.ILOAD;
        byteCodes[bcPos++] = VARIABLE_EXECUTE_COUNT_INDEX;
        byteCodes[bcPos++] = JavaOpcode.IADD;
        byteCodes[bcPos++] = JavaOpcode.IRETURN;

//          System.out.println("\n\nCompiled: ");
//          for (MicrocodeNode uc : microcodes)
//              System.err.println(uc);
        
//          System.err.println("\n");
//          for (int i=0; i<ints.length; i += JavaOpcode.getOpcodeLength(ints, i))
//          {
//              System.err.print(JavaOpcode.toString(ints[i]));
//              for (int j = 1; j < JavaOpcode.getOpcodeLength(ints, i); j++)
//                  System.err.print(", " + ints[i+j]);
//              System.err.print("\n");
//          }

        cf.setMethodCode("execute", byteCodes, bcPos);
    }

    private void compileX86CountMethod(ClassFile cf, int x86CountIndex)
    {
        int[] code = new int[3];
        code[0] = JavaOpcode.LDC;
        code[1] = x86CountIndex;
        code[2] = JavaOpcode.IRETURN;
        
        cf.setMethodCode("getX86Count", code);
    }

    private void compileX86LengthMethod(ClassFile cf, int x86LengthIndex)
    {
        int[] code = new int[3];
        code[0] = JavaOpcode.LDC;
        code[1] = x86LengthIndex;
        code[2] = JavaOpcode.IRETURN;
        
        cf.setMethodCode("getX86Length", code);
    }


    public static int writeBytecodes(int[] output, int outputPos, ClassFile cf, Object[] bytecodes) throws IOException
    {
        int lastByte = -1;
        for(int i = 0; i < bytecodes.length; i++)
        {
            Object o = bytecodes[i];

            if (o instanceof Integer) 
            {
                lastByte = ((Integer) o).intValue();
                output[outputPos++] = lastByte;
            } 
            else if (o instanceof ConstantPoolSymbol) 
            {
                int index = cf.addToConstantPool(((ConstantPoolSymbol) o).poolEntity());
                switch(JavaOpcode.getConstantPoolIndexSize(lastByte)) 
                {
                case 1: 
                    if (index > 0xff) 
                        throw new IllegalStateException();
                    output[outputPos++] = index & 0xff;
                    break;
                case 2:
                    output[outputPos++] = index >>> 8;
                    output[outputPos++] = index & 0xff;
                    break;
                default: 
                    throw new IllegalStateException();
                }
            } 
            else 
                throw new IllegalStateException(o.toString() + "    " + BytecodeFragments.X86LENGTH + "     " + BytecodeFragments.IMMEDIATE);
        }

        return outputPos;
    }

}
