/*******************************************************************************
 * Copyright (c) 2006, 2010, 2012 Wind River Systems, Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Ted R Williams (Wind River Systems, Inc.) - initial implementation
 *     Randy Rohrbach (Wind River Systems, Inc.) - Copied and modified to create the floating point plugin
 *******************************************************************************/

package org.eclipse.cdt.debug.ui.memory.floatingpoint;

import java.math.BigInteger;

import org.eclipse.debug.core.DebugException;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;

public class FPDataPane extends FPAbstractPane
{
    public FPDataPane(Rendering parent)
    {
        super(parent);
    }

    // Returns the representation of the given memory bytes as a scientific notation string

    protected String bytesToSciNotation(FPMemoryByte[] bytes)
    {
        return fRendering.sciNotationString(bytes, fRendering.getFPDataType(), fRendering.isDisplayLittleEndian());
    }

    // Cell editing:  Accumulate text entry characters, replacing or inserting them at the current cursor position

    @Override
    protected void editCell(BigInteger cellAddress, int subCellPosition, char character)
    {
        try
        {
            // Switch to cell edit mode if not we're not currently in the middle of an edit

            if (!fRendering.isEditingCell())
            {
                // Calculate the memory address from the cell address

                BigInteger vpStart  = fRendering.getViewportStartAddress();
                BigInteger colChars = BigInteger.valueOf(fRendering.getCharsPerColumn());
                BigInteger dtBytes  = BigInteger.valueOf(fRendering.getFPDataType().getByteLength());
                BigInteger memoryAddress = vpStart.add(((cellAddress.subtract(vpStart)).divide(colChars)).multiply(dtBytes));

                if (!fRendering.insertMode())
                {
                    // Overstrike/overwrite mode: Enter cell-edit mode; start with the current cell contents.

                    fRendering.startCellEditing(cellAddress, memoryAddress, bytesToSciNotation(fRendering.getBytes(cellAddress, fRendering.getFPDataType().getByteLength())));
                }
                else
                {
                    // Insert/Replace mode:  Clear the current cell contents; start
                    // with a blank string.  Move the caret to the start of the cell.

                    fRendering.startCellEditing(cellAddress, memoryAddress, FPutilities.fillString(fRendering.getCharsPerColumn(), ' '));

                    subCellPosition = 0;
                    Point cellCoordinates = getCellLocation(cellAddress);
                    positionCaret(cellCoordinates.x, cellCoordinates.y);
                    fCaret.setVisible(true);
                }
            }

            // Validate the current string:  Only one decimal point and exponent character ('e' or 'E') is allowed.  Number
            // signs may be present up to two times - once for the number and once for the exponent, and may occur at the
            // very beginning of a number or immediately after the exponent character.  If an entry violates a rule, do not
            // echo the character (implicitly, through replacement and re-paint) and do not advance the cursor.

            String cellString = fRendering.getEditBuffer().toString().toLowerCase();

            // Check to see if a second decimal point or exponent was entered

            if ((character == '.' && FPutilities.countMatches(cellString, ".") > 0 && cellString.indexOf('.') != subCellPosition) || //$NON-NLS-1$
                (character == 'e' && FPutilities.countMatches(cellString, "e") > 0 && cellString.indexOf('e') != subCellPosition)) //$NON-NLS-1$
                return;

            // Check to see if more than two number signs have been entered

            if ((character == '+' && FPutilities.countMatches(cellString, "+") > 1) ||  //$NON-NLS-1$
                (character == '-' && FPutilities.countMatches(cellString, "-") > 1)) //$NON-NLS-1$
                return;

            // We've passed the previous checks:  Make the character substitution, if possible.
            // Advance the cursor as long as we're inside the column.  Re-paint the view.

            if (subCellPosition < fRendering.getEditBuffer().length())
                fRendering.getEditBuffer().setCharAt(subCellPosition, character);

            if (subCellPosition < fRendering.getCharsPerColumn())
                advanceCursor();

            redraw();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    // Returns cell width, in pixels

    @Override
    protected int getCellWidth()
    {
        return getCellCharacterCount() * getCellCharacterWidth() + (fRendering.getCellPadding() * 2);
    }

    @Override
    protected int getCellCharacterCount()
    {
        return fRendering.getCharsPerColumn();
    }

    @Override
    public Point computeSize(int wHint, int hHint)
    {
        return new Point(fRendering.getColumnCount() * getCellWidth() + fRendering.getRenderSpacing(), 100);
    }

    private BigInteger getCellAddressAt(int x, int y) throws DebugException
    {
        BigInteger address = fRendering.getViewportStartAddress();

        int col = x / getCellWidth();
        int row = y / getCellHeight();

        if (col >= fRendering.getColumnCount())
            return null;

        address = address.add(BigInteger.valueOf(row * fRendering.getColumnCount() * fRendering.getFPDataType().getByteLength()));
        address = address.add(BigInteger.valueOf(col * fRendering.getFPDataType().getByteLength()));

        return address;
    }

    // Return a Point representing the cell address

    @Override
    protected Point getCellLocation(BigInteger cellAddress)
    {
        try
        {
            BigInteger address = fRendering.getViewportStartAddress();

            int cellOffset = cellAddress.subtract(address).intValue();
            cellOffset *= fRendering.getAddressableSize();

            int row = cellOffset / (fRendering.getColumnCount() * fRendering.getFPDataType().getByteLength());
            cellOffset -= row * fRendering.getColumnCount()     * fRendering.getFPDataType().getByteLength();
            int col = cellOffset / fRendering.getFPDataType().getByteLength();

            int x = col * getCellWidth()  + fRendering.getCellPadding();
            int y = row * getCellHeight() + fRendering.getCellPadding();

            return new Point(x, y);
        }
        catch (Exception e)
        {
            fRendering.logError(FPRenderingMessages.getString("FPRendering.FAILURE_DETERMINE_CELL_LOCATION"), e); //$NON-NLS-1$
            return null;
        }
    }

    @Override
    protected void positionCaret(int x, int y)
    {
        try
        {
            BigInteger cellAddress = getCellAddressAt(x, y);

            if (cellAddress != null)
            {
                Point cellPosition = getCellLocation(cellAddress);
                int offset = x - cellPosition.x;
                int subCellCharacterPosition = offset / getCellCharacterWidth();

                if (subCellCharacterPosition == this.getCellCharacterCount())
                {
                    cellAddress = cellAddress.add(BigInteger.valueOf(fRendering.getFPDataType().getByteLength()));
                    subCellCharacterPosition = 0;
                    cellPosition = getCellLocation(cellAddress);
                }

                fCaret.setLocation(cellPosition.x + subCellCharacterPosition * getCellCharacterWidth(), cellPosition.y);

                this.fCaretAddress = cellAddress;
                this.fSubCellCaretPosition = subCellCharacterPosition;
                setCaretAddress(fCaretAddress);
            }
        }
        catch (Exception e)
        {
            fRendering.logError(FPRenderingMessages.getString("FPRendering.FAILURE_POSITION_CURSOR"), e); //$NON-NLS-1$
        }
    }

    @Override
    protected BigInteger getViewportAddress(int col, int row) throws DebugException
    {
        BigInteger address = fRendering.getViewportStartAddress();
        address = address.add(BigInteger.valueOf((row * fRendering.getColumnCount() + col) * fRendering.getFPDataType().getByteLength()));

        return address;
    }

    @Override
    protected void paint(PaintEvent pe)
    {
        super.paint(pe);
        // Allow subclasses to override this method to do their own painting
        doPaintData(pe);
    }

    // Display text in the cell


    protected void doPaintData(PaintEvent pe)
    {
        GC gc = pe.gc;
        gc.setFont(fRendering.getFont());

        int cellHeight   = getCellHeight();
        int cellWidth    = getCellWidth();
        int boundsHeight = this.getBounds().height;
        int columns      = fRendering.getColumnCount();

        int cellX = 0;
        int cellY = 0;

        String displayString = FPutilities.fillString(fRendering.getCharsPerColumn(), '.');

        try
        {
            BigInteger vpStart = fRendering.getViewportStartAddress();
            BigInteger cellStartAddr = vpStart;
            BigInteger memoryAddr = vpStart;
            BigInteger cellEndAddr;

            for(int row = 0; row < boundsHeight / cellHeight; row++)
            {
                for (int column = 0; column < columns; column++)
                {
                    // Set alternating colors for every other column and display the text
                    // FIXME: There is duplicate code in applyCustomColor() in this class

                    if (isOdd(column))
                        gc.setForeground(fRendering.getFPRendering().getColorText());
                    else
                        gc.setForeground(fRendering.getFPRendering().getColorTextAlternate());

                    // Calculate the cell starting address and X/Y coordinates

                    cellStartAddr = vpStart.add(BigInteger.valueOf((row * fRendering.getColumnCount() + column) * fRendering.getFPDataType().getByteLength()));
                    cellEndAddr = cellStartAddr.add(BigInteger.valueOf(fRendering.getFPDataType().getByteLength()).subtract(BigInteger.ONE));

                    cellX = (cellWidth  * column) + fRendering.getCellPadding();
                    cellY = (cellHeight * row   ) + fRendering.getCellPadding();

                    // Cell editing:  If we're in edit mode, change the cell color and then set the
                    // edit buffer as the string to display.  Otherwise, just use the memory contents.

                    if (fRendering.isEditingCell() && cellStartAddr.equals(fRendering.getCellEditAddress()))
                    {
                        gc.setForeground(fRendering.getFPRendering().getColorEdit());
                        FPMemoryByte[] memoryBytes = fRendering.getBytes(cellStartAddr, fRendering.getFPDataType().getByteLength());
                        for (FPMemoryByte memoryByte : memoryBytes)
                            memoryByte.setEdited(true);
                        applyCustomColor(gc, memoryBytes, column);
                        displayString = fRendering.getEditBuffer().toString();
                    }
                    else
                        displayString = bytesToSciNotation(fRendering.getBytes(memoryAddr, fRendering.getFPDataType().getByteLength()));

                    // Cell selection

                    if (fRendering.getSelection().isSelected(cellStartAddr))
                    {
                        gc.setBackground(fRendering.getFPRendering().getColorSelection());
                        gc.fillRectangle(cellX, row * cellHeight, cellWidth, cellHeight);
                        gc.setForeground(fRendering.getFPRendering().getColorBackground());
                    }
                    else
                    {
                        gc.setBackground(fRendering.getFPRendering().getColorBackground());
                        gc.fillRectangle(cellX, row * cellHeight, cellWidth, cellHeight);
                        // Allow subclasses to override this method to do their own coloring
                        applyCustomColor(gc, fRendering.getBytes(cellStartAddr, fRendering.getFPDataType().getByteLength()), column);
                    }

                    gc.drawText(displayString, cellX, cellY);

                    // Move the caret if appropriate

                    if (fCaretEnabled)
                    {
                        if(cellStartAddr.compareTo(fCaretAddress) <= 0 && cellEndAddr.compareTo(fCaretAddress) >= 0)
                        {
                            int x = cellWidth  * column + fRendering.getCellPadding() + fSubCellCaretPosition * this.getCellCharacterWidth();
                            int y = cellHeight * row    + fRendering.getCellPadding();
                            fCaret.setLocation(x, y);
                        }
                    }

                    // For debugging

                    if (fRendering.isDebug())
                        gc.drawRectangle(cellX, cellY, cellWidth, cellHeight);

                    // Increment the memory address by the length of the data type

                    memoryAddr = memoryAddr.add(BigInteger.valueOf(fRendering.getFPDataType().getByteLength()));
                }
            }
        }
        catch(Exception e)
        {
            fRendering.logError(FPRenderingMessages.getString("FPRendering.FAILURE_PAINT"), e); //$NON-NLS-1$
            e.printStackTrace();
        }
    }

    // Allow subclasses to override this method to do their own coloring

    protected void applyCustomColor(GC gc, FPMemoryByte bytes[], int col)
    {
        // Check to see if any byte has been changed and whether we're actually in edit mode

        boolean anyByteEditing = false;

        for (int n = 0; n < bytes.length && !anyByteEditing; n++)
        	if (bytes[n].isEdited()) anyByteEditing = true;

        anyByteEditing = anyByteEditing && fRendering.isEditingCell();

        // Even/odd column coloring

        if (isOdd(col))
            gc.setForeground(fRendering.getFPRendering().getColorText());
        else
            gc.setForeground(fRendering.getFPRendering().getColorTextAlternate());

        // Background

        gc.setBackground(fRendering.getFPRendering().getColorBackground());

        if (anyByteEditing)
        {
            gc.setForeground(fRendering.getFPRendering().getColorEdit());
        }
        else
        {
            boolean isColored = false;

            for (int index = 0; index < fRendering.getHistoryDepth() && !isColored; index++)
            {
                // TODO consider adding finer granularity?

                for (int n = 0; n < bytes.length; n++)
                {
                    if (bytes[n].isChanged(index))
                    {
                        if (index == 0)
                            gc.setForeground(fRendering.getFPRendering().getColorsChanged()[index]);
                        else
                            gc.setBackground(fRendering.getFPRendering().getColorsChanged()[index]);

                        isColored = true;
                        break;
                    }
                }
            }
        }
    }

    // Draw a box around the specified cell

    public void highCellBox(BigInteger memoryAddress)
    {
//        if (fRendering.isDebug())
//            gc.drawRectangle(cellX, cellY, cellWidth, cellHeight);
    }

    // Clear the box around the specified cell

    public void clearCellBox(BigInteger memoryAddress)
    {

    }
}
