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

import java.util.*;
import java.io.*;
import java.lang.reflect.*;
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.undo.*;

import org.jpc.debugger.util.*;
import org.jpc.emulator.*;
import org.jpc.emulator.processor.*;
import org.jpc.emulator.motherboard.*;
import org.jpc.emulator.memory.*;
import org.jpc.emulator.memory.codeblock.*;
import org.jpc.emulator.memory.codeblock.basic.*;
import org.jpc.emulator.memory.codeblock.optimised.*;

public class MicrocodeOverlayTable extends JTable implements ListSelectionListener
{
    private Font f;
    private boolean showX86Lengths;
    private int targetColumn;
    private String[] operationNames, operandNames;
    private BlockWrapper[] allBlocks;

    public MicrocodeOverlayTable(TableModel model, int codeBlockColumn, boolean showX86Lengths)
    {
        super(model);
        this.showX86Lengths = showX86Lengths;
        targetColumn = codeBlockColumn;

        operationNames = extractOrdinalNames(FirstStageOperationSet.class);
        operandNames = extractOrdinalNames(FirstStageOperandSet.class);

        f = new Font("Monospaced", Font.PLAIN, 12);
        allBlocks = new BlockWrapper[0];
        getSelectionModel().addListSelectionListener(this);
    }

    public static String[] extractOrdinalNames(Class tgt)
    {
        Field[] fields = tgt.getDeclaredFields();
        int max = 0;
        for (int i=0; i<fields.length; i++)
        {
            try
            {
                max = Math.max(max, fields[i].getInt(null));
            }
            catch (Throwable t) {}
        }
        
        String[] result = new String[max+1];
        for (int i=0; i<fields.length; i++)
        {
            try
            {
                String name = fields[i].getName();
                int value = fields[i].getInt(null);
                result[value] = name;
            }
            catch (Throwable t) {}
        }

        return result;
    }

    public Rectangle getOverlayRect(int row, int maxExtent)
    {
        Rectangle r1 = getCellRect(row, targetColumn, true);
        
        CodeBlock cb = getCodeBlockForRow(row);
        if (cb == null)
            return r1;

        int rows = Math.min(cb.getX86Length(), maxExtent);
        Rectangle r2 = getCellRect(row + rows - 1, targetColumn, true);
        
        return r2.union(r1);
    }

    public void valueChanged(ListSelectionEvent e) 
    {
        repaint();
    }

    class BlockWrapper
    {
        int address, indent;
        CodeBlock block;

        BlockWrapper(int address, int indent, CodeBlock block)
        {
            this.block = block;
            this.indent = indent;
            this.address = address;
        }
    }

    public void recalculateBlockPositions()
    {
        Vector buffer = new Vector();
        Vector stack = new Vector();

        int len = getModel().getRowCount();
        for (int i=0; i<len; i++)
        {
            CodeBlock cb = getCodeBlockForRow(i);
            if (cb == null)
                continue;

            for (int j=stack.size()-1; j>=0; j--)
            {
                BlockWrapper bw = (BlockWrapper) stack.elementAt(j);
                if (i >= bw.block.getX86Length() + bw.address)
                    stack.removeElementAt(j);
            }

            int indent = 0;
            if (stack.size() > 0)
                indent = ((BlockWrapper) stack.elementAt(stack.size()-1)).indent+1;

            BlockWrapper w = new BlockWrapper(i, indent, cb);
            buffer.add(w);
            stack.add(w);
        }

        allBlocks = new BlockWrapper[buffer.size()];
        buffer.toArray(allBlocks);
    }

    protected CodeBlock getCodeBlockForRow(int row)
    {
        try
        {
            return (CodeBlock) getModel().getValueAt(convertRowIndexToModel(row), targetColumn);
        }
        catch (Exception e)
        {
            return null;
        }
    }

    class OverlayLineFormat
    {
        Color c;
        String text;
        int tabPosition;

        OverlayLineFormat(String txt, int pos, Color c)
        {
            text = txt;
            tabPosition = pos;
            this.c = c;
        }
    }

    private static Hashtable<String, String> reflectedNameCache = new Hashtable<String, String>();
    static
    {
        try
        {
            Class cls = MicrocodeSet.class;
            Field[] flds = cls.getDeclaredFields();
            int count = 0;
            for (int i=0; i<flds.length; i++)
            {
                Field f = flds[i];
                int mods = f.getModifiers();
                if (!Modifier.isPublic(mods) || !Modifier.isStatic(mods) || !Modifier.isFinal(mods))
                    continue;
                if (f.getType() != int.class)
                    continue;

                int value = f.getInt(null);
                count++;
                reflectedNameCache.put(String.valueOf(value), f.getName());
            }

            System.out.println("Scanned "+cls+" and cached "+count+" field names");
        }
        catch (Throwable t) {}
    }

    class UCodeOverlayLineFormat extends OverlayLineFormat
    {
        int hasImmediate;

        UCodeOverlayLineFormat(String txt, int pos)
        {
            super(txt, pos, Color.black);
            hasImmediate = 0;

            int colon = txt.indexOf(":");
            if (colon >= 0)
            {
                String ucode = txt.substring(colon+1).trim();
                String readable = reflectedNameCache.get(ucode);
                if (readable != null)
                {
                    c = Color.blue;
                    text = txt.substring(0, colon)+" "+readable;

		    if (readable.indexOf("_IB") > 0)
			hasImmediate = 2;
		    else if (readable.indexOf("_IW") > 0)
			hasImmediate = 4;
		    else if (readable.indexOf("_ID") > 0)
			hasImmediate = 8;
                }
            }
        }
    }

    private OverlayLineFormat[] formatMicrocodeStream(FirstStageCodeBlock src)
    {
        Vector uCodes = new Vector();
        StringTokenizer tokens = new StringTokenizer(src.getDisplayString(), "\n");
        while (tokens.hasMoreTokens())
        {
            String l = tokens.nextToken();
            try
            {
                uCodes.add(Integer.valueOf(l));
            }
            catch (Exception e) {}
        }
        
        Color c1 = new Color(0, 120, 30);
        Color c2 = Color.blue;
        Color c3 = new Color(150, 20, 20);
        Color c4 = Color.red;
        
        int tab1 = 5;
        int tab2 = 35;
        int tab3 = 65;

        Vector<OverlayLineFormat> flines = new Vector<OverlayLineFormat>();
        for (int i=0; i<uCodes.size(); )
        {
            int operation = 0;
            try
            {
                operation = ((Integer) uCodes.elementAt(i)).intValue();
                flines.add(new OverlayLineFormat(operationNames[operation], tab1, c1));
                i++;
            }
            catch (Exception e)
            {
                flines.add(new OverlayLineFormat(e+": "+uCodes.elementAt(i).toString(), tab1, c4));
                break;
            }
                
            int operandCount = src.operandCount(operation);
            for (int j=0; j<operandCount; j++)
            {
                int operand = 0;
                try
                {
                    operand = ((Integer) uCodes.elementAt(i)).intValue();
                    flines.add(new OverlayLineFormat(operandNames[operand], tab2, c2));
                    i++;
                }
                catch (Exception e)
                {
                    flines.add(new OverlayLineFormat(e+" "+uCodes.elementAt(i).toString(), tab1, c4));
                    break;
                }
                    
                int immCount = src.immediateCount(operand);
                for (int k=0; k<immCount; k++)
                {
                    try
                    {
                        int imm = ((Integer) uCodes.elementAt(i)).intValue();
                        String val = Integer.toHexString(imm).toUpperCase()+"  ("+imm+")";
                        flines.add(new OverlayLineFormat(val, tab3, c3));
                        i++;
                    }
                    catch (Exception e)
                    {
                        flines.add(new OverlayLineFormat(e+" "+uCodes.elementAt(i).toString(), tab1, c4));
                        break;
                    }
                }
            } 
        } 

        OverlayLineFormat[] result = new OverlayLineFormat[flines.size()];
        flines.toArray(result);
        return result;
    }

    private OverlayLineFormat[] basicLineFormat(CodeBlock block)
    {
        Vector lines = new Vector();
        String details = block.getDisplayString();
        StringTokenizer tokens = new StringTokenizer(details, "\n");

        int immediateFollows = 0;
        while (tokens.hasMoreTokens())
        {
            String l = tokens.nextToken();
            
            if (immediateFollows > 0)
            {
		String[] imm = l.split("\\s", 2);		
		StringBuffer buf = new StringBuffer(Integer.toHexString(Integer.parseInt(imm[1])));
		while (buf.length() != immediateFollows) {
		    if (buf.length() > immediateFollows)
			buf.deleteCharAt(0);
		    else
			buf.insert(0, "0");
		}
                lines.add(new OverlayLineFormat(imm[0] + " 0x" + buf.toString(), 20, Color.red));
                immediateFollows = 0;
            }
            else
            {
                UCodeOverlayLineFormat lf = new UCodeOverlayLineFormat(l, 10);
                immediateFollows = lf.hasImmediate;
                lines.add(lf);
            }
        }

        OverlayLineFormat[] result = new OverlayLineFormat[lines.size()];
        lines.toArray(result);
        return result;
    }

    private OverlayLineFormat[] exceptionLineFormat(Class cls, Exception e)
    {
        StackTraceElement[] trace = e.getStackTrace();
        OverlayLineFormat[] result = new OverlayLineFormat[trace.length+2];

        result[0] = new OverlayLineFormat("Exception formatting CodeBlock", 10, Color.red);
        result[1] = new OverlayLineFormat("Source: "+cls, 20, Color.red);
        
        Color rr = new Color(255, 50, 50);
        for (int i=0; i<trace.length; i++)
            result[i+2] = new OverlayLineFormat(trace[i].toString(), 30, rr);

        return result;
    }

    private void paintCodeBlockOverlay(Graphics g, int row)
    {
        CodeBlock cb = getCodeBlockForRow(row);
        if ((cb == null) || (cb instanceof CodeBlockTrigger))
            return;
        
        Rectangle r1 = getCellRect(row, targetColumn, true);
        int rowHeight = getRowHeight();
        int h1 = r1.height;
        if (showX86Lengths)
            h1 = cb.getX86Length() * rowHeight;
        Dimension s = getSize();

        OverlayLineFormat[] lines = null;
        try
        {
            if (cb instanceof FirstStageCodeBlock)
                lines = formatMicrocodeStream((FirstStageCodeBlock) cb);
            else
                lines = basicLineFormat(cb);
        }
        catch (Exception e)
        {
            lines = exceptionLineFormat(cb.getClass(), e);
        }
        
        int popupHeight = lines.length * rowHeight;
        int w1 = 330;
        int w2 = 80;
        
        int x1 = r1.x;
        int y1 = r1.y;
        int x2 = r1.x + 20;
        int y2 = r1.y;
        int x3 = x1 + w2;
        int y3 = Math.min(y1 + rowHeight, s.height - popupHeight - 1);
        int x4 = x3;
        int y4 = Math.min(y1 + rowHeight + popupHeight, s.height-1);
        int x5 = x1 + 20;
        int y5 = r1.y + h1 - 1;
        int x6 = x1;
        int y6 = r1.y + h1 - 1;
        
        Polygon p = new Polygon(new int[]{x1,x2,x3,x4,x5,x6}, new int[]{y1,y2,y3,y4,y5,y6}, 6);
        g.setColor(new Color(150, 150, 255, 60));
        g.fillPolygon(p);
        g.setColor(Color.blue);
        g.drawPolygon(p);
        
        g.setColor(new Color(200, 200, 200));
        g.fillRect(x3, y3, w1, y4 - y3);
        g.setColor(Color.blue);
        g.drawRect(x3, y3, w1, y4 - y3);
        g.setFont(f);
        g.setColor(Color.black);
        
        int ypos = 14;
        for (int i=0; i<lines.length; i++)
        {
            OverlayLineFormat l = lines[i];
            g.setColor(l.c);
            g.drawString(l.text, x3 + l.tabPosition, y3 + ypos);
            ypos += rowHeight;
        }
    }

    private void drawBlocks(Graphics g)
    {
        int rowHeight = getRowHeight();
        Rectangle tgtRect = getCellRect(0, targetColumn, true);
        Rectangle r1 = new Rectangle();
        Rectangle clip = g.getClipRect();
        int width = 10;
        int gap = 3;

        g.setColor(new Color(0, 255, 100, 120));

        for (int i=0; i<allBlocks.length; i++)
        {
            BlockWrapper bw = allBlocks[i];
            int x1 = 5+tgtRect.x + bw.indent * (width + gap);
            int y1 = bw.address * rowHeight + rowHeight/2;
            int w1 = width;
            int h1 = (bw.block.getX86Length() - 1) * rowHeight;
            
            r1.setRect(x1, y1, w1, h1);

            if (!clip.intersects(r1))
                continue;
            g.fillRect(x1, y1, w1, h1);
        }
    }

    public int getHeadRowForBlockRect(Point pt)
    {
        int rowHeight = getRowHeight();
        Rectangle tgtRect = getCellRect(0, targetColumn, true);
        Rectangle r1 = new Rectangle();
        int width = 10;
        int gap = 3;

        for (int i=0; i<allBlocks.length; i++)
        {
            BlockWrapper bw = allBlocks[i];
            int x1 = 5+tgtRect.x + bw.indent * (width + gap);
            int y1 = bw.address * rowHeight + rowHeight/2;
            int w1 = width;
            int h1 = (bw.block.getX86Length() - 1) * rowHeight;
            
            r1.setRect(x1, y1, w1, h1);
            if (r1.contains(pt))
                return bw.address;
        }

        return -1;
    }
    
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        drawBlocks(g);

        int selectedRow = getSelectedRow();
        if (selectedRow >= 0)
            paintCodeBlockOverlay(g, selectedRow);
    }
}
