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

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

public class FragmentParser implements MicrocodeSet
{
    private static final String[] NO_ARGS = new String[0];

    private CharSequence source;
    private int position, lineNumber, byteOffset, totalByteLength;

    private String uCodeName, resultName;
    private String[] args;
    private Hashtable labels;
    private Vector instructions;

    private static Hashtable microcodeIndex = new Hashtable();
    private static Hashtable qanddIndex = new Hashtable();
    private static Hashtable opcodeIndex = new Hashtable();
    private static Hashtable constantPoolIndex = new Hashtable();
    static 
    {
        try
        {
            Field[] fields = MicrocodeSet.class.getDeclaredFields();
            for (int i=0; i<fields.length; i++)
            {             
                int mods = fields[i].getModifiers();
                if (!Modifier.isStatic(mods) || !Modifier.isPublic(mods) || !Modifier.isFinal(mods))
                    continue;
                microcodeIndex.put(fields[i].getName(), new Integer(fields[i].getInt(null)));
            }
        }
        catch (Throwable t)
        {
            System.out.println("Warning: microcode lookup table not completed");
            t.printStackTrace();
        }

        try
        {
            Field[] fields = QandDCompiler.class.getDeclaredFields();
            for (int i=0; i<fields.length; i++)
            {
                int mods = fields[i].getModifiers();
                if (!Modifier.isStatic(mods) || !Modifier.isPublic(mods) || !Modifier.isFinal(mods))
                    continue;

                int value = fields[i].getInt(null);
                if ((value >= 0) && (value < QandDCompiler.MICROCODE_ELEMENT_LIMIT))
                {
                    String name = fields[i].getName();
                    if (name.equals("PROCESSOR_ELEMENT_LIMIT"))
                        continue;

                    if (name.startsWith("PROCESSOR_ELEMENT_"))
                        name = name.substring("PROCESSOR_ELEMENT_".length());
                    qanddIndex.put(name, new Integer(value));
                }
            }
        }
        catch (Throwable t)
        {
            System.out.println("Warning: qandd lookup table not completed");
            t.printStackTrace();
        }
        
        try
        {
            Field[] fields = JavaOpcode.class.getDeclaredFields();
            for (int i=0; i<fields.length; i++)
            {
                int mods = fields[i].getModifiers();
                if (!Modifier.isStatic(mods) || !Modifier.isPublic(mods) || !Modifier.isFinal(mods))
                    continue;
                if (fields[i].getType() != Integer.TYPE)
                    continue;
                opcodeIndex.put(fields[i].getName(), new Integer(fields[i].getInt(null)));
            }
        }
        catch (Throwable t)
        {
            System.out.println("Warning: opcode lookup table not completed");
            t.printStackTrace();
        }

        constantPoolIndex.put("[parityMap]", BytecodeFragments.field(RealModeTemplateBlock.class, "parityMap"));
        constantPoolIndex.put("[ioports]", BytecodeFragments.field("ioports"));
        constantPoolIndex.put("[getByte]", BytecodeFragments.method(Segment.class, "getByte", Integer.TYPE));
        constantPoolIndex.put("[getWord]", BytecodeFragments.method(Segment.class, "getWord", Integer.TYPE));
        constantPoolIndex.put("[getDoubleWord]", BytecodeFragments.method(Segment.class, "getDoubleWord", Integer.TYPE));
        constantPoolIndex.put("[setByte]", BytecodeFragments.method(Segment.class, "setByte", Integer.TYPE, Byte.TYPE));
        constantPoolIndex.put("[setWord]", BytecodeFragments.method(Segment.class, "setWord", Integer.TYPE, Short.TYPE));
        constantPoolIndex.put("[setDoubleWord]", BytecodeFragments.method(Segment.class, "setDoubleWord", Integer.TYPE, Integer.TYPE));
        constantPoolIndex.put("[setSelector]", BytecodeFragments.method(Segment.class, "setSelector", Integer.TYPE));
        constantPoolIndex.put("[getSelector]", BytecodeFragments.method(Segment.class, "getSelector"));
        constantPoolIndex.put("[ioPortWriteByte]", BytecodeFragments.method(IOPortHandler.class, "ioPortWriteByte", Integer.TYPE, Integer.TYPE));
        constantPoolIndex.put("[ioPortWriteWord]", BytecodeFragments.method(IOPortHandler.class, "ioPortWriteWord", Integer.TYPE, Integer.TYPE));
        constantPoolIndex.put("[ioPortWriteLong]", BytecodeFragments.method(IOPortHandler.class, "ioPortWriteLong", Integer.TYPE, Integer.TYPE));
        constantPoolIndex.put("[ioPortReadByte]", BytecodeFragments.method(IOPortHandler.class, "ioPortReadByte", Integer.TYPE));
        constantPoolIndex.put("[ioPortReadWord]", BytecodeFragments.method(IOPortHandler.class, "ioPortReadWord", Integer.TYPE));
        constantPoolIndex.put("[ioPortReadLong]", BytecodeFragments.method(IOPortHandler.class, "ioPortReadLong", Integer.TYPE));
        constantPoolIndex.put("[IMMEDIATE]", BytecodeFragments.IMMEDIATE);
        constantPoolIndex.put("[X86LENGTH]", BytecodeFragments.X86LENGTH);
    }
        
