package jrummikub.view.impl;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import javax.swing.ImageIcon;
import javax.swing.SwingUtilities;

import jrummikub.model.Position;
import jrummikub.model.Stone;
import jrummikub.model.StoneColor;
import jrummikub.model.StoneSet;
import jrummikub.util.Event1;
import jrummikub.util.IListener1;
import jrummikub.util.Pair;
import jrummikub.view.IStoneCollectionPanel;
import jrummikub.view.ITablePanel;
import jrummikub.view.impl.StonePainter.StoneState;

/**
 * The implementation of the table
 */
@SuppressWarnings("serial")
class TablePanel extends AbstractStonePanel implements ITablePanel {
	private final static ImageIcon BACKGROUND = new ImageIcon(
			HandPanel.class.getResource("/jrummikub/resource/felt.png"));
	private final static ImageIcon DARK_BACKGROUND = new ImageIcon(
			HandPanel.class.getResource("/jrummikub/resource/dark_felt.png"));
	private final static ImageIcon BRIGHT_BACKGROUND = new ImageIcon(
			HandPanel.class.getResource("/jrummikub/resource/bright_felt.png"));

	private final static double MIN_VISIBLE_WIDTH = 10;
	private final static double MIN_VISIBLE_HEIGHT = 5;
	private final static double HORIZONTAL_MARGIN = 1;
	private final static double VERTICAL_MARGIN = 0.7f;
	private final static double CONNECTOR_WIDTH = 0.25f;
	private final double COLLECTION_RATIO = 0.12f;
	private final int COLLECTION_GAP = 5;

	private StoneCollectionPanel stoneCollection;

	private Iterable<Pair<StoneSet, Position>> stoneSets = Collections.emptySet();
	private List<Pair<StoneSet, Position>> pauseStoneSets;

	private Collection<StoneSet> invalidStoneSets = Collections.emptyList();

	private Collection<Stone> selectedStones = Collections.emptyList();

	private Event1<StoneSet> leftConnectorClickEvent = new Event1<StoneSet>();
	private Event1<StoneSet> rightConnectorClickEvent = new Event1<StoneSet>();

	private StoneSet leftHoveredConnector;
	private StoneSet rightHoveredConnector;

	private boolean pauseMode = false;

	@Override
	public Event1<StoneSet> getLeftConnectorClickEvent() {
		return leftConnectorClickEvent;
	}

	@Override
	public Event1<StoneSet> getRightConnectorClickEvent() {
		return rightConnectorClickEvent;
	}

	@Override
	public void setStoneSets(Iterable<Pair<StoneSet, Position>> stoneSets) {
		List<Pair<Stone, Position>> stones = new ArrayList<Pair<Stone, Position>>();

		for (Pair<StoneSet, Position> entry : stoneSets) {
			double x = entry.getSecond().getX(), y = entry.getSecond().getY();

			for (Stone stone : entry.getFirst()) {
				stones.add(new Pair<Stone, Position>(stone, new Position(x, y)));
				x++;
			}
		}

		setStones(stones);
		this.stoneSets = stoneSets;

		setScale();

		repaint();
	}

	@Override
	public IStoneCollectionPanel getStoneCollectionPanel() {
		return stoneCollection;
	}

	/**
	 * Sets the currently selected stones
	 * 
	 * @param stones
	 *          the selected stones
	 */
	void setSelectedStones(Collection<Stone> stones) {
		selectedStones = stones;
		stoneCollection.setSelectedStones(stones);
		repaint();
	}

	void setInvalidStoneSets(Collection<StoneSet> sets) {
		invalidStoneSets = sets;
		repaint();
	}

	void createPauseStoneSets() {
		pauseStoneSets = new ArrayList<Pair<StoneSet, Position>>();

		Stone stoneP = new Stone(-'P', StoneColor.BLACK);
		Stone stonea = new Stone(-'a', StoneColor.ORANGE);
		Stone stoneu = new Stone(-'u', StoneColor.BLUE);
		Stone stones = new Stone(-'s', StoneColor.RED);
		Stone stonee = new Stone(-'e', StoneColor.BLACK);

		pauseStoneSets.add(new Pair<StoneSet, Position>(new StoneSet(Arrays.asList(
				stoneP, stonea, stoneu, stones, stonee)), new Position(-2.5, 0)));
	}

