/* Useful imports */

import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;
import edu.neu.ccs.codec.*;
import edu.neu.ccs.console.*;
import edu.neu.ccs.filter.*;
import edu.neu.ccs.jpf.*;
import edu.neu.ccs.parser.*;
import edu.neu.ccs.pedagogy.*;
import edu.neu.ccs.quick.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.font.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.border.*;
import java.io.*;
import java.util.*;
import java.math.*;
import java.beans.*;
import java.lang.reflect.*;
import java.net.URL;
import java.util.regex.*;
import java.text.ParseException;


public class SudokuBlock
    extends BufferedPanel
{
    // Static definitions //
    
    /** The normal background color. */
    private static final Color backcolor = Colors.bisque;
    
    /** The background color when the block is frozen */
    private static final Color frozencolor = Colors.lightblue;
    
    /** The border color for a selected block. */
    private static final Color bordercolor = Colors.red;
    
    
    /** The size of the box for the digits or hints. */
    private static final int boxsize = 48;
    
    /** The stroke thickness for the selection rectangle. */
    private static final int gapsize = 2;

    /** The size of the full cell. */
    private static final int cellsize = boxsize + 4 * gapsize;
    
    
    /** The large font size for the regular digits. */
    private static final int largefontsize = boxsize;
    
    /** The large font for the regular digits. */
    private static final Font largefont =
        new Font("serif", Font.BOLD, largefontsize);
    
    
    /** The 9 regular digits as paintables. */
    private static TextPaintable[] digits = 
        new TextPaintable[10];
    
    /** Initialization of the 9 regular digits as paintables. */
    static {
        // find the height of a typical digit in the large font
        TextPaintable p =
            new TextPaintable
                ("0", largefont, Colors.black, TextBounds.TIGHT,
                 TextAnchor.CENTER_BASELINE, 0, 0);
        
        Rectangle2D textbounds = p.getBounds2D();
        
        int digitheight = (int) textbounds.getHeight();
        
        // find the location of the center baseline for all digits
        int x = cellsize / 2;
        int y = (cellsize + digitheight) / 2;
        
        // create digits 1-9 
        // omit digit 0 since it will never be used
        for (int i = 1; i <= 9; i++) {
            digits[i] =
                new TextPaintable
                    (i + "", largefont, Colors.black, TextBounds.TIGHT,
                     TextAnchor.CENTER_BASELINE, x, y);
        }
    }
    
    
    /** The small font size for the hint digits. */
    private static final int smallfontsize = (boxsize / 3);
    
    /** The small font for the hint digits. */
    private static final Font smallfont =
        new Font("serif", Font.BOLD, smallfontsize);
    
    
    /** The 9 hint digits as paintables. */
    private static TextPaintable[] hintdigits = 
        new TextPaintable[10];
    
    /** Initialization of the 9 hint digits as paintables. */
    static {
        // find the height of a typical digit in the small font
        TextPaintable p =
            new TextPaintable
                ("0", smallfont, Colors.black, TextBounds.TIGHT,
                 TextAnchor.CENTER_BASELINE, 0, 0);
        
        Rectangle2D textbounds = p.getBounds2D();
        
        int digitheight = (int) textbounds.getHeight();
        
        int[] xpos = {
            cellsize / 5,
            cellsize / 2,
            cellsize - (cellsize / 5)
        };
        
        int[] ypos = {
            3 * gapsize + digitheight,
            (cellsize + digitheight) / 2,
            cellsize - (3 * gapsize)
        };
        
        // create digits 1-9 
        // omit digit 0 since it will never be used
        for (int i = 1; i <= 9; i++) {
            int row = (i - 1) / 3;
            int col = (i - 1) % 3;
            
            int x = xpos[col];
            int y = ypos[row];
            
            hintdigits[i] =
                new TextPaintable
                    (i + "", smallfont, Colors.black, TextBounds.TIGHT,
                     TextAnchor.CENTER_BASELINE, x, y);
        }
    }
    
    
    /** The rectangle for selection of a cell. */
    private static Rectangle2D border = null;
    
    /** The stroke for selection of a cell. */
    private static Stroke stroke = null;
    
    
    /** Initialization for selection objects. */
    static {
        int a = gapsize / 2;
        int b = gapsize;
        
        border = new Rectangle2D.Double(a, a, cellsize - b, cellsize - b);
        stroke = new BasicStroke(b);
    }
    
    
    // Member definitions //
    
    /** The Sudoku GUI object. */
    private SudokuBase  GUI   = null;
    
    /** The Sudoku model object. */
    private SudokuModel model = null;
    
    /** The Sudoku table object. */
    private SudokuTable table = null;
    
    
    /** The row of this block in the table. */
    private int row = 0;
    
    /** The col of this block in the table. */
    private int col = 0;
    
    /** The cell position. */
    private CellPosition position = null;
    
    
    /** Is this block the selected block for user key input? */
    private boolean selected = false;
    
    /** Is this block active or frozen? */
    private boolean active = true;
    
   
    /** The inner panel that can do key input listening. */
    private DisplayPanel inner = null;
    
    /** The key listener adapter. */
    private KeyActionAdapter keyadapter = null;
    
    /** The mouse listener adapter. */
    private MouseActionAdapter mouseadapter = null;
    
    
    /** The key action object for key pressed events. */
    private KeyAction keyPressed = new KeyAction() {
        public void keyActionPerformed(KeyEvent kevt) {
            handleKeyPressed(kevt);
        }
    };
    
    /** The mouse action object for mouse pressed events. */
    private MouseAction mousePressed = new MouseAction() {
        public void mouseActionPerformed(MouseEvent mevt) {
            handleMousePressed(mevt);
        }
    };
    
    
    
    // Constructors //
    
    /** The default constructor useful only for simple tests. */
    public SudokuBlock() {
        super(cellsize, cellsize, backcolor);
        
        initializeSudokuBlock();
    }
    
    
    /**
     * The constructor that provides the table and the
     * row and col position of this block in the table.
     */
    public SudokuBlock
        (SudokuTable table, int row, int col)
    {
        super(cellsize, cellsize, backcolor);
        
        this.table = table;
        
        if (table != null)
            model = table.getSudokuModel();
        
        if (model != null)
            GUI = model.getSoduku();
        
        this.row = row;
        this.col = col;
        
        initializeSudokuBlock();
    }
    
    
    // Methods //
    
    /** Complete the initialization for this block. */
    private void initializeSudokuBlock() {
        position = new CellPosition(row, col);
        
        inner = getInnerPanel();
        
        keyadapter = getKeyActionAdapter();
        initializeKeys();
        
        mouseadapter = getMouseActionAdapter();
        initializeMouse();
    }
    
    
    /**
     * The key action method for key pressed events.
     * 
     * Keys 1 to 9 enter that digit in the cell and
     * inform the model and the GUI.
     * 
     * Keys 0 or spacebar clear the cell and inform
     * the model and the GUI.
     * 
     * Arrow keys shift the selection from this block
     * to the nearest block in that direction that is
     * active.  If no such block exists then nothing
     * is changed.
     */
    private void handleKeyPressed(KeyEvent kevt) {
        if (! (selected && active))
            return;
        
        int code  = kevt.getKeyCode();
        
        // first check for digits or space bar
        
        int digit = -1;
        
        switch(code) {
            case KeyEvent.VK_1:
            case KeyEvent.VK_2:
            case KeyEvent.VK_3:
            case KeyEvent.VK_4:
            case KeyEvent.VK_5:
            case KeyEvent.VK_6:
            case KeyEvent.VK_7:
            case KeyEvent.VK_8:
            case KeyEvent.VK_9:
            {
                digit = code - KeyEvent.VK_0;
                break;
            }
            
            case KeyEvent.VK_0:
            case KeyEvent.VK_SPACE:
                digit = 0;
                break;
                
            default:
                break;
        }
        
        if (digit >= 0) {
            paintDigit(digit);
            
            if (model != null)
                model.setCellData(row, col, digit);
            
            if (GUI != null)
                GUI.updateHints();
            
            return;
        }
        
        // now check for arrow keys
        
        if (table == null)
            return;
        
        int delta_r = 0;
        int delta_c = 0;
        
        switch(code) {
            case KeyEvent.VK_LEFT:
                delta_c = -1;
                break;
                
            case KeyEvent.VK_RIGHT:
                delta_c =  1;
                break;
                
            case KeyEvent.VK_UP:
                delta_r = -1;
                break;
                
            case KeyEvent.VK_DOWN:
                delta_r =  1;
                break;
                
            default:
                return;
        }

        int r = row + delta_r;
        int c = col + delta_c;
        
        while ((1 <= r) && (r <= 9) && (1 <= c) && (c <= 9)) {
            SudokuBlock block = table.getSudokuBlock(r, c);
            
            if (block.isActive()) {
                this.unselect();
                block.select();
                return;
            }
            
            r += delta_r;
            c += delta_c;
        }
    }
    
    
    /** Initialize key listening for the inner panel. */
    private void initializeKeys() {
        // enable the inner panel to gain the focus of attention
        inner.setFocusable(true);
        
        // attach the pressed action object to the key adapter
        keyadapter.addKeyPressedAction(keyPressed);
    }
    
    
    /** Set or reset key listening to the inner panel. */
    public void resetKeyFocus() {
        inner.requestFocusInWindow();
    }
    
    
    /** Initialize mouse listening. */
    private void initializeMouse() {
        mouseadapter.addMousePressedAction(mousePressed);
    }
    
    
    /**
     * The mouse action method for mouse pressed events.
     * 
     * If this block is active then a mouse press makes
     * this block the selected block.
     */
    private void handleMousePressed(MouseEvent mevt) {
        if (! active)
            return;
        
        if (table != null)
            table.unselectBlocks();
        
        select();
    }
    
    
    /** Returns the row of the block. */
    public int getRow() {
        return row;
    }
    
    
    /** Returns the col of the block. */
    public int getCol() {
        return col;
    }
    
    
    /**
     * Returns the cell position of the block.
     * 
     * The cell position returned should not be modified.
     */
    public CellPosition getCellPosition() {
        return position;
    }
    
    
    /** Select or unselect this block for key input. */
    public void setSelect(boolean select) {
        if (select)
            resetKeyFocus();
        
        if (selected != select) {
            selected = select;
            repaintBlock();
        }
    }
    
    
    /** Select this block for key input. */
    public void select() {
        setSelect(true);
    }
    
    
    /** Unselect this block for key input. */
    public void unselect() {
        setSelect(false);
    }
    
    
    /** Is this block selected for key input? */
    public boolean isSelected() {
        return selected;
    }
    
    
    /** Set this block to active or frozen. */
    public void setActive(boolean active) {
        if (this.active == active)
            return;
        
        this.active = active;
        
        Color color = active
            ? backcolor
            : frozencolor;
        
        this.setBufferBackground(color);
        
        if (!active) {
            unselect();
        }
        
        repaintBlock();
    }
    
    
    /** Set this block to frozen. */
    public void freeze() {
        setActive(false);
    }
    
    
    /** Set this block to active. */
    public void thaw() {
        setActive(true);
    }
    
    
    /** Is this block active? */
    public boolean isActive() {
        return active;
    }
    
    /** Is this block frozen? */
    public boolean isFrozen() {
        return !active;
    }
    
    
    /** Clear the block of regular digits and hint digits. */
    public void clearBlock() {
        clearSequence();
        repaintBlock();
    }
    
    
    /**
     * Paint a regular digit between 1 and 9.
     * 
     * If the given digit is not between 1 and 9 then clear
     * the block.
     */
    public void paintDigit(int digit) {
        clearSequence();
        
        if ((digit >= 1) && (digit <= 9))
            addPaintable(digits[digit]);
        
        repaintBlock();
    }
    
    
    /**
     * Paint the small hint digits corresponding to the data
     * items in the given hint that are true.
     */
    public void paintHint(SudokuHint hint) {
        clearSequence();
        
        if (hint != null)
            for (int digit = 1; digit <= 9; digit++)
                if (hint.getHint(digit))
                    addPaintable(hintdigits[digit]);

        repaintBlock();
    }
    
    
    /**
     * After refreshing the selection state border,
     * repaint the block.
     */
    public void repaintBlock() {
        paintSelectState();
        repaint();
    }
    
    
    /** Refresh the selection state border. */
    private void paintSelectState() {
        clearPanel();
        
        if (selected) {
            Graphics2D g = getBufferGraphics();
            
            g.setPaint(bordercolor);
            g.setStroke(stroke);
            g.draw(border);
        }
    }
    
    
    /** Trivial test code. */
    public static void main(String[] args) {
        SudokuBlock block1 = new SudokuBlock();
        SudokuBlock block2 = new SudokuBlock();
        
        SudokuHint hint = new SudokuHint();
        
        hint.setAll(true);
        
        block1.paintHint(hint);
        block2.paintHint(hint);
        
        Object[] stuff = { block1, block2 };
        
        TablePanel panel = new TablePanel(stuff, HORIZONTAL, 5, 5, CENTER);
        
        panel.setBackground(Colors.black);
        
        panel.frame("Two Sudoku Blocks");
    }
    
}
