/*
 * Copyright 2015-2016 Alessio Vinerbi. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 */

package com.anchorage.docks.containers.zones;

import com.anchorage.docks.containers.common.AnchorageSettings;
import static com.anchorage.docks.containers.common.AnchorageSettings.FLOATING_NODE_DROPSHADOW_RADIUS;
import com.anchorage.docks.node.DockNode;
import com.anchorage.docks.node.ui.DockUIPanel;
import com.anchorage.docks.stations.DockStation;
import com.anchorage.docks.stations.DockSubStation;
import java.util.ArrayList;
import java.util.List;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;

/**
 *
 * @author avinerbi
 */
public final class DockZones extends Stage {

    private static final double CIRCLE_RADIUS = 100;
    public static final int OFFSET_IMAGE = 15;

    ///////// MAIN STAGE SCENE AND ROOT
    private Scene scene;
    private Pane mainRoot;

    ////////////////////////////////////
    /////////// CIRCLE EXTERNAL STAGE 
    private Stage circleStage;
    private Scene circleStageScene;
    private Pane circleStageRoot;
    private Circle circleZone;
    ////////////////////////////////////

    private static Image dragTopImage;
    private static Image dragBottomImage;
    private static Image dragLeftImage;
    private static Image dragRightImage;
    private static Image dragCenterImage;

    private List<ZoneSelector> selectors;

    ///////////////////////////////////////////////
    private DockStation ownerStation;
    private Rectangle currentAreaSelected;

    private DockNode currentNodeTarget;
    private DockNode nodeToMove;
    private DockNode.DockPosition currentPosition;

    private ZoneSelector currentZoneSelector = null;

    private Rectangle rectanglePreview;
    private Timeline opacityAnimationPreview;

    static {
        dragTopImage = new Image("dragtop.png");
        dragBottomImage = new Image("dragbottom.png");
        dragLeftImage = new Image("dragleft.png");
        dragRightImage = new Image("dragright.png");
        dragCenterImage = new Image("dragcenter.png");
    }

    private DockZones() {

    }

    public DockZones(DockStation station, DockNode nodeToMove) {

        this.nodeToMove = nodeToMove;
        this.ownerStation = station;

        initOwner(ownerStation.getStationWindow());
        initStyle(StageStyle.TRANSPARENT);

        buildUI();

        buildCircleStage();
        makeSelectors();
        createRectangleForPreview();

        setAlwaysOnTop(true);
        circleStage.setAlwaysOnTop(true);
    }

    private void createRectangleForPreview() {
        rectanglePreview = new Rectangle(0, 0, 50, 50);
        rectanglePreview.getStyleClass().add("dockzone-rectangle-preview");
        rectanglePreview.setOpacity(0);

        opacityAnimationPreview = new Timeline(new KeyFrame(Duration.seconds(0.5), new KeyValue(rectanglePreview.opacityProperty(), 0.5, Interpolator.LINEAR)));

        opacityAnimationPreview.setAutoReverse(true);
        opacityAnimationPreview.setCycleCount(-1);

        mainRoot.getChildren().add(rectanglePreview);

    }

    private void makeSelectors() {
        selectors = new ArrayList<>();

        if (ownerStation.getChildren().size() > 0) {
            // selectors of station
            selectors.add(new ZoneSelector(dragTopImage, DockNode.DockPosition.TOP, true, mainRoot, (mainRoot.getWidth() - dragTopImage.getWidth()) / 2, OFFSET_IMAGE));
            selectors.add(new ZoneSelector(dragBottomImage, DockNode.DockPosition.BOTTOM, true, mainRoot, (mainRoot.getWidth() - dragTopImage.getWidth()) / 2, mainRoot.getHeight() - dragBottomImage.getHeight() - OFFSET_IMAGE));
            selectors.add(new ZoneSelector(dragLeftImage, DockNode.DockPosition.LEFT, true, mainRoot, OFFSET_IMAGE, (mainRoot.getHeight() - dragLeftImage.getWidth()) / 2));
            selectors.add(new ZoneSelector(dragRightImage, DockNode.DockPosition.RIGHT, true, mainRoot, (mainRoot.getWidth() - dragRightImage.getWidth() - OFFSET_IMAGE), (mainRoot.getHeight() - dragRightImage.getWidth()) / 2));

            // selectors of node
            selectors.add(new ZoneSelector(dragTopImage, DockNode.DockPosition.TOP, false, circleStageRoot, (circleStageRoot.getWidth() - dragTopImage.getWidth()) / 2, OFFSET_IMAGE));
            selectors.add(new ZoneSelector(dragBottomImage, DockNode.DockPosition.BOTTOM, false, circleStageRoot, (circleStageRoot.getWidth() - dragBottomImage.getWidth()) / 2, circleStageRoot.getHeight() - dragBottomImage.getHeight() - OFFSET_IMAGE));
            selectors.add(new ZoneSelector(dragLeftImage, DockNode.DockPosition.LEFT, false, circleStageRoot, OFFSET_IMAGE, (circleStageRoot.getHeight() - dragLeftImage.getHeight()) / 2));
            selectors.add(new ZoneSelector(dragRightImage, DockNode.DockPosition.RIGHT, false, circleStageRoot, circleStageRoot.getWidth() - dragRightImage.getWidth() - OFFSET_IMAGE, (circleStageRoot.getHeight() - dragRightImage.getHeight()) / 2));
            selectors.add(new ZoneSelector(dragCenterImage, DockNode.DockPosition.CENTER, false, circleStageRoot, (circleStageRoot.getWidth() - dragCenterImage.getWidth()) / 2, (circleStageRoot.getHeight() - dragCenterImage.getHeight()) / 2));

        }
        else {
            selectors.add(new ZoneSelector(dragCenterImage, DockNode.DockPosition.CENTER, true, mainRoot, (mainRoot.getWidth() - dragCenterImage.getWidth()) / 2, (mainRoot.getHeight() - dragCenterImage.getHeight()) / 2));
        }

    }