	/**
	 * Creates a new Table instance
	 */
	TablePanel() {
		setLayout(null);

		createPauseStoneSets();

		stoneCollection = new StoneCollectionPanel();
		stoneCollection.getOtherClickEvent().add(new IListener1<Point>() {

			@Override
			public void handle(Point p) {
				Point p2 = SwingUtilities.convertPoint(stoneCollection, p,
						TablePanel.this);
				// theres nothing below here
				clickAt(p2, 1, false, false, true);
			}
		});
		add(stoneCollection);

		addComponentListener(new ComponentAdapter() {
			@Override
			public void componentResized(ComponentEvent e) {
				rescale();
			}
		});
	}

	private Rectangle2D calculateTableExtent() {
		double minx = -MIN_VISIBLE_WIDTH / 2, maxx = MIN_VISIBLE_WIDTH / 2;
		double miny = -MIN_VISIBLE_HEIGHT / 2, maxy = MIN_VISIBLE_HEIGHT / 2;

		for (Pair<StoneSet, Position> entry : (pauseMode ? pauseStoneSets
				: stoneSets)) {
			Position p = entry.getSecond();
			StoneSet stoneSet = entry.getFirst();

			if (p.getX() < minx)
				minx = p.getX();

			if (p.getY() < miny)
				miny = p.getY();

			if (p.getX() + stoneSet.getSize() > maxx)
				maxx = p.getX() + stoneSet.getSize();

			if (p.getY() + 1 > maxy)
				maxy = p.getY() + 1;
		}

		return new Rectangle2D.Double(minx - HORIZONTAL_MARGIN, miny
				- VERTICAL_MARGIN, maxx - minx + 2 * HORIZONTAL_MARGIN, maxy - miny + 2
				* VERTICAL_MARGIN);
	}

	private void rescale() {
		Insets insets = getInsets();
		int x = insets.left, y = insets.top, width = getWidth() - insets.left
				- insets.right, height = getHeight() - insets.top - insets.bottom;

		int collectionHeight = (int) (height * COLLECTION_RATIO);
		stoneCollection
				.setBounds(x, y + height - collectionHeight - COLLECTION_GAP, width,
						collectionHeight);

		setScale();

		repaint();
	}

	private void setScale() {
		Insets insets = getInsets();
		int width = getWidth() - insets.left - insets.right, height = getHeight()
				- insets.top - insets.bottom;
		Rectangle2D extent = calculateTableExtent();

		double widthScale = width / (double) extent.getWidth()
				* StonePainter.WIDTH_SCALE;
		double heightScale = height * (1 - COLLECTION_RATIO)
				/ (double) extent.getHeight() * StonePainter.HEIGHT_SCALE;

		getStonePainter().setScale(Math.min(widthScale, heightScale));
	}

	@Override
	protected boolean handleOtherClickEvent(Position pos) {
		for (Pair<StoneSet, Position> entry : stoneSets) {
			Position p = entry.getSecond();
			StoneSet stoneSet = entry.getFirst();
			double x = p.getX(), y = p.getY();

			// left connector
			Rectangle2D rect = new Rectangle2D.Double(x - CONNECTOR_WIDTH, y,
					CONNECTOR_WIDTH, 1);
			if (rect.contains(pos.getX(), pos.getY())) {
				leftConnectorClickEvent.emit(stoneSet);
				return true;
			}

			// right connector
			rect = new Rectangle2D.Double(x + stoneSet.getSize(), y, CONNECTOR_WIDTH,
					1);
			if (rect.contains(pos.getX(), pos.getY())) {
				rightConnectorClickEvent.emit(stoneSet);
				return true;
			}
		}
		return false;
	}

	@Override
	protected void handleOtherMoveEvent(Position pos) {
		StoneSet oldLeftHoveredConnector = leftHoveredConnector;
		StoneSet oldRightHoveredConnector = rightHoveredConnector;

		leftHoveredConnector = null;
		rightHoveredConnector = null;

		for (Pair<StoneSet, Position> entry : stoneSets) {
			Position p = entry.getSecond();
			StoneSet stoneSet = entry.getFirst();
			double x = p.getX(), y = p.getY();

			// left connector
			Rectangle2D rect = new Rectangle2D.Double(x - CONNECTOR_WIDTH, y,
					CONNECTOR_WIDTH, 1);
			if (rect.contains(pos.getX(), pos.getY())) {
				leftHoveredConnector = stoneSet;
				break;
			}

			// right connector
			rect = new Rectangle2D.Double(x + stoneSet.getSize(), y, CONNECTOR_WIDTH,
					1);
			if (rect.contains(pos.getX(), pos.getY())) {
				rightHoveredConnector = stoneSet;
				break;
			}
		}

		if (leftHoveredConnector != oldLeftHoveredConnector
				|| rightHoveredConnector != oldRightHoveredConnector) {
			repaint();
		}
	}

