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

public class RPNNode
{
    private int id, count, useCount, subtreeIndex;
    private Vector argLinks;
    private boolean resultInLocalVariable, externalUcode;
    private MicrocodeNode parent;

    private static int counter = 0;

    public RPNNode(int id, MicrocodeNode parent)
    {
        this.id = id;
        this.parent = parent;

        useCount = 0;
        subtreeIndex = -1;
        argLinks = new Vector();
        count = counter++;
        resultInLocalVariable = false;

        if (parent != null)
            externalUcode = parent.hasExternalEffect();
        else
            externalUcode = false;
    }

    private Object[] getByteCodes()
    {
        if (parent == null)
            return BytecodeFragments.pushCode(id);
        
        if (parent.hasImmediate())
            return BytecodeFragments.getOperation(id, getMicrocode(), parent.getX86Position(), parent.getImmediate());
        else
            return BytecodeFragments.getOperation(id, getMicrocode(), parent.getX86Position());
    }

    public boolean hasExternalEffect()
    {
        return externalUcode;
    }

    public int getID()
    {
        return id;
    }

    public int getMicrocode()
    {
        if (parent == null)
            return -1;
        
        return parent.getMicrocode();
    }

    public boolean hasLinks()
    {
        return argLinks.size() > 0;
    }
    
    public void linkTo(RPNNode link)
    {
        argLinks.add(link);
    }   

    public int markSubtrees(int index)
    {
        useCount++;
        if (subtreeIndex < 0)
        {
            if ((useCount > 1) || hasExternalEffect())
                subtreeIndex = index++;
        }

	if (useCount == 1) 
        {
	    for (int i=0; i<argLinks.size(); i++)
		index = ((RPNNode) argLinks.elementAt(i)).markSubtrees(index);
	}

        return index;
    }

    public void print(String indent)
    {
        System.out.println(indent+"["+id+"] by "+MicrocodeNode.getName(getMicrocode())+"  {"+count+" used "+useCount+"}");
        if (argLinks.size() == 0)
            return;
        System.out.println(indent+"{");
        for (int i=0; i<argLinks.size(); i++)
            ((RPNNode) argLinks.elementAt(i)).print(indent+" ");
        System.out.println(indent+"}");
    }

    public void print()
    {
        print("");
    }

    public static void writeBytecodes(OutputStream output, 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.write(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.write(index & 0xff);
                    break;
                case 2:
                    output.write(index >>> 8);
                    output.write(index & 0xff);
                    break;
                default: throw new IllegalStateException();
                }
            } 
            else 
                throw new IllegalStateException(o.toString()+"    "+BytecodeFragments.X86LENGTH+"     "+BytecodeFragments.IMMEDIATE);
        }
    }

    public void write(OutputStream output, ClassFile cf, boolean leaveResultOnStack) throws IOException
    {
        if (resultInLocalVariable)
        {
            if (!leaveResultOnStack)
                return;

            switch (id)
            {
            case QandDCompiler.PROCESSOR_ELEMENT_ES:
            case QandDCompiler.PROCESSOR_ELEMENT_CS:
            case QandDCompiler.PROCESSOR_ELEMENT_SS:
            case QandDCompiler.PROCESSOR_ELEMENT_DS:
            case QandDCompiler.PROCESSOR_ELEMENT_FS:
            case QandDCompiler.PROCESSOR_ELEMENT_GS:
            case QandDCompiler.PROCESSOR_ELEMENT_SEG0:
                output.write(JavaOpcode.ALOAD);
                break;
            case QandDCompiler.PROCESSOR_ELEMENT_MEMORY_WRITE:
            case QandDCompiler.PROCESSOR_ELEMENT_IOPORT_WRITE:
                return;
            default:
                output.write(JavaOpcode.ILOAD);
            }
                
            output.write(subtreeIndex);
            return;
        }

        for (int i=0; i<argLinks.size(); i++)
            ((RPNNode) argLinks.elementAt(i)).write(output, cf, true);

        writeBytecodes(output, cf, getByteCodes());

        if ((subtreeIndex >= 0) && (subtreeIndex < 0xFF))
        {
            resultInLocalVariable = true;
            if (leaveResultOnStack)
                output.write(JavaOpcode.DUP);

            switch (id)
            {
            case QandDCompiler.PROCESSOR_ELEMENT_ES:
            case QandDCompiler.PROCESSOR_ELEMENT_CS:
            case QandDCompiler.PROCESSOR_ELEMENT_SS:
            case QandDCompiler.PROCESSOR_ELEMENT_DS:
            case QandDCompiler.PROCESSOR_ELEMENT_FS:
            case QandDCompiler.PROCESSOR_ELEMENT_GS:
            case QandDCompiler.PROCESSOR_ELEMENT_SEG0:
                output.write(JavaOpcode.ASTORE);
                break;
            case QandDCompiler.PROCESSOR_ELEMENT_MEMORY_WRITE:
            case QandDCompiler.PROCESSOR_ELEMENT_IOPORT_WRITE:
                return;
           default:
                output.write(JavaOpcode.ISTORE);
            }
                
            output.write(subtreeIndex);
        }
    }
}