    private void buildCircleStage() {

        circleStage = new Stage();
        circleStage.initStyle(StageStyle.TRANSPARENT);
        circleStage.initOwner(this);

        circleZone = new Circle(CIRCLE_RADIUS);
        circleZone.setCenterX(CIRCLE_RADIUS);
        circleZone.setCenterY(CIRCLE_RADIUS);
        circleZone.getStyleClass().add("dockzone-circle-container-selectors");

        circleStageRoot = new Pane(circleZone);
        circleStageRoot.setStyle("-fx-background-color:rgba(0,0,0,0);");

        circleStageScene = new Scene(circleStageRoot, CIRCLE_RADIUS * 2, CIRCLE_RADIUS * 2, Color.TRANSPARENT);

        circleStage.setScene(circleStageScene);

        circleStageRoot.setOpacity(0);
    }

    private void buildUI() {

        mainRoot = new Pane();
        mainRoot.setStyle("-fx-background-color:rgba(0,0,0,0);");

        scene = new Scene(mainRoot, ownerStation.getWidth(), ownerStation.getHeight(), Color.TRANSPARENT);

        setScene(scene);

        Point2D screenOrigin = ownerStation.localToScreen(ownerStation.getBoundsInLocal().getMinX(), ownerStation.getBoundsInLocal().getMinY());

        setX(screenOrigin.getX());
        setY(screenOrigin.getY());

    }

    public void showZones() {
        show();
        circleStage.show();
    }

    public void moveAt(DockNode node) {

        currentNodeTarget = node;

        Bounds screenBounds = node.getScreenBounds();

        if (circleStageRoot.opacityProperty().get() == 0) {
            circleStageRoot.setOpacity(1);
        }

        circleStage.setX(screenBounds.getMinX() + (screenBounds.getWidth() - circleStageRoot.getWidth()) / 2);
        circleStage.setY(screenBounds.getMinY() + (screenBounds.getHeight() - circleStageRoot.getHeight()) / 2);

    }

    public void hideCircleZones() {

        hidePreview();

        currentNodeTarget = null;

        if (currentZoneSelector != null) {
            currentZoneSelector.reset();
        }

        currentZoneSelector = null;

        currentPosition = null;
        circleStageRoot.setOpacity(0);
    }

    private void checkVisibilityConditions() {
        selectors.
                stream().
                forEach(z -> z.setZoneDisabled(false));

        if (currentNodeTarget == nodeToMove) {
            // disable border zones
            selectors.
                    stream().
                    filter(z -> !z.isStationZone() && z.getPosition() != DockNode.DockPosition.CENTER).
                    forEach(z -> z.setZoneDisabled(true));
        }
    }

    public boolean searchArea(double x, double y) {

        checkVisibilityConditions();

        ZoneSelector selector = selectors.stream()
                .filter(s -> s.overMe(x, y) && !s.isZoneDisabled())
                .findFirst()
                .orElse(null);

        highLight(selector);

        if (selector != null && selector != currentZoneSelector) {
            currentZoneSelector = selector;
            makePreview(currentZoneSelector, currentNodeTarget);
            currentPosition = currentZoneSelector.getPosition();
             
        }
        else {
            if (selector == null) {
                hidePreview();
                currentZoneSelector = null;
                currentPosition = null;
            }
        }
        return selector != null;
    }

    public DockNode getCurrentNodeTarget() {
        return currentNodeTarget;
    }

    public ZoneSelector getCurrentZoneSelector() {
        return currentZoneSelector;
    }

