import java.net.URL;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;

public class KlondikeTable extends Application {
	private static final double GAP = 10;
	private static final double TABLE_WIDTH = 600;
	private static final double TABLE_HEIGHT = 380;
	private static final double TABLEAU_WIDTH =
	    MiniGame.COLUMNS * CardView.WIDTH + (MiniGame.COLUMNS - 1) * GAP;

	private MiniGame game_ = new MiniGame(42);
	private CardView stock_;
	private CardView waste_;
	private StackPane[] columns_ = new StackPane[MiniGame.COLUMNS];
	private Rectangle[] outlines_ = new Rectangle[MiniGame.COLUMNS];
	private Rectangle[] rings_ = new Rectangle[MiniGame.COLUMNS];
	private Rectangle dropzone_;
	private Label message_;

	@Override
	public void start ( Stage stage ) {
		// stock: a face-down card you click
		stock_ = new CardView("",false);
		stock_.setFaceUp(false);
		stock_.setCursor(Cursor.HAND);
		stock_.setOnMouseClicked(e -> drawFromStock());

		// waste: the drag source
		waste_ = new CardView("",false);
		setUpDragSource();
		HBox topRow = new HBox(GAP,stock_,waste_);

		// tableau: the drop targets
		HBox tableau = new HBox(GAP);
		for ( int i = 0 ; i < MiniGame.COLUMNS ; i++ ) {
			columns_[i] = makeColumn(i);
			tableau.getChildren().add(columns_[i]);
		}
		setUpDropTargets();

		// drop zone indicator over the tableau row
		dropzone_ = new Rectangle(TABLEAU_WIDTH,CardView.HEIGHT,
		                          Color.rgb(255,255,255,0.25));
		dropzone_.setVisible(false);
		dropzone_.setMouseTransparent(true); // comment out to show the bug
		StackPane tableauLayer = new StackPane(tableau,dropzone_);
		tableauLayer.setAlignment(Pos.TOP_LEFT);

		message_ = new Label();
		message_.setTextFill(Color.WHITE);
		CheckBox showDropZone = new CheckBox("Show drop zone");
		showDropZone.setTextFill(Color.WHITE);
		showDropZone
		    .setOnAction(e -> dropzone_.setVisible(showDropZone.isSelected()));

		VBox piles = new VBox(2 * GAP,topRow,tableauLayer,message_,showDropZone);
		piles.setPadding(new Insets(20));

		// layers, bottom to top
		StackPane table = new StackPane(makeBackground(),piles);
		table.setAlignment(Pos.TOP_LEFT);
		table.setPrefSize(TABLE_WIDTH,TABLE_HEIGHT);

		refreshTable();
		stage.setScene(new Scene(table));
		stage.setTitle("Klondike table");
		stage.setResizable(false);
		stage.show();
	}

	/**
	 * The felt image if felt.png is next to this class; otherwise plain green.
	 */
	private Node makeBackground () {
		URL url = getClass().getResource("/images/felt.png");
		if ( url == null ) {
			return new Rectangle(TABLE_WIDTH,TABLE_HEIGHT,Color.DARKGREEN);
		}
		ImageView felt = new ImageView(new Image(url.toExternalForm()));
		felt.setFitWidth(TABLE_WIDTH);
		felt.setFitHeight(TABLE_HEIGHT);
		return felt;
	}

	/**
	 * An empty slot outline plus a highlight ring; the card itself is added by
	 * refreshTable().
	 */
	private StackPane makeColumn ( int i ) {
		outlines_[i] =
		    new Rectangle(CardView.WIDTH,CardView.HEIGHT,Color.rgb(0,0,0,0.15));
		outlines_[i].setArcWidth(10);
		outlines_[i].setArcHeight(10);
		outlines_[i].setStrokeType(StrokeType.INSIDE);
		outlines_[i].setStroke(Color.rgb(255,255,255,0.5));

		rings_[i] = new Rectangle(CardView.WIDTH,CardView.HEIGHT);
		rings_[i].setFill(null);
		rings_[i].setArcWidth(10);
		rings_[i].setArcHeight(10);
		rings_[i].setStrokeType(StrokeType.INSIDE);
		rings_[i].setStroke(Color.DODGERBLUE);
		rings_[i].setStrokeWidth(4);
		rings_[i].setVisible(false);
		rings_[i].setMouseTransparent(true); // decoration

		StackPane column = new StackPane();
		column.setMinSize(CardView.WIDTH,CardView.HEIGHT);
		column.setPrefSize(CardView.WIDTH,CardView.HEIGHT);
		column.setMaxSize(CardView.WIDTH,CardView.HEIGHT);
		return column;
	}

	private void setUpDragSource () {
		waste_.setOnDragDetected(e -> {
			if ( game_.getWasteTop() == null ) {
				return;
			}
			Dragboard db = waste_.startDragAndDrop(TransferMode.MOVE);
			ClipboardContent content = new ClipboardContent();
			content.putString("waste");
			db.setContent(content);

			SnapshotParameters params = new SnapshotParameters();
		 	params.setFill(Color.TRANSPARENT);
			db.setDragView(waste_.snapshot(params,null),CardView.WIDTH / 2,
			               CardView.HEIGHT / 2);

			waste_.setOpacity(0.4);
			e.consume();
		});

		waste_.setOnDragDone(e -> {
			waste_.setOpacity(1.0);
			e.consume();
		});
	}

	private void setUpDropTargets () {
		for ( int i = 0 ; i < MiniGame.COLUMNS ; i++ ) {
			final int col = i;
			StackPane target = columns_[i];

			target.setOnDragOver(e -> {
				if ( e.getGestureSource() != target && e.getDragboard().hasString() ) {
					e.acceptTransferModes(TransferMode.MOVE);
				}
				e.consume();
			});
			target.setOnDragEntered(e -> {
				setColumnHighlight(col,true);
				e.consume();
			});
			target.setOnDragExited(e -> {
				setColumnHighlight(col,false);
				e.consume();
			});
			target.setOnDragDropped(e -> {
				boolean moved = game_.moveWasteTo(col);
				if ( moved ) {
					message_.setText("");
					refreshTable();
				} else {
					message_.setText(game_.getLastError());
				}
				e.setDropCompleted(moved);
				e.consume();
			});
		}
	}

	private void setColumnHighlight ( int col, boolean on ) {
		rings_[col].setVisible(on);
	}

	private void drawFromStock () {
		game_.drawFromStock();
		message_.setText("");
		refreshTable();
	}

	/** Redraws every pile from the game's state. */
	private void refreshTable () {
		if ( game_.getStockSize() > 0 ) {
			stock_.setOpacity(1.0);
		} else {
			stock_.setOpacity(0.3); // still clickable: recycles the waste
		}

		Card top = game_.getWasteTop();
		if ( top == null ) {
			waste_.setVisible(false);
		} else {
			waste_.setCard(top.toString(),top.isRed());
			waste_.setVisible(true);
		}

		for ( int i = 0 ; i < MiniGame.COLUMNS ; i++ ) {
			columns_[i].getChildren().setAll(outlines_[i]);
			Card card = game_.getColumnTop(i);
			if ( card != null ) {
				columns_[i].getChildren()
				    .add(new CardView(card.toString(),card.isRed()));
			}
			columns_[i].getChildren().add(rings_[i]);
		}
	}

	public static void main ( String[] args ) {
		launch(args);
	}
}