    public FragmentParser(CharSequence src)
    {
        source = src;
        position = 0;
        lineNumber = 1;
        
        labels = new Hashtable();
        instructions = new Vector();
        reset();
    }

    private void reset()
    {
        uCodeName = null;
        resultName = null;
        args = NO_ARGS;
        labels.clear();
        byteOffset = 0;
        totalByteLength = 0;
        instructions.removeAllElements();
    }

    private int getNextIndexOf(char ch)
    {
        return getNextIndexOf(ch, position);
    }

    private int getNextIndexOf(char ch, int startPos)
        {
        return getNextIndexOf(ch, startPos, source.length());
    }

    private int getNextIndexOf(char ch, int startPos, int endPos)
        {
        for (int i=startPos; i<endPos; i++)
            if (source.charAt(i) == ch)
                return i;

        return -1;
    }

    private int nextNonWhitespace(int start)
    {
        for (int i=start; i<source.length(); i++)
            if (!Character.isWhitespace(source.charAt(i)))
                return i;
        
        return -1;
    }

    private int previousNonWhitespace(int start)
    {
        for (int i=start; i>=0; i--)
            if (!Character.isWhitespace(source.charAt(i)))
                return i;
        
        return -1;
    }

    private void advancePosition(int absolutePosition)
    {
        for (; position<absolutePosition; position++)
            if (source.charAt(position) == '\n')
                lineNumber++;
    }
    
    private void syntaxError(String message)
    {
        throw new IllegalStateException(message+" at line "+lineNumber);
    }
    
    private boolean parseFragmentHeader()
    {
        int fragmentStart = nextNonWhitespace(position);
        if (fragmentStart < 0)
            return false;

        int resultStart = getNextIndexOf('[');
        int resultEnd = getNextIndexOf(']');
        if ((resultEnd < 0) || (resultStart < 0) || (resultEnd <= resultStart))
            syntaxError("Malformed result specifier");
        uCodeName = source.subSequence(fragmentStart, resultStart).toString().trim();

        resultStart = nextNonWhitespace(resultStart+1);
        resultEnd = previousNonWhitespace(resultEnd-1);
        if (resultEnd <= resultStart)
            syntaxError("Missing result specifier near "+uCodeName);
        resultName = source.subSequence(resultStart, resultEnd+1).toString();

        int argStart = getNextIndexOf('(');
        int argEnd = getNextIndexOf(')');
        if ((argStart < 0) || (argEnd < 0))
            syntaxError("Malformed arg specifier");

        argStart = nextNonWhitespace(argStart+1);
        argEnd = previousNonWhitespace(argEnd-1);

        args = NO_ARGS;
        if (argStart < argEnd)
        {
            int argc = 1;
            for (int i=argStart; i<argEnd; i++)
                if (source.charAt(i) == ',')
                    argc++;

            args = new String[argc];
            argc = 0;
            int pos = argStart;
            for (int i=argStart; i<argEnd; i++)
                if (source.charAt(i) == ',')
                {
                    args[argc++] = source.subSequence(pos, i).toString().trim();
                    pos = i+1;
                }
            args[argc] = source.subSequence(pos, argEnd+1).toString().trim();
        }

        advancePosition(argEnd+2);
        return true;
    }

    private Object getObjectForToken(String token)
    {
        if (token.startsWith("#"))
            return token;

        Object symbol = constantPoolIndex.get(token);
        if (symbol != null)
            return symbol;

        if (token.startsWith("[") && token.endsWith("]"))
        {
            token = token.substring(1, token.length()-1).trim();
            try
            {
                boolean not = false;
                if (token.startsWith("~"))
                {
                    token = token.substring(1);
                    not = true;
                }

                if (token.endsWith("l"))
                {
                    token = token.substring(0, token.length()-1);

                    long value = 0;
                    if (token.startsWith("0x"))
                        value = Long.parseLong(token.substring(2), 16);
                    else
                        value = Long.parseLong(token);
                    
                    if (not)
                        return BytecodeFragments.longint(~value);
                    else
                        return BytecodeFragments.longint(value);
                }
                else
                {
                    int value = 0;
                    if (token.startsWith("0x"))
                        value = (int) Long.parseLong(token.substring(2), 16);
                    else
                        value = (int) Long.parseLong(token);

                    if (not)
                        return BytecodeFragments.integer(~value);
                    else
                        return BytecodeFragments.integer(value);
                }
            }
            catch (Exception e)
            {
                syntaxError("Cannot parse constant token '"+token+"'");
            }
        }

        try
        {
            return Integer.valueOf(token);
        }
        catch (Exception e) 
        {
            return token;
        }
    }

