/*
    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 org.jpc.classfile.*;
import java.lang.reflect.*;
import java.util.*;
import java.io.*;

import org.jpc.emulator.processor.*;
import org.jpc.emulator.memory.codeblock.optimised.*;
import org.jpc.emulator.memory.codeblock.*;
import org.jpc.emulator.motherboard.*;
import org.jpc.classfile.*;

public class BytecodeFragments implements MicrocodeSet
{
    public static final Object IMMEDIATE = new Object();
    public static final Object X86LENGTH = new Object();

    private static Object[][][] operationArray = new Object[MICROCODE_LIMIT][][];
    private static int[][][] operandArray = new int[MICROCODE_LIMIT][ByteCodeCompiler.ELEMENT_COUNT][];

    private static Object[][] pushCodeArray = new Object[ByteCodeCompiler.ELEMENT_COUNT][];
    private static Object[][] popCodeArray = new Object[ByteCodeCompiler.PROCESSOR_ELEMENT_COUNT][];

    static 
    {
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_EAX] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                               new Integer(JavaOpcode.GETFIELD), field("eax") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_ECX] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                               new Integer(JavaOpcode.GETFIELD), field("ecx") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_EDX] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                               new Integer(JavaOpcode.GETFIELD), field("edx") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_EBX] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                               new Integer(JavaOpcode.GETFIELD), field("ebx") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_ESP] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                               new Integer(JavaOpcode.GETFIELD), field("esp") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_EBP] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                               new Integer(JavaOpcode.GETFIELD), field("ebp") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_ESI] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                               new Integer(JavaOpcode.GETFIELD), field("esi") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_EDI] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                               new Integer(JavaOpcode.GETFIELD), field("edi") };
        
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_EIP] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                               new Integer(JavaOpcode.GETFIELD), field("eip") };
        
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_ZFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                 new Integer(JavaOpcode.INVOKEVIRTUAL), method("getZeroFlag") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_SFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                 new Integer(JavaOpcode.INVOKEVIRTUAL), method("getSignFlag") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_PFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                 new Integer(JavaOpcode.INVOKEVIRTUAL), method("getParityFlag") };
        
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_OFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                 new Integer(JavaOpcode.INVOKEVIRTUAL), method("getOverflowFlag") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_CFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                 new Integer(JavaOpcode.INVOKEVIRTUAL), method("getCarryFlag") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_AFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                 new Integer(JavaOpcode.INVOKEVIRTUAL), method("getAuxiliaryCarryFlag") };
        
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_DFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                 new Integer(JavaOpcode.GETFIELD), field("eflagsDirection") };
        
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_IFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                 new Integer(JavaOpcode.GETFIELD), field("eflagsInterruptEnable") };
        
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_ES] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.GETFIELD), field("es") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_CS] = new Object[] { new Integer(JavaOpcode.ALOAD_1),
                                                                              new Integer(JavaOpcode.GETFIELD), field("cs") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_SS] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.GETFIELD), field("ss") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_DS] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.GETFIELD), field("ds") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_FS] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.GETFIELD), field("fs") };
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_GS] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.GETFIELD), field("gs") };
        
        pushCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_ADDR0] = new Object[]{new Integer(JavaOpcode.ICONST_0)};
    }

    static
    {
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_EAX] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.SWAP), 
                                                                              new Integer(JavaOpcode.PUTFIELD), field("eax") };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_ECX] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.SWAP), 
                                                                              new Integer(JavaOpcode.PUTFIELD), field("ecx") };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_EDX] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.SWAP), 
                                                                              new Integer(JavaOpcode.PUTFIELD), field("edx") };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_EBX] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.SWAP), 
                                                                              new Integer(JavaOpcode.PUTFIELD), field("ebx") };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_ESP] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.SWAP), 
                                                                              new Integer(JavaOpcode.PUTFIELD), field("esp") };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_EBP] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.SWAP), 
                                                                              new Integer(JavaOpcode.PUTFIELD), field("ebp") };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_ESI] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.SWAP), 
                                                                              new Integer(JavaOpcode.PUTFIELD), field("esi") };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_EDI] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.SWAP), 
                                                                              new Integer(JavaOpcode.PUTFIELD), field("edi") };

        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_EIP] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                              new Integer(JavaOpcode.SWAP), 
                                                                              new Integer(JavaOpcode.PUTFIELD), field("eip") };
        
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_ZFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                new Integer(JavaOpcode.SWAP), 
                                                                                new Integer(JavaOpcode.INVOKEVIRTUAL), method("setZeroFlag", Boolean.TYPE) };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_SFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                new Integer(JavaOpcode.SWAP), 
                                                                                new Integer(JavaOpcode.INVOKEVIRTUAL), method("setSignFlag", Boolean.TYPE) };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_PFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                new Integer(JavaOpcode.SWAP), 
                                                                                new Integer(JavaOpcode.INVOKEVIRTUAL), method("setParityFlag", Boolean.TYPE) };
        
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_OFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                new Integer(JavaOpcode.SWAP), 
                                                                                new Integer(JavaOpcode.INVOKEVIRTUAL), method("setOverflowFlag", Boolean.TYPE) };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_CFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                new Integer(JavaOpcode.SWAP), 
                                                                                new Integer(JavaOpcode.INVOKEVIRTUAL), method("setCarryFlag", Boolean.TYPE) };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_AFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                new Integer(JavaOpcode.SWAP), 
                                                                                new Integer(JavaOpcode.INVOKEVIRTUAL), method("setAuxiliaryCarryFlag", Boolean.TYPE) };

        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_DFLAG] = new Object[] { new Integer(JavaOpcode.ALOAD_1), 
                                                                                new Integer(JavaOpcode.SWAP), 
                                                                                new Integer(JavaOpcode.PUTFIELD), field("eflagsDirection") };

        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_IFLAG] = new Object[] { new Integer(JavaOpcode.DUP),
                                                                                new Integer(JavaOpcode.ALOAD_1),
                                                                                new Integer(JavaOpcode.DUP_X2),
                                                                                new Integer(JavaOpcode.SWAP),
                                                                                new Integer(JavaOpcode.PUTFIELD), field("eflagsInterruptEnable"),
                                                                                new Integer(JavaOpcode.PUTFIELD), field("eflagsInterruptEnableSoon") };

        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_ES] = new Object[] { new Integer(JavaOpcode.ALOAD_1),
                                                                             new Integer(JavaOpcode.SWAP),
                                                                             new Integer(JavaOpcode.PUTFIELD), field("es") };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_CS] = new Object[] { new Integer(JavaOpcode.ALOAD_1),
                                                                             new Integer(JavaOpcode.SWAP),
                                                                             new Integer(JavaOpcode.PUTFIELD), field("cs") };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_SS] = new Object[] { new Integer(JavaOpcode.ALOAD_1),
                                                                             new Integer(JavaOpcode.SWAP),
                                                                             new Integer(JavaOpcode.PUTFIELD), field("ss") };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_DS] = new Object[] { new Integer(JavaOpcode.ALOAD_1),
                                                                             new Integer(JavaOpcode.SWAP),
                                                                             new Integer(JavaOpcode.PUTFIELD), field("ds") };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_FS] = new Object[] { new Integer(JavaOpcode.ALOAD_1),
                                                                             new Integer(JavaOpcode.SWAP),
                                                                             new Integer(JavaOpcode.PUTFIELD), field("fs") };
        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_GS] = new Object[] { new Integer(JavaOpcode.ALOAD_1),
                                                                             new Integer(JavaOpcode.SWAP),
                                                                             new Integer(JavaOpcode.PUTFIELD), field("gs") };

        popCodeArray[ByteCodeCompiler.PROCESSOR_ELEMENT_ADDR0] = new Object[] { new Integer(JavaOpcode.POP) };
    }


    static 
    {
        try 
        {
	    loadStream(ClassLoader.getSystemClassLoader().getResourceAsStream("org/jpc/emulator/memory/codeblock/bytecodecompiler/bytecodes.jasm"));            
        } 
        catch (Exception e) 
        {
            System.err.println("failed loading bytecodes from .jasm:" + e);
        }
    }


    private BytecodeFragments() {}

    public static void loadStream(InputStream data) throws IOException
    {
	StringBuilder input = new StringBuilder();
	
	byte[] buffer = new byte[1024];
	while (true)
        {
            int read = data.read(buffer);
            if (read < 0)
                break;
            for (int i=0; i<read; i++)
                input.append((char) buffer[i]);
        }

	FragmentParser p = new FragmentParser(FragmentParser.removeSlashStarComments(input));
	while(p.parseNext())
	    p.insertIntoFragmentArrays(operationArray, operandArray);
    }

    public static Object[] getOperation(int element, int microcode, int x86Position)
    {
        Object[] temp = operationArray[microcode][element];
        if (temp == null)
            return null;
        temp = Arrays.copyOf(temp, temp.length);
        
        for (int i = 0; i < temp.length; i++) 
        {
            if (temp[i] == X86LENGTH) 
                temp[i] = integer(x86Position);
        }
        
        return temp;
    }

    public static Object[] getOperation(int element, int microcode, int x86Position, int immediate)
    {
        Object[] temp = getOperation(element, microcode, x86Position);
        if (temp == null)
            return null;

        for (int i = 0; i < temp.length; i++) 
        {
            if (temp[i] == IMMEDIATE)
                temp[i] = integer(immediate);
        }

        return temp;
    }

    public static Object[] getTargetsOf(int microcode)
    {
        return operationArray[microcode];
    }

    public static int[] getOperands(int element, int microcode)
    {
        return operandArray[microcode][element];
    }

    public static Object[] pushCode(int element)
    {
        Object[] temp = pushCodeArray[element];
        if (temp == null) 
            throw new IllegalStateException("Non existant CPU Element: "+element);
        return temp;
    }

    public static Object[] popCode(int element)
    {
        Object[] temp = popCodeArray[element];
        if (temp == null) 
            throw new IllegalStateException("Non existant CPU Element: "+element);
        return temp;
    }

    public static Object field(String name)
    {
        try {
            return new ConstantPoolSymbol(Processor.class.getDeclaredField(name));
        } catch (NoSuchFieldException e) {
            throw new IllegalStateException(e);
        }
    }

    public static Object field(Class cls, String name)
    {
        try {
            return new ConstantPoolSymbol(cls.getDeclaredField(name));
        } catch (NoSuchFieldException e) {
            throw new IllegalStateException(e);
        }
    }

    public static Object method(String name)
    {
        return method(name, new Class[0]);
    }

    public static Object method(String name, Class arg)
    {
        return method(name, new Class[]{arg});
    }

    public static Object method(String name, Class arg0, Class arg1)
    {
        return method(name, new Class[]{arg0, arg1});
    }

    public static Object method(String name, Class[] args)
    {
        try {
            return new ConstantPoolSymbol(Processor.class.getDeclaredMethod(name, args));
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException(e);
        }
    }

    public static Object method(Class cls, String name)
    {
        return method(cls, name, new Class[0]);
    }

    public static Object method(Class cls, String name, Class arg)
    {
        return method(cls, name, new Class[]{arg});
    }

    public static Object method(Class cls, String name, Class arg0, Class arg1)
    {
        return method(cls, name, new Class[]{arg0, arg1});
    }

    public static Object method(Class cls, String name, Class[] args)
    {
        try {
            return new ConstantPoolSymbol(cls.getMethod(name, args));
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException(e);
        }
    }

    public static Object integer(int value)
    {
        return new ConstantPoolSymbol(new Integer(value));
    }

    public static Object longint(long value)
    {
	return new ConstantPoolSymbol(new Long(value));
    }

//     public void main()
//     {
//         System.out.println("Hello World");
//     }
    
}


