package org.limewire.ui.swing.properties;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.event.InputEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.HashMap;
import java.util.Map;

import org.jdesktop.swingx.JXPanel;
import org.jdesktop.swingx.painter.AbstractPainter;
import org.jdesktop.swingx.painter.Painter;

/**
 * Lightweight component for displaying a coloured grid.  
 * 
 * <p> At this moment specialised for displaying piece data, however could be extended. 
 */
public class PiecesGrid extends JXPanel {
    
    private int rows = -1;
    private int columns = -1;
    private Paint[] gridFillPaint = null;
    private final Map<Integer,Paint> borderPaintMap = new HashMap<Integer,Paint>();
    
    public PiecesGrid() {
        init();
    }
    
    public PiecesGrid(int r, int c) {
        resizeGrid(r, c);
    
        init();
    }
    
    private void init() {
        setBackgroundPainter(new GridPainter());
        
        // Just for fun
        addMouseWheelListener(new MouseWheelListener() {
            @Override
            public void mouseWheelMoved(MouseWheelEvent e) {
                if ((e.getModifiers() & InputEvent.CTRL_MASK) == 0) {
                    return;
                }
                
                int newRows = rows;
                int newColumns = columns;
                if (e.getUnitsToScroll() < 0) {
                    if (columns > 4) {
                        newColumns = columns - 1;
                    }
                    if (rows > 4) {
                        newRows = rows - 1;
                    }
                } else if (e.getUnitsToScroll() > 0) {
                    if (columns < 200) {
                        newColumns = columns + 1;
                    }
                    if (rows < 200 ) {
                        newRows = rows + 1;
                    }
                }
                resizeGrid(newRows, newColumns);
            }
        });
    }
    
    void resizeGrid(int rows, int columns) {
        this.rows = rows;
        this.columns = columns;
        gridFillPaint = new Paint[rows*columns];
    }
    
    public void clear() {
        clearFillPaints();
        clearBorderPaints();
    }
    
    public void clearFillPaints() {
        gridFillPaint = new Paint[rows*columns];
    }
    
    public void clearBorderPaints() {
        borderPaintMap.clear();
    }
    
    boolean hasGrid() {
        return gridFillPaint != null;
    }
    
    public int getRows() {
        return rows;
    }
    
    public int getColumns() {
        return columns;
    }
    
    public int getCellCount() {
        return gridFillPaint.length;
    }
    
    public void setCellFillPaint(int cell, Paint paint) {
        gridFillPaint[cell] = paint;
    }
    
    public void setCellBorderPaint(int cell, Paint paint) {
        borderPaintMap.put(cell, paint);
    }
    
    public Paint getCellFillPaint(int cell) {
        return gridFillPaint[cell];
    }
    
    public Paint getCellBorderPaint(int cell) {
        return borderPaintMap.get(cell);
    }
    
    @Override
    public void repaint() {
        Painter painter = getBackgroundPainter();
        if (painter instanceof AbstractPainter) {
            ((AbstractPainter)painter).clearCache();
        }
        super.repaint();
    }
    
    /**
     * Hack to aid with alignment of components along the outer edge above 
     *  or below this component.  No way to easily avoid.  Only relevant with
     *  centred x-alignment at this moment.
     */
    public int getInnerMargin() {
        Painter painter = this.getBackgroundPainter();
        if (painter instanceof GridPainter) {
            ((GridPainter)painter).marginUpdated = false;
            return ((GridPainter)painter).marginX;
        }
        return 0;
    }
    
    public boolean isMarginUpdated() {
        Painter painter = this.getBackgroundPainter();
        if (painter instanceof GridPainter) {
            return ((GridPainter)painter).marginUpdated;
        }
        return false;
    }
        
    private static class GridPainter extends AbstractPainter<PiecesGrid> {
        
        /**
         * Variable used for hack to correct the margins of the components under the grid
         *  despite loss of table width due to need for rounding.
         */
        protected int marginX = 0;

        /**
         * Variable used for hack to correct the margins of the components under the grid
         *  despite loss of table width due to need for rounding.
         */
        protected boolean marginUpdated = false;
        
        GridPainter() {
            setCacheable(true);
            setAntialiasing(true);
        }
        
        @Override
        protected void doPaint(Graphics2D g, PiecesGrid grid, int width, int height) {
            if (!grid.hasGrid()) {
                g.setPaint(Color.BLACK);
                g.drawRect(0,0, width-1, height-1);
                return;
            }
            
            int columns = grid.getColumns();
            int rows = grid.getRows();
            
            int cellWidth = (width-2) / columns;
            int cellHeight = (height-2) / rows;

            int actualTableWidth = cellWidth*columns;
            int actualTableHeight = cellHeight*rows;
            
            int alignOffsetX = Math.round(grid.getAlignmentX() * (width-actualTableWidth));
            int alignOffsetY = Math.round(grid.getAlignmentY() * (height-actualTableHeight));

            if (marginX != alignOffsetX) {
                marginX = alignOffsetX;
                marginUpdated = true;
            }
            
            int cellRow = 0;
            for ( int i=0 ; i<grid.getCellCount() ; i++ ) {
                Paint cellPaint = grid.getCellFillPaint(i);
                int cellColumn = i % columns;
                if (i != 0 && i % columns == 0) {
                    cellRow++;
                }
                
                g.setPaint(cellPaint);
                
                g.fillRect(cellColumn*cellWidth+1+alignOffsetX,
                        cellRow*cellHeight+1+alignOffsetY,
                        cellWidth, cellHeight);
                
                
                Paint borderPaint = grid.getCellBorderPaint(i);
                
                if (borderPaint != null) {
                    g.setPaint(borderPaint);
                
                    g.drawRect(cellColumn*cellWidth+1+alignOffsetX,
                        cellRow*cellHeight+1+alignOffsetY,
                        cellWidth-2, cellHeight-2);
                }
                
                g.setPaint(Color.BLACK);
            }
            
            g.setPaint(Color.BLACK);
            g.drawRect(0+alignOffsetX, 0+alignOffsetY, actualTableWidth, actualTableHeight);
            
            for ( int i=1 ; i<columns ; i++ ) {
                g.drawLine(i*cellWidth+alignOffsetX, 0+alignOffsetY, 
                        i*cellWidth+alignOffsetX, actualTableHeight+alignOffsetY);
            }
            
            for ( int i=1 ; i<rows ; i++ ) {
                g.drawLine(0+alignOffsetX, i*cellHeight+alignOffsetY
                        , actualTableWidth+alignOffsetX, i*cellHeight+alignOffsetY);
            }
        }
    }

}