    private void parseLine(int lineStart, int lineEnd)
    {
        int labelDelimeter = getNextIndexOf(':', lineStart, lineEnd);
        if (labelDelimeter > 0)
        {
            advancePosition(labelDelimeter);
            String label = source.subSequence(lineStart, labelDelimeter).toString().trim();
            labels.put(label, new Integer(byteOffset));
            lineStart = labelDelimeter+1;
        }
        
        int tokenStart = nextNonWhitespace(lineStart);
        String token = "";
        Integer javaOpcode = null;

        for (int i=tokenStart; i<lineEnd; i++)
        {
            if (Character.isWhitespace(source.charAt(i)))
            {
                token = source.subSequence(tokenStart, i).toString();
                if (javaOpcode == null)
                {
                    javaOpcode = (Integer) opcodeIndex.get(token);
                    if (javaOpcode == null)
                        syntaxError("Undefined opcode "+token);

                    try
                    {
                        byteOffset += JavaOpcode.getConstantPoolIndexSize(javaOpcode.intValue()) - 1;
                    }
                    catch (IllegalStateException e) {}
                }
                
                if (token.startsWith("#"))
                {
                    byteOffset++;
                    instructions.add("+==+");
                }

                instructions.add(getObjectForToken(token));
                byteOffset++;
                tokenStart = nextNonWhitespace(i);
            }
        }
        token = source.subSequence(tokenStart, lineEnd).toString();
        if (token.startsWith("#"))
        {
            byteOffset++;
            instructions.add("+==+");
        }
        instructions.add(getObjectForToken(token));
        byteOffset++;

        totalByteLength = byteOffset;
    }

    private void parseCodeBody()
    {
        int bodyStart = getNextIndexOf('{');
        int bodyEnd = getNextIndexOf('}');
        if ((bodyStart < 0) || (bodyEnd < 0))
            syntaxError("Malformed code body");

        int lineStart = bodyStart+1;
        boolean inComment = false;
        for (int lineEnd=lineStart; lineEnd<bodyEnd; lineEnd++)
        {
            if (inComment)
            {
                if (source.charAt(lineEnd) == '\n')
                {
                    inComment = false;
                    lineEnd = lineStart = lineEnd+1;
                }
            }
            else
            {
                if (source.charAt(lineEnd) == '/')
                    inComment = true;
            }

            if (!inComment)
            {
                if (source.charAt(lineEnd) != ';')
                    continue;

                advancePosition(lineStart);
                parseLine(lineStart, lineEnd);
                lineStart = lineEnd+1;
            }
        }
        advancePosition(bodyEnd+1);

        byteOffset = 0;
        for (int i=0; i<instructions.size(); i++)
        {
            Object obj = instructions.elementAt(i);
            byteOffset++;
            if (!(obj instanceof String))
                continue;

            String instr = (String) obj;
            try
            {
                Integer javaOpcode = (Integer) opcodeIndex.get(instr);
                byteOffset += JavaOpcode.getConstantPoolIndexSize(javaOpcode.intValue()) - 1;
            }
            catch (Exception e) {}
            if (!instr.startsWith("#"))
                continue;

            int refLine = -1;
            if (instr.equalsIgnoreCase("#end"))
                refLine = totalByteLength;
            else
            {
                String ref = instr.substring(1).trim();
                Integer target = (Integer) labels.get(ref);
                if (target == null)
                    syntaxError("Unknown label "+ref);
                refLine = target.intValue();
            }
            
            int jumpDistance = refLine - byteOffset + 3;
            int hiByte = 0xFF & (jumpDistance >> 8);
            int loByte = 0xFF & jumpDistance;
            instructions.setElementAt(new Integer(hiByte), i-1);
            instructions.setElementAt(new Integer(loByte), i);
        }
    }

    public boolean parseNext()
    {
        reset();
        if (!parseFragmentHeader())
            return false;
        parseCodeBody();
        return true;
    }

    public void printFragment()
    {
        System.out.println("Microcode "+uCodeName+" ("+microcodeIndex.get(uCodeName)+") - Element "+resultName+" ("+qanddIndex.get(resultName)+")");
        for (int i=0; i<args.length; i++)
            System.out.println("  Arg["+i+"] = "+args[i]+" ("+qanddIndex.get(args[i])+")");
        
        System.out.println("Bytecodes...");
        for (int i=0; i<instructions.size(); i++)
        {
            Object val = instructions.elementAt(i);
            if (val instanceof Number)
                System.out.println("     "+i+":  "+val);
            else if (val instanceof ConstantPoolSymbol)
                System.out.println("     "+i+":  {"+val+"}");
            else
                System.out.println("     "+i+":  "+val+"  ("+opcodeIndex.get(val)+")");
        }    
    }