	@Override
	protected Pair<Integer, Integer> getTranslation() {
		Insets insets = getInsets();
		int width = getWidth() - insets.left - insets.right, height = getHeight()
				- insets.top - insets.bottom;
		int stoneWidth = getStonePainter().getStoneWidth(), stoneHeight = getStonePainter()
				.getStoneHeight();
		Rectangle2D extent = calculateTableExtent();

		return new Pair<Integer, Integer>((int) (width / 2 - extent.getCenterX()
				* stoneWidth),
				(int) ((height * (1 - COLLECTION_RATIO)) / 2 - extent.getCenterY()
						* stoneHeight));
	}

	private void paintStoneSet(Graphics2D g, StoneSet stoneSet, Position pos,
			Area connectorArea, Area hoveredConnectorArea) {
		double x = pos.getX();
		int width = getStonePainter().getStoneWidth(), height = getStonePainter()
				.getStoneHeight();
		Area leftConnectorArea = (stoneSet == leftHoveredConnector ? hoveredConnectorArea
				: connectorArea);
		Area rightConnectorArea = (stoneSet == rightHoveredConnector ? hoveredConnectorArea
				: connectorArea);

		// Left connector

		leftConnectorArea.add(new Area(new Rectangle2D.Double(Math.round(x * width)
				- (int) width * CONNECTOR_WIDTH + 1, Math.round(pos.getY() * height),
				(int) (width * CONNECTOR_WIDTH), height)));

		for (Stone stone : stoneSet) {
			StoneState state = invalidStoneSets.contains(stoneSet) ? StoneState.INVALID
					: selectedStones.contains(stone) ? StoneState.SELECTED
							: StoneState.NORMAL;

			getStonePainter().paintStone(g, stone, new Position(x, pos.getY()),
					state, stone == getHoveredStone());
			x++;
		}

		// Right connector
		rightConnectorArea.add(new Area(new Rectangle2D.Double(Math
				.round(x * width), Math.round(pos.getY() * height),
				(int) (width * CONNECTOR_WIDTH), height)));
	}

	@Override
	protected void paintComponent(Graphics g1) {
		Graphics2D g = (Graphics2D) g1;

		for (int x = 0; x < getWidth(); x += BACKGROUND.getIconWidth()) {
			for (int y = 0; y < getHeight(); y += BACKGROUND.getIconHeight()) {
				BACKGROUND.paintIcon(this, g, x, y);
			}
		}

		AffineTransform oldTransform = g.getTransform();
		Shape oldClip = g.getClip();

		Pair<Integer, Integer> translation = getTranslation();
		g.translate(translation.getFirst(), translation.getSecond());

		Area connectorArea = new Area();
		Area hoveredConnectorArea = new Area();

		for (Pair<StoneSet, Position> entry : (pauseMode ? pauseStoneSets
				: stoneSets)) {
			paintStoneSet(g, entry.getFirst(), entry.getSecond(), connectorArea,
					hoveredConnectorArea);
		}

		
		if (pauseMode) {
			g.setTransform(oldTransform);
			return;
		}

		g.setClip(connectorArea);
		g.setTransform(oldTransform);

		for (int x = 0; x < getWidth(); x += DARK_BACKGROUND.getIconWidth()) {
			for (int y = 0; y < getHeight(); y += DARK_BACKGROUND.getIconHeight()) {
				DARK_BACKGROUND.paintIcon(this, g, x, y);
			}
		}

		g.setClip(oldClip);

		g.translate(translation.getFirst(), translation.getSecond());
		g.setClip(hoveredConnectorArea);
		g.setTransform(oldTransform);

		for (int x = 0; x < getWidth(); x += BRIGHT_BACKGROUND.getIconWidth()) {
			for (int y = 0; y < getHeight(); y += BRIGHT_BACKGROUND.getIconHeight()) {
				BRIGHT_BACKGROUND.paintIcon(this, g, x, y);
			}
		}

		g.setClip(oldClip);
	}

	void enablePauseMode(boolean enable) {
		stoneCollection.setHidden(enable);

		pauseMode = enable;
		setScale();
		repaint();
	}
}
