/*
 *  Keyframe-Based Video Summarization Designer
 *  Copyright (C) 2014 Waltenberger Andreas
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package at.ac.tuwien.ims.layout;

import at.ac.tuwien.ims.event.ZoomEvent;
import at.ac.tuwien.ims.ui.ZoomBox;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.effect.Glow;
import javafx.scene.effect.InnerShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;

/**
 * This class represents a Tile in the Template which is zoomable and the target for drag and drop action
 * @author Andreas Waltenberger
 *
 */
public class Tile extends StackPane {

	private double ratio;
	private double scalewidth;
	private double scaleheight;

	private double mousePosX;
	private double mousePosY;
	private double mouseOldX;
	private double mouseOldY;
	private double mouseDeltaX;
	private double mouseDeltaY;
	private double border;

	private InnerShadow shadow;
	private Glow glow;
	private ZoomBox zoom;

	public Tile(int width, int height) {
		border = 3.0;
		ratio = (double) width / height;
		scalewidth = width * 0.1;
		scaleheight = height * 0.1;
		shadow = new InnerShadow(15, Color.web("#0d1325"));
		glow = new Glow(0.5);

		setPrefSize(width, height);
		setMinSize(width, height);
		setAlignment(Pos.TOP_LEFT);
		setBorder(new Border(new BorderStroke(Color.GRAY, BorderStrokeStyle.SOLID, null, new BorderWidths(border))));
		setBackground(new Background(new BackgroundFill(Color.WHITE, null, null)));

		// only accept images dragged over the Tile
		setOnDragOver(new EventHandler<DragEvent>() {
			public void handle(DragEvent event) {
				if (event.getDragboard().hasImage()) {
					event.acceptTransferModes(TransferMode.COPY);
				}
				event.consume();
			}
		});

		// set a shadow effect on the Tile if an image gets dragged over it to display the Tile as target
		setOnDragEntered(new EventHandler<DragEvent>() {
			public void handle(DragEvent event) {
				if (event.getDragboard().hasImage()) {
					setEffect(shadow);
				}
				event.consume();
			}
		});

		setOnDragExited(new EventHandler<DragEvent>() {
			public void handle(DragEvent event) {
				setEffect(null);
				event.consume();
			}
		});

		setOnDragDropped(new EventHandler<DragEvent>() {
			public void handle(DragEvent event) {
				Dragboard db = event.getDragboard();
				boolean success = false;
				if (db.hasImage()) {
					// we have to subtract the borders of the Tile from the actual calculation
					double h = getInsets().getTop();
					double w = getInsets().getLeft();
					double height = getHeight() - 2 * h;
					double width = getWidth() - 2 * w;

					// retrieve the image and ...
					Image image = db.getImage();
					double image_ratio = image.getWidth() / image.getHeight();
					ImageView view = new ImageView(image);
					view.setPreserveRatio(true);
					view.setSmooth(true);

					// fit it into the Tile by comparing image-ratio with Tile-ratio
					if (ratio >= image_ratio) {
						view.setFitWidth(width);
					} else {
						view.setFitHeight(height);
					}

					// set a clip to display only the parts of the image which are inside the bounds of the Tile
					view.setClip(new Rectangle(width, height));
					getChildren().clear();
					
					// add a ZoomBox which features zoom and flip functionality for the image in the Tile
					zoom = new ZoomBox(view, width, height);
					getChildren().addAll(view, zoom);
					success = true;
				}
				event.setDropCompleted(success);
				event.consume();
			}
		});

		// needed for drag interaction with the image in a Tile
		setOnMousePressed(new EventHandler<MouseEvent>() {
			public void handle(MouseEvent event) {
				mousePosX = event.getX();
				mousePosY = event.getY();
				event.consume();
			}
		});

		// function to rearrange the image in the Tile
		setOnMouseDragged(new EventHandler<MouseEvent>() {
			@Override
			public void handle(MouseEvent event) {
				if (!getChildren().isEmpty()) {
					ImageView image = (ImageView) getChildren().get(0);
					Rectangle clip = (Rectangle) image.getClip();
					double imageHeight = image.getLayoutBounds().getHeight();
					double imageWidth = image.getLayoutBounds().getWidth();
					double h = getInsets().getTop();
					double w = getInsets().getLeft();
					double tileHeight = getHeight() - 2 * h;
					double tileWidth = getWidth() - 2 * w;
					double offsetX = image.getTranslateX();
					double offsetY = image.getTranslateY();
					mouseOldX = mousePosX;
					mouseOldY = mousePosY;
					mousePosX = event.getX();
					mousePosY = event.getY();
					mouseDeltaX = mousePosX - mouseOldX;
					mouseDeltaY = mousePosY - mouseOldY;

					// check to keep the image in the display bounds of the Tile along the x-coordinate
					if (event.isPrimaryButtonDown() && (offsetX + mouseDeltaX <= 0) && (offsetX + mouseDeltaX >= tileWidth - imageWidth)) {
						image.setTranslateX(offsetX + mouseDeltaX);
						// if the image is flipped horizontally we have to add our mouse movement, not subtract it
						if (image.getScaleX() == -1)
							clip.setX(clip.getX() + mouseDeltaX);
						else
							clip.setX(clip.getX() - mouseDeltaX);
					}

					// check to keep the image in the display bounds of the Tile along the y-coordinate
					if (event.isPrimaryButtonDown() && (offsetY + mouseDeltaY <= 0) && (offsetY + mouseDeltaY >= tileHeight - imageHeight)) {
						image.setTranslateY(offsetY + mouseDeltaY);
						clip.setY(clip.getY() - mouseDeltaY);
					}
				}

				event.consume();
			}
		});

		// add a glow effect to the Tile hovered over with the mouse
		setOnMouseEntered(new EventHandler<MouseEvent>() {
			public void handle(MouseEvent event) {
				if (!getChildren().isEmpty()) {
					setEffect(glow);
					zoom.setVisible(true);
				}
				event.consume();
			}
		});

		setOnMouseExited(new EventHandler<MouseEvent>() {
			public void handle(MouseEvent event) {
				if (!getChildren().isEmpty()) {
					setEffect(null);
					zoom.setVisible(false);
				}
				event.consume();
			}
		});

		// on a zoom in on the Tile ...
		addEventHandler(ZoomEvent.ZOOM_IN, new EventHandler<ZoomEvent>() {
			@Override
			public void handle(ZoomEvent event) {
				// ... make the Tile 10 percent bigger ...
				setPrefSize(getWidth() + scalewidth, getHeight() + scaleheight);

				if (!getChildren().isEmpty()) {
					// ... aswell as the image ...
					ImageView view = (ImageView) getChildren().get(0);
					if (view.getFitHeight() == 0)
						view.setFitWidth(view.getFitWidth() + scalewidth);
					else
						view.setFitHeight(view.getFitHeight() + scaleheight);

					// ... and the clip
					Rectangle clip = (Rectangle) view.getClip();
					clip.setWidth(clip.getWidth() + scalewidth);
					clip.setHeight(clip.getHeight() + scaleheight);
					view.setClip(clip);
				}
				event.consume();
			}
		});

		// contrary to zooming in
		addEventHandler(ZoomEvent.ZOOM_OUT, new EventHandler<ZoomEvent>() {
			@Override
			public void handle(ZoomEvent event) {
				setPrefSize(getWidth() - scalewidth, getHeight() - scaleheight);

				if (!getChildren().isEmpty()) {
					ImageView view = (ImageView) getChildren().get(0);
					if (view.getFitHeight() == 0)
						view.setFitWidth(view.getFitWidth() - scalewidth);
					else
						view.setFitHeight(view.getFitHeight() - scaleheight);

					Rectangle clip = (Rectangle) view.getClip();
					clip.setWidth(clip.getWidth() - scalewidth);
					clip.setHeight(clip.getHeight() - scaleheight);
					view.setClip(clip);
				}
				event.consume();
			}
		});
	}
}
