package org.ggp.base.apps.kiosk.templates;

import java.awt.Graphics;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.ggp.base.util.statemachine.MachineState;


/**
 * GameCanvas_FancyGrid expands on the GameCanvas_SimpleGrid model, with
 * additional features based on situations that commonly occur when writing
 * game visualizations. These features make stronger assumptions about the
 * game being visualized, and so may not be appropriate for all games.
 * These assumptions, in addition to the GameCanvas_SimpleGrid assumptions,
 * are as follows:
 *
 *      1) Each cell of the grid can be drawn independently,
 *         using the following layers:
 *
 *              a) Background layer, based only on coordinates.
 *              b) Content layer, based only on game propositions.
 *              c) Foreground layer, based only on coordinates.
 *              d) Selection layer, based only on the coordinates
 *                     and the currently selected move.
 *
 *      2) The game propositions required to render the content layer
 *         of a given cell can be determined from the current state of
 *         the game and the cell's coordinates. As a result, they will
 *         not change unless the game state changes.
 *
 *      3) Each cell has a certain number of moves associated with it.
 *         These moves can be determined from the current state of the
 *         game and the cell's coordinates. As a result, they will not
 *         change unless the game state changes.
 *
 *         NOTE: Multiple cells can share the same moves.
 *
 * Given these assumptions, GameCanvas_FancyGrid handles a lot of the work
 * associated with selecting moves, rendering the grid, and caching useful
 * parts of the game state. The player will be able to select their move by
 * clicking on a cell: if there are multiple moves associated with the cell,
 * they can click on the cell multiple times to cycle through them.
 *
 * To reflect the above assumptions, GameCanvas_FancyGrid overrides the
 * andleClickOnCell() and clearMoveSelection() methods, and instead provides
 * the following abstract methods:
 *
 *      getFactsAboutCell(x,y)           Get all facts associated with a cell.
 *      getLegalMovesForCell(x,y)        Get all legal moves associated with a cell.
 *
 *      renderCellBackground(g,x,y)      Draw the background for a cell.
 *      renderCellContent(g,fact)        Draw the content for a cell, given its fact.
 *      renderCellContent(g,facts)       Draw the content for a cell, given its facts.
 *      renderCellForeground(g,x,y)      Draw the foreground for a cell.
 *
 *      renderMoveSelectionForCell(g,x,y,move)      Draw the move selection for a cell.
 *
 * One common situation involves each cell having exactly one fact associated
 * with it, often of the form "(cell 1 1 x)". In this situation, it's more convenient
 * to have that single fact extracted and passed to renderCellContent() as a String,
 * rather than as a {@code Set<String>} containing one element. If you want that automatic
 * extraction done, override the renderCellContent() method that takes a String as its
 * second parameter. Otherwise, if your getFactsAboutCell() method is expected to return
 * multiple facts about the same cell, override the renderCellContent() method that takes
 * a {@code Set<String>} as its second parameter.
 *
 * Another common situation involves having a game where you want to automatically
 * display the grid, drawing black boxes around grid cells and always highlighting
 * the selected grid cell with a green border. This grid visualization will be done
 * by default. If you want to disable it, override the "useGridVisualization" function
 * so that it returns false.
 *
 * @author Sam Schreiber
 */
public abstract class GameCanvas_FancyGrid extends GameCanvas_SimpleGrid {
    private static final long serialVersionUID = 1L;

    protected abstract Set<String> getFactsAboutCell(int xCell, int yCell);
    protected abstract Set<String> getLegalMovesForCell(int xCell, int yCell);

    protected void renderCellBackground(Graphics g, int xCell, int yCell) {};
    protected void renderCellForeground(Graphics g, int xCell, int yCell) {};
    protected void renderMoveSelectionForCell(Graphics g, int xCell, int yCell, String theMove) {};

    protected void renderCellContent(Graphics g, String theFact) {};
    protected void renderCellContent(Graphics g, Set<String> theFacts){
        if(!theFacts.isEmpty()) {
            if(theFacts.size() > 1) {
                System.err.println("More than one fact for a cell? Unexpected!");
            }

            String theFact = theFacts.iterator().next();
            renderCellContent(g, theFact);
        }
    }

    protected boolean useGridVisualization() { return true; }

    protected final boolean isSelectedCell(int xCell, int yCell) {
        return (yCell == selectedRow && xCell == selectedColumn);
    }

    private int selectedRow = -1;
    private int selectedColumn = -1;
    private String currentSelectedMove;
    private Iterator<String> possibleSelectedMoves = null;
    @Override
    protected final void handleClickOnCell(int xCell, int yCell, int xWithin, int yWithin) {
        if(selectedRow != yCell || selectedColumn != xCell || !possibleSelectedMoves.hasNext()) {
            SortedSet<String> theMoves = new TreeSet<String>(getLegalMovesForCell(xCell, yCell));
            if(theMoves.isEmpty())
                return;
            possibleSelectedMoves = theMoves.iterator();
        }

        selectedRow = yCell;
        selectedColumn = xCell;

        currentSelectedMove = possibleSelectedMoves.next();
        submitWorkingMove(stringToMove(currentSelectedMove));
    }

    // Cache all of the facts about cells that we compute, since they should not
    // change unless the game state changes.
    private Map<Integer, Set<String>> factsCache = new HashMap<Integer, Set<String>>();
    protected Set<String> getCachedFactsAboutCell(int xCell, int yCell) {
        int cellHash = xCell*getGridHeight()*2 + yCell;
        Set<String> cachedFacts = factsCache.get(cellHash);
        if(cachedFacts != null)
            return cachedFacts;

        Set<String> realFacts = getFactsAboutCell(xCell, yCell);
        factsCache.put(cellHash, realFacts);
        return realFacts;
    }

    // When the game state changes, clear our cache of known facts.
    @Override
    public void updateGameState(MachineState gameState) {
        factsCache.clear();
        super.updateGameState(gameState);
    }

    @Override
    protected final void renderCell(Graphics g, int xCell, int yCell) {
        renderCellBackground(g, xCell, yCell);
        renderCellContent(g, getCachedFactsAboutCell(xCell, yCell));
        if(useGridVisualization()) CommonGraphics.drawCellBorder(g);
        renderCellForeground(g, xCell, yCell);
        if(!currentSelectedMove.isEmpty()) {
            renderMoveSelectionForCell(g, xCell, yCell, currentSelectedMove);
            if(useGridVisualization() && isSelectedCell(xCell, yCell))
                CommonGraphics.drawSelectionBox(g);
        }
    }

    @Override
    public final void clearMoveSelection() {
        submitWorkingMove(null);

        possibleSelectedMoves = null;
        currentSelectedMove = "";
        selectedColumn = -1;
        selectedRow = -1;

        repaint();
    }
}