package jrummikub.view.impl;

import java.awt.Color;
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.Collection;
import java.util.Collections;
import java.util.List;

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

import jrummikub.model.Position;
import jrummikub.model.Stone;
import jrummikub.model.StoneSet;
import jrummikub.util.Event1;
import jrummikub.util.IListener1;
import jrummikub.util.Pair;
import jrummikub.view.IStoneCollectionPanel;
import jrummikub.view.ITablePanel;

/**
 * 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 float MIN_VISIBLE_WIDTH = 15;
	private final static float MIN_VISIBLE_HEIGHT = 7.5f;
	private final static float HORIZONTAL_MARGIN = 1;
	private final static float VERTICAL_MARGIN = 0.7f;
	private final static float CONNECTOR_WIDTH = 0.25f;
	private final float COLLECTION_RATIO = 0.12f;
	private final int COLLECTION_GAP = 5;

	private JLabel leftPlayerLabel, topPlayerLabel, rightPlayerLabel;
	private StoneCollectionPanel stoneCollection;

	private Iterable<Pair<StoneSet, Position>> stoneSets = Collections.emptySet();
	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;

	@Override
	public void setLeftPlayerName(String playerName) {
		leftPlayerLabel.setText(playerName);
	}

	@Override
	public void setTopPlayerName(String playerName) {
		topPlayerLabel.setText(playerName);
	}

	@Override
	public void setRightPlayerName(String playerName) {
		rightPlayerLabel.setText(playerName);
	}

	@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) {
			float 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();
	}

	private void createLabels() {
		leftPlayerLabel = new JLabel();
		leftPlayerLabel.setForeground(Color.WHITE);
		leftPlayerLabel.setHorizontalAlignment(JLabel.LEFT);
		leftPlayerLabel.setHorizontalTextPosition(JLabel.LEFT);
		add(leftPlayerLabel);

		topPlayerLabel = new JLabel();
		topPlayerLabel.setHorizontalAlignment(JLabel.CENTER);
		topPlayerLabel.setHorizontalTextPosition(JLabel.CENTER);
		topPlayerLabel.setVerticalAlignment(JLabel.TOP);
		topPlayerLabel.setVerticalTextPosition(JLabel.TOP);
		topPlayerLabel.setForeground(Color.WHITE);
		add(topPlayerLabel);

		rightPlayerLabel = new JLabel();
		rightPlayerLabel.setForeground(Color.WHITE);
		rightPlayerLabel.setHorizontalAlignment(JLabel.RIGHT);
		rightPlayerLabel.setHorizontalTextPosition(JLabel.RIGHT);
		add(rightPlayerLabel);
	}

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

		createLabels();

		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() {
		float minx = -MIN_VISIBLE_WIDTH / 2, maxx = MIN_VISIBLE_WIDTH / 2;
		float miny = -MIN_VISIBLE_HEIGHT / 2, maxy = MIN_VISIBLE_HEIGHT / 2;

		for (Pair<StoneSet, Position> entry : 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.size() > maxx)
				maxx = p.getX() + stoneSet.size();

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

		return new Rectangle2D.Float(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;

		leftPlayerLabel.setBounds(x, y, width, height);
		topPlayerLabel.setBounds(x, y, width, height);
		rightPlayerLabel.setBounds(x, y, width, height);

		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();

		float widthScale = width / (float) extent.getWidth()
				* StonePainter.WIDTH_SCALE;
		float heightScale = height * (1 - COLLECTION_RATIO)
				/ (float) 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();
			float x = p.getX(), y = p.getY();

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

			// right connector
			rect = new Rectangle2D.Float(x + stoneSet.size(), 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();
			float x = p.getX(), y = p.getY();

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

			// right connector
			rect = new Rectangle2D.Float(x + stoneSet.size(), 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) {
		float 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.Float(Math.round(x * width)
				- (int) width * CONNECTOR_WIDTH + 1, Math.round(pos.getY() * height),
				(int) (width * CONNECTOR_WIDTH), height)));

		for (Stone stone : stoneSet) {
			getStonePainter().paintStone(g, stone, new Position(x, pos.getY()),
					selectedStones.contains(stone), stone == getHoveredStone());
			x++;
		}

		// Right connector
		rightConnectorArea.add(new Area(new Rectangle2D.Float(
				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 : stoneSets) {
			paintStoneSet(g, entry.getFirst(), entry.getSecond(), connectorArea,
					hoveredConnectorArea);
		}

		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);

		if (leftHoveredConnector == null && rightHoveredConnector == null) {
			return; // We're done here...
		}

		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);
	}
}
