/*
    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.qanddcompiler;

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

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

public class QandDCompiler 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_LIMIT = 24;

    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;

    public static final int MICROCODE_ELEMENT_LIMIT = 30;

    private static final int VARIABLE_EXECUTE_COUNT_INDEX = 10;

    private static int classIndex = 0;

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

    public RealModeCodeBlock getRealModeCodeBlock(InstructionSource source)
    {
        MicrocodeNode[] microcodes = MicrocodeNode.getMicrocodes(source);
        ClassFile newClass = null;
        try 
        {
            newClass = QandDClassFileBuilder.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("QQ_"+(classIndex++));

            return (RealModeCodeBlock) QandDClassFileBuilder.instantiateClass(newClass);
        } 
        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 block QQ", e);
        }
        catch (NullPointerException 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 block QQ", e);
        }
    }

    private static void compileExecuteMethod(MicrocodeNode[] microcodes, ClassFile cf, int x86CountIndex)
    {
        Vector externalEffects = new Vector();
        HashMap args = new HashMap();
        for (int i=0; i<PROCESSOR_ELEMENT_LIMIT; i++)
            args.put(new Integer(i), new RPNNode(i, null));
        
        boolean hasRepeatedOperation = false;
        for (int i=0; i<microcodes.length; i++)
        {
            MicrocodeNode node = microcodes[i];
            int uCode = node.getMicrocode();
            if (MicrocodeNode.hasVariableX86Count(uCode))
                hasRepeatedOperation = true;

            Object[] codes = BytecodeFragments.getTargetsOf(uCode);
            if (codes == null)
                throw new IllegalStateException("Unimplemented Microcode: "+MicrocodeNode.getName(uCode));

            Vector tgts = new Vector();
            for(int j=0; j < codes.length; j++)
            {
                if (codes[j] == null)
                    continue;

                RPNNode rpn = new RPNNode(j, node);
                if (rpn.hasExternalEffect())
                    externalEffects.add(rpn);
                tgts.add(rpn);
                
                int[] argIds = BytecodeFragments.getOperands(j, uCode);
                if (argIds == null)
                    System.out.println("NULL IDS FOR: "+j+"  "+uCode);

                for (int k=0; k<argIds.length; k++)
                {
                    RPNNode arg = (RPNNode) args.get(new Integer(argIds[k]));
                    rpn.linkTo(arg);
                }
            }
            
            for (int j=0; j<tgts.size(); j++)
            {
                RPNNode rpn = (RPNNode) tgts.elementAt(j);
                args.put(new Integer(rpn.getID()), rpn);
            }
        }

        for (int i=PROCESSOR_ELEMENT_LIMIT; i<MICROCODE_ELEMENT_LIMIT; i++)
            args.remove(new Integer(i));

        int localVariableIndex = VARIABLE_EXECUTE_COUNT_INDEX; 
        if (hasRepeatedOperation)
            localVariableIndex++;
        for (int i=0; i<externalEffects.size(); i++)
	    localVariableIndex = ((RPNNode)externalEffects.elementAt(i)).markSubtrees(localVariableIndex);
                      
        int affectedCount = 0;
	for (Iterator itt = args.values().iterator(); itt.hasNext();)
	{
            RPNNode n = (RPNNode) itt.next();
            localVariableIndex = n.markSubtrees(localVariableIndex);
            if (n.hasLinks())
                affectedCount++;
        }

        ByteArrayOutputStream byteCodes = new ByteArrayOutputStream();
        if (hasRepeatedOperation)
        {
            byteCodes.write(JavaOpcode.ICONST_0);
            byteCodes.write(JavaOpcode.ISTORE);
            byteCodes.write(VARIABLE_EXECUTE_COUNT_INDEX);
        }

        for (int i=0; i<externalEffects.size(); i++)
        {
            RPNNode rpn = (RPNNode) externalEffects.elementAt(i);
            try
            {
                rpn.write(byteCodes, cf, false);
            }
            catch (IOException e) {}
        }

        Iterator itt2 = args.values().iterator();
        int index = 0;
        RPNNode[] roots = new RPNNode[affectedCount];
        while (itt2.hasNext())
        {
            RPNNode n = (RPNNode) itt2.next();
            if (!n.hasLinks())
                continue;
            
            try
            {
                n.write(byteCodes, cf, true);
                roots[index++] = n;
            }
            catch (IOException e) {}
            //System.out.println("Formula for "+n.getID());
            //n.print();
        }

        for (int i=roots.length-1; i>=0; i--)
        {
            try
            {
                RPNNode.writeBytecodes(byteCodes, cf, BytecodeFragments.popCode(roots[i].getID()));
            }
            catch (IOException e) {}
        }

        byteCodes.write(JavaOpcode.LDC);
        byteCodes.write(x86CountIndex);
        if (hasRepeatedOperation)
        {
            byteCodes.write(JavaOpcode.ILOAD);
            byteCodes.write(VARIABLE_EXECUTE_COUNT_INDEX);
            byteCodes.write(JavaOpcode.IADD);
        }
        byteCodes.write(JavaOpcode.IRETURN);

        byte[] bytes = byteCodes.toByteArray();

        int[] ints = new int[bytes.length];
        for(int i = 0; i < ints.length; i++)
            ints[i] = 0xff & bytes[i];

        /*System.err.println();
        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");
            }*/

        try 
        {
            cf.setMethodCode("execute", ints);
        } 
        catch (Error e) 
        {
            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");
            }

            throw e;
        }
    }

    private static void compileX86CountMethod(ClassFile cf, int x86CountIndex)
    {
        ByteArrayOutputStream byteCodes = new ByteArrayOutputStream();
        
        byteCodes.write(JavaOpcode.LDC);
        byteCodes.write(x86CountIndex);
        byteCodes.write(JavaOpcode.IRETURN);
        
        byte[] bytes = byteCodes.toByteArray();
        
        int[] ints = new int[bytes.length];
        for(int i = 0; i < ints.length; i++)
            ints[i] = 0xff & bytes[i];
        
        cf.setMethodCode("getX86Count", ints);
    }

    private static void compileX86LengthMethod(ClassFile cf, int x86LengthIndex)
    {
        ByteArrayOutputStream byteCodes = new ByteArrayOutputStream();
        
        byteCodes.write(JavaOpcode.LDC);
        byteCodes.write(x86LengthIndex);
        byteCodes.write(JavaOpcode.IRETURN);
        
        byte[] bytes = byteCodes.toByteArray();
        
        int[] ints = new int[bytes.length];
        for(int i = 0; i < ints.length; i++)
            ints[i] = 0xff & bytes[i];
        
        cf.setMethodCode("getX86Length", ints);
    }
}