    public DockNode getNodeSource() {
        return nodeToMove;
    }

    public DockNode.DockPosition getCurrentPosition() {
        return currentPosition;
    }

    private void highLight(ZoneSelector selector) {

        selectors.stream().forEach(s -> s.reset());

        if (selector != null) {
            if (selector.isStationZone()) {
                circleStageRoot.setOpacity(0);
            }
            else {
                circleStageRoot.setOpacity(1);
            }
            selector.highLight();

        }
        else {
            if (rectanglePreview != null) {
                hidePreview();
            }
            currentNodeTarget = null;
        }
    }

    private DockStation getStation() {
        return ownerStation;
    }

    private void hidePreview() {
        if (AnchorageSettings.isDockingPositionPreview()) {
            stopAnimatePreview();
            rectanglePreview.setOpacity(0);
        }
    }

    private void showPreview(Bounds sceneBounds, ZoneSelector selector) {

        if (selector.getPosition() == DockNode.DockPosition.LEFT) {

            rectanglePreview.setX(sceneBounds.getMinX());
            rectanglePreview.setY(sceneBounds.getMinY());
            rectanglePreview.setWidth(sceneBounds.getWidth() / 2);
            rectanglePreview.setHeight(sceneBounds.getHeight());
        }

        if (selector.getPosition() == DockNode.DockPosition.TOP) {

            rectanglePreview.setX(sceneBounds.getMinX());
            rectanglePreview.setY(sceneBounds.getMinY());
            rectanglePreview.setWidth(sceneBounds.getWidth());
            rectanglePreview.setHeight(sceneBounds.getHeight() / 2);

        }

        if (selector.getPosition() == DockNode.DockPosition.RIGHT) {

            rectanglePreview.setX(sceneBounds.getMinX() + sceneBounds.getWidth() / 2);
            rectanglePreview.setY(sceneBounds.getMinY());
            rectanglePreview.setWidth(sceneBounds.getWidth() / 2);
            rectanglePreview.setHeight(sceneBounds.getHeight());
        }

        if (selector.getPosition() == DockNode.DockPosition.BOTTOM) {

            rectanglePreview.setX(sceneBounds.getMinX());
            rectanglePreview.setY(sceneBounds.getMinY() + sceneBounds.getHeight() / 2);
            rectanglePreview.setWidth(sceneBounds.getWidth());
            rectanglePreview.setHeight(sceneBounds.getHeight() / 2);

        }

        if (selector.getPosition() == DockNode.DockPosition.CENTER) {

            rectanglePreview.setX(sceneBounds.getMinX());
            rectanglePreview.setY(sceneBounds.getMinY());
            rectanglePreview.setWidth(sceneBounds.getWidth());
            rectanglePreview.setHeight(sceneBounds.getHeight());
        }
    }

    private void animatePreview() {
        stopAnimatePreview();
        rectanglePreview.setOpacity(1);
        rectanglePreview.toFront();
        opacityAnimationPreview.play();
    }

    private void stopAnimatePreview() {
        opacityAnimationPreview.stop();
    }

    private void makePreview(ZoneSelector selector, DockNode currentNodeTarget) {

        if (AnchorageSettings.isDockingPositionPreview()) {

            if (selector.isStationZone()) {
                Bounds sceneBounds = ownerStation.getBoundsInParent();
                showPreview(sceneBounds,selector);
            }
            else {
                Bounds nodeSceneBounds = currentNodeTarget.localToScene(currentNodeTarget.getBoundsInLocal());
                Bounds stationSceneBounds = ownerStation.localToScene(ownerStation.getBoundsInLocal());
                
                Bounds sceneBounds = new BoundingBox(nodeSceneBounds.getMinX()-stationSceneBounds.getMinX(),
                                                     nodeSceneBounds.getMinY()-stationSceneBounds.getMinY(),
                                                     nodeSceneBounds.getWidth(),nodeSceneBounds.getHeight());
                
                if (ownerStation.isSubStation())
                {
                    DockSubStation subStationNode = ownerStation.getDockNodeForSubStation();
                    
                    if (subStationNode.floatingProperty().get())
                    {
                        sceneBounds = new BoundingBox(sceneBounds.getMinX()-FLOATING_NODE_DROPSHADOW_RADIUS-subStationNode.getFloatableStage().getPaddingOffset().getLeft(),
                        sceneBounds.getMinY()-FLOATING_NODE_DROPSHADOW_RADIUS-subStationNode.getFloatableStage().getPaddingOffset().getTop()-DockUIPanel.BAR_HEIGHT,
                        sceneBounds.getWidth(),
                        sceneBounds.getHeight());
                    }
                }
                showPreview(sceneBounds,selector);
            }

            animatePreview();
        }

    }

}