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

public class MemoryViewPanel extends JPanel
{
    private JTable memoryBlockTable;
    private AddressSpace memory;
    private int startAddress, tableSize;
    private MemoryTableModel model;
    private boolean rowReversed;

    private static Font f = new Font("Monospaced", Font.PLAIN, 12);

    public MemoryViewPanel()
    {
        this(0);
    }

    public MemoryViewPanel(int initialAddress)
    {
        super(new BorderLayout());
        this.memory = null;
        startAddress = initialAddress;
        tableSize = 0;
        rowReversed = false;

        model = new MemoryTableModel();
        memoryBlockTable = new JTable(model);
        memoryBlockTable.setRowHeight(18);
        model.setupColumnWidths(memoryBlockTable);
        memoryBlockTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
        
        ValidatingTextField hex = new ValidatingTextField("0123456789abcdefABCDEF", '0', 8);
        hex.setHorizontalAlignment(JLabel.LEFT);
        hex.setFont(f);

        memoryBlockTable.setDefaultRenderer(Object.class, new CellRenderer());
        memoryBlockTable.setDefaultEditor(Object.class, new DefaultCellEditor(hex));

        add("Center", new JScrollPane(memoryBlockTable));
    }

    public void setCellRenderer(TableCellRenderer cr)
    {
        memoryBlockTable.setDefaultRenderer(Object.class, cr);
        memoryBlockTable.repaint();
    }

    public void refresh()
    {
        refresh(memory);
    }

    public void refresh(AddressSpace memory)
    {
        this.memory = memory;
        if (memory == null)
            tableSize = 0;
        else
            tableSize = AddressSpace.BLOCK_SIZE;

        model.fireTableDataChanged();
    }

    protected Object formatMemoryDisplay(int address)
    {
        StringBuffer buf = new StringBuffer();
        for (int i=0; i<4; i++)
        {
            int val = memory.getByte(address+i);
            buf.append(zeroPadHex(0xFF & val, 2));
        }

        return buf;
    }

    protected Object formatAsciiDisplay(int address)
    {
        StringBuffer buffer = new StringBuffer();
        for (int i=0; i<16; i++)
        {
            byte b = (byte) memory.getByte(address + i);
            buffer.append(getASCII(b));
        }
        
        return buffer;
    }

    class MemoryTableModel extends BasicTableModel
    {
        MemoryTableModel()
        {
            super(new String[]{"Absolute Address", "0-3", "4-7", "8-B", "C-F", "ASCII"}, new int[]{100, 80, 80, 80, 80, 140});
        }

        public int getRowCount()
        {
            return tableSize / 16;
        }

        public Object getValueAt(int row, int column)
        {
            if (memory == null)
                return null;

            if (rowReversed)
                row = getRowCount() - row - 1;

            int index = row * 16 + startAddress;
            switch (column)
            {
            case 0:
                return zeroPadHex(index, 8);
            case 5:
                return formatAsciiDisplay(index);
            default:
                int address = index + (column - 1)*4;
                return formatMemoryDisplay(address);
            }            
        }

        public boolean isCellEditable(int rowIndex, int columnIndex)
        {
            return (columnIndex > 0) && (columnIndex < 5);
        }

        public void setValueAt(Object obj, int row, int column)
        {
            if (memory == null)
                return;
            if (rowReversed)
                row = getRowCount() - row - 1;

            int address = 16 * row + (column - 1)*4 + startAddress;
            try
            {
                StringBuffer buf = new StringBuffer(obj.toString());
                while (buf.length() < 8)
                    buf.append("0");

                long value = Long.parseLong(buf.toString(), 16);
                for (int i=0; i<4; i++)
                    memory.setByte(address+i, (byte) (value >> (24 - 8*i)));
            }
            catch (Exception e) {}

            JPC.getInstance().refresh();
        }
    }
    
    public static class CellRenderer extends DefaultTableCellRenderer
    {
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
        {
            super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
            setFont(f);
            setHorizontalAlignment(JLabel.LEFT);

            if (column == 0)
            {
                setBackground(Color.pink);
                setForeground(Color.blue);
            }
            else if (column == 5)
            {
                setBackground(Color.white);
                setForeground(Color.blue);
            }
            else
            {
                setBackground(Color.white);
                setForeground(Color.black);
            }

            return this;
        }
    }

    public void setViewLimits(AddressSpace memory, int address, int limit)
    {
        startAddress = address;
        tableSize = Math.max(limit, 16);
        refresh(memory);
    }

    public void setCurrentAddress(AddressSpace memory, int address)
    {
        startAddress = address;
        refresh(memory);
    }

    public void setTableSize(AddressSpace memory, int size)
    {
        tableSize = Math.max(size, 16);
        refresh(memory);
    }

    public void setRowReversed(boolean value)
    {
        rowReversed = value;
        refresh();
    }

    public static String zeroPadHex(int value, int size)
    {
        StringBuffer result = new StringBuffer(Integer.toHexString(value).toUpperCase());
        while (result.length() < size)
            result.insert(0, '0');
        
        return result.toString();
    }

    public static char getASCII(byte b)
    {
        if ((b >= 32) && (b < 127))
            return (char) b;
        return ' ';
    }
}