    public void insertIntoFragmentArrays(Object[][][] operations, int[][][] operandArray)
    {
        try
        {
            Integer codeVal = (Integer) microcodeIndex.get(uCodeName);
            if (codeVal == null)
                syntaxError("Unknown microcode "+uCodeName);

            int uCode = codeVal.intValue();
            if (operations[uCode] == null)
                operations[uCode] = new Object[QandDCompiler.MICROCODE_ELEMENT_LIMIT][];
            if (operandArray[uCode] == null)
                operandArray[uCode] = new int[QandDCompiler.MICROCODE_ELEMENT_LIMIT][];

	    Integer elementValue = (Integer) qanddIndex.get(resultName);
	    if (elementValue == null)
		syntaxError("Unknown PROCESSOR_ELEMENT " + resultName);
            int elementId = elementValue.intValue();

            int[] argIds = new int[args.length];
            for (int i=0; i<args.length; i++)
                argIds[i] = ((Integer) qanddIndex.get(args[i])).intValue();
            operandArray[uCode][elementId] = argIds;

            Object[] byteCodes = new Object[instructions.size()];
            operations[uCode][elementId] = byteCodes;
            for (int i=0; i<instructions.size(); i++)
            {
                Object val = instructions.elementAt(i);
                if (val instanceof Number)
                    byteCodes[i] = val;
                else if (val instanceof ConstantPoolSymbol)
                    byteCodes[i] = val;
                else if ((val == BytecodeFragments.IMMEDIATE) || (val == BytecodeFragments.X86LENGTH))
                    byteCodes[i] = val;
                else
                {
                    byteCodes[i] = (Integer) opcodeIndex.get(val);
		    if (byteCodes[i] == null)
			syntaxError("Unknown java opcode " + val);
		}
            }    

            /* Used to run through the fragments to check their individual stack depths
              for (int i=0; i<byteCodes.length; i++)
            {
                if (byteCodes[i] == BytecodeFragments.IMMEDIATE)
                    byteCodes[i] = new Integer(42);
                else if (byteCodes[i] == BytecodeFragments.X86LENGTH)
                    byteCodes[i] = new Integer(1);
            }

            ClassFile cf = QandDClassFileBuilder.createNewSkeletonClass();
            ByteArrayOutputStream dummy = new ByteArrayOutputStream();
            try
            {
                RPNNode.writeBytecodes(dummy, cf, byteCodes);
                dummy.write(0);
            }
            catch (IOException e) {}

            int[] ints = Compiler2.promoteArray(dummy.toByteArray());
            System.out.println(uCodeName+"["+resultName+"]");
            int totalDelta = -JavaCodeAnalyser.getMaxStackDepth(ints, cf);
            System.out.println("Bytecode length "+ints.length+"  ARGS "+argIds.length+" Total Delta "+totalDelta);
            if ((elementId == Compiler2.PROCESSOR_ELEMENT_MEMORY_WRITE) || (elementId == Compiler2.PROCESSOR_ELEMENT_IOPORT_WRITE))
            {
                if (argIds.length + totalDelta != 0)
                    System.out.println("     ^^^^^^^^^^^^^^^^^^");
            }
            else
            {
                if (argIds.length + totalDelta != 1)
                    System.out.println("     ^^^^^^^^^^^^^^^^^^");
                    }*/
        }
        catch (Exception e)
        {
            System.out.println("Warning: exception loading uCode fragments");
            e.printStackTrace();
        }
    }

    /** Fragment format:

        JA_O8[EIP](EIP, REG0, CFLAG, ZFLAG)
        {
            IFNE #0;
            IFNE #1;
            I2B;
            IADD;
            GOTO #end;
            0: POP;
            1: POP;
        }

        LOAD0_MEM_BYTE[REG0](SEG0, ADDR0)
        {
            INVOKEINTERFACE [getByte] 2 0;
            LDC [0xff];
            IAND;
        }
    */

    public static CharSequence removeSlashStarComments(CharSequence src)
    {
        StringBuffer result = new StringBuffer(src);
        while (true)
        {
            int start = result.indexOf("/*");
            if (start < 0)
                break;

            int end = result.indexOf("*/", start+2);
            if (end < 0)
                end = result.length();
            else 
                end += 2;
            
            result.replace(start, end, "");
        }

        return result;
    }

    public static void main(String[] args)
    {
        FragmentParser p = new FragmentParser("JUMP_FAR_O16[EIP]()\n{LDC [~0xffff];\n}\n");

        p.parseNext();
        p.printFragment();
    }
}
