/*
 * 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
 */
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.anchorage.docks.node;

import com.anchorage.docks.containers.StageFloatable;
import static com.anchorage.docks.containers.common.AnchorageSettings.FLOATING_NODE_DROPSHADOW_RADIUS;
import com.anchorage.docks.node.ui.DockUIPanel;
import javafx.scene.layout.StackPane;

import com.anchorage.docks.containers.interfaces.DockContainableComponent;
import com.anchorage.docks.containers.interfaces.DockContainer;
import com.anchorage.docks.containers.subcontainers.DockTabberContainer;
import com.anchorage.docks.node.interfaces.DockNodeCloseRequestHandler;
import com.anchorage.docks.node.interfaces.DockNodeCreationListener;
import com.anchorage.docks.stations.DockStation;
import com.anchorage.docks.stations.DockSubStation;
import com.anchorage.system.AnchorageSystem;
import java.util.Objects;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Rectangle2D;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.stage.Screen;
import javafx.stage.Window;

/**
 *
 * @author Alessio
 */
public class DockNode extends StackPane implements DockContainableComponent {

    public enum DockPosition {

        LEFT,
        RIGHT,
        TOP,
        BOTTOM,
        CENTER
    }

    private DockUIPanel content;

    private BooleanProperty floatableProperty;
    private BooleanProperty closeableProperty;
    private BooleanProperty resizableProperty;
    private BooleanProperty maximizableProperty;

    private ObjectProperty<DockStation> station;
    private ReadOnlyBooleanWrapper floatingProperty;
    private ReadOnlyBooleanWrapper draggingProperty;
    private ReadOnlyBooleanWrapper resizingProperty;
    private ReadOnlyBooleanWrapper maximizingProperty;

    private ReadOnlyObjectWrapper<DockContainer> container;

    private StageFloatable stageFloatable;

    private double floatingStateCoordinateX;
    private double floatingStateCoordinateY;
    private double floatingStateWidth;
    private double floatingStateHeight;

    private DockNodeCloseRequestHandler closeRequestHanlder;

    private DockNode() {

        station = new SimpleObjectProperty<>(null);

        floatableProperty = new SimpleBooleanProperty(true);
        closeableProperty = new SimpleBooleanProperty(true);
        resizableProperty = new SimpleBooleanProperty(true);
        maximizableProperty = new SimpleBooleanProperty(true);

        floatingProperty = new ReadOnlyBooleanWrapper(false);
        draggingProperty = new ReadOnlyBooleanWrapper(false);
        resizingProperty = new ReadOnlyBooleanWrapper(false);
        maximizingProperty = new ReadOnlyBooleanWrapper(false);

        container = new ReadOnlyObjectWrapper<>(null);

    }

    public DockNode(DockUIPanel node) {

        this();

        this.content = node;

        buildUI(node);

        callCreationCallBack();
    }

    private void callCreationCallBack() {
        if (content.getNodeContent() instanceof DockNodeCreationListener) {
            ((DockNodeCreationListener) content.getNodeContent()).onDockNodeCreated(this);
        }
    }

    public void ensureVisibility() {
        if (container.get() instanceof DockTabberContainer) {
            ((DockTabberContainer) container.get()).ensureVisibility(this);
        }
    }

    public void setCloseRequestHandler(DockNodeCloseRequestHandler handler) {
        Objects.requireNonNull(handler);
        closeRequestHanlder = handler;
    }

    public DockNodeCloseRequestHandler getCloseRequestHandler() {
        return closeRequestHanlder;
    }

    public BooleanProperty floatableProperty() {
        return floatableProperty;
    }

    public BooleanProperty closeableProperty() {
        return closeableProperty;
    }

    public BooleanProperty resizableProperty() {
        return resizableProperty;
    }

    public BooleanProperty maximizableProperty() {
        return maximizableProperty;
    }

    public ReadOnlyBooleanProperty floatingProperty() {
        return floatingProperty.getReadOnlyProperty();
    }

    public ReadOnlyBooleanProperty draggingProperty() {
        return draggingProperty.getReadOnlyProperty();
    }

    public ReadOnlyBooleanProperty maximizingProperty() {
        return maximizingProperty.getReadOnlyProperty();
    }

    public ReadOnlyObjectProperty<DockContainer> containerProperty() {
        return container.getReadOnlyProperty();
    }

    public void setIcon(Image icon) {
        content.setIcon(icon);
    }

    public void restore() {
        if (draggingProperty.get()) {
            closeFloatingStage();
        }
        disableDragging();
    }

    public boolean isDockVisible() {
        if (containerProperty().get() == null || floatingProperty.get()) {
            return true;
        } else {
            return container.get().isDockVisible(this);
        }
    }

    public void closeFloatingStage() {

        if (stageFloatable != null) {
            stageFloatable.closeStage();
            stageFloatable = null;
        }

        floatingProperty.set(false);
    }

    public StageFloatable getFloatableStage() {
        return stageFloatable;
    }

    private void buildUI(DockUIPanel panel) {
        getChildren().add(panel);
        panel.setDockNode(this);
    }

    public void moveFloatable(double x, double y) {
        if (!maximizingProperty.get()) {
            stageFloatable.move(x, y);
        }
    }

    public void makeGhostFloatable(Window owner, double x, double y) {

        if (!floatingProperty.get()) {

            stageFloatable = new StageFloatable(this, owner, x, y);
            stageFloatable.show();

            makeTransparent();
        }

    }

    public void enableDraggingOnPosition(double x, double y) {
        draggingProperty.set(true);

        makeGhostFloatable(station.get().getScene().getWindow(), x, y);

        if (!maximizingProperty().get()) {
            AnchorageSystem.prepareDraggingZoneFor(station.get(), this);
        }
    }

    public void disableDragging() {
        draggingProperty.set(false);
        makeOpaque();
    }

    private void makeTransparent() {
        content.setOpacity(0.4);
    }

    private void makeOpaque() {
        content.setOpacity(1);
    }

    public void makeNodeActiveOnFloatableStage(Window owner, double x, double y) {

        disableDragging();

        if (!floatingProperty.get()) {

            if (floatableProperty.get()) {
                if (stageFloatable == null) {
                    makeGhostFloatable(owner, x, y);
                }
                stageFloatable.makeNodeActiveOnFloatableStage();

                floatingProperty.set(true);

            } else {
                closeFloatingStage();
            }
        }

    }

    /**
     * Get the value of station
     *
     * @return the value of station
     */
    public ObjectProperty<DockStation> stationProperty() {
        return station;
    }

    @Override
    public void setParentContainer(DockContainer container) {
        this.container.set(container);
    }

    @Override
    public DockContainer getParentContainer() {
        return container.get();
    }

    public void dockAsFloating(Window owner, DockStation station, double x, double y, double width, double height) {

        if (stationProperty().get() != null) {
            ensureVisibility();
            return;
        }

        station.add(this);
        stageFloatable = new StageFloatable(this, owner, x, y);
        stageFloatable.show();
        stageFloatable.setWidth(width);
        stageFloatable.setHeight(height);
        floatingProperty.set(true);
        this.station.set((DockStation) station);
    }

    public void dock(DockStation station, DockPosition position) {

        if (stationProperty().get() != null) {
            ensureVisibility();
            return;
        }

        station.add(this);
        station.putDock(this, position, 0.5);
        this.station.set((DockStation) station);
    }

    public void dock(DockNode nodeTarget, DockPosition position) {

        if (stationProperty().get() != null) {
            ensureVisibility();
            return;
        }

        nodeTarget.stationProperty().get().add(this);
        nodeTarget.getParentContainer().putDock(this, nodeTarget, position, 0.5);
        station.set(nodeTarget.station.get());
    }

    public void dock(DockStation station, DockPosition position, double percentage) {

        if (stationProperty().get() != null) {
            ensureVisibility();
            return;
        }
        station.add(this);
        station.putDock(this, position, percentage);
        this.station.set((DockStation) station);
    }

    public void dock(DockNode nodeTarget, DockPosition position, double percentage) {

        if (stationProperty().get() != null) {
            ensureVisibility();
            return;
        }

        nodeTarget.stationProperty().get().add(this);
        nodeTarget.getParentContainer().putDock(this, nodeTarget, position, percentage);
        station.set(nodeTarget.station.get());
    }

    public void dock(DockSubStation subStation, DockPosition position) {

        if (stationProperty().get() != null) {
            ensureVisibility();
            return;
        }

        subStation.putDock(this, position, 0.5);
    }

    public void dock(DockSubStation subStation, DockPosition position, double percentage) {

        if (stationProperty().get() != null) {
            ensureVisibility();
            return;
        }

        subStation.putDock(this, position, percentage);
    }

    public void undock() {

        if (stationProperty().get() == null) {
            return;
        }

        boolean isFloating = floatingProperty.get();

        restore();

        if (getParentContainer() != null) {
            getParentContainer().undock(this);
            station.get().remove(this);
        } else if (isFloating) {
            station.get().remove(this);
            station.set(null);
        }
    }

    public DockUIPanel getContent() {
        return content;
    }

    @Override
    public String toString() {
        return content.titleProperty().get();
    }

    public Bounds getSceneBounds() {
        return localToScene(getBoundsInLocal());
    }

    public Bounds getScreenBounds() {
        return localToScreen(getBoundsInLocal());
    }

    public boolean checkForTarget(double x, double y) {

        Point2D screenToScenePoint = getScene().getRoot().screenToLocal(x, y);
        Bounds sceneBounds = getSceneBounds();
        return sceneBounds.contains(screenToScenePoint.getX(), screenToScenePoint.getY());

    }

    public boolean insideTabContainer() {
        return container.get() instanceof DockTabberContainer;
    }

    public void maximizeOrRestore() {

        if (maximizingProperty.get()) {
            restoreLayout();
        } else {
            maximizeLayout();
        }

    }

    public void restoreLayout() {
        if (maximizableProperty.get()) {
            if (floatingProperty.get()) {
                stageFloatable.setX(floatingStateCoordinateX);
                stageFloatable.setY(floatingStateCoordinateY);
                stageFloatable.setWidth(floatingStateWidth);
                stageFloatable.setHeight(floatingStateHeight);
                maximizingProperty.set(false);
            } else if (station.get().restore(this)) {
                maximizingProperty.set(false);
            }

        }
    }

    private void moveStateToFullScreen() {
        // Get current screen of the stage      
        ObservableList<Screen> screens = Screen.getScreensForRectangle(new Rectangle2D(stageFloatable.getX(), stageFloatable.getY(), stageFloatable.getWidth(), stageFloatable.getHeight()));

        // Change stage properties
        Rectangle2D bounds = screens.get(0).getBounds();
        stageFloatable.setX(bounds.getMinX() - FLOATING_NODE_DROPSHADOW_RADIUS);
        stageFloatable.setY(bounds.getMinY() - FLOATING_NODE_DROPSHADOW_RADIUS);
        stageFloatable.setWidth(bounds.getWidth() + FLOATING_NODE_DROPSHADOW_RADIUS * 2);
        stageFloatable.setHeight(bounds.getHeight() + FLOATING_NODE_DROPSHADOW_RADIUS * 2);
    }

    public void maximizeLayout() {
        if (maximizableProperty.get()) {
            if (floatingProperty.get()) {
                floatingStateCoordinateX = stageFloatable.getX();
                floatingStateCoordinateY = stageFloatable.getY();
                floatingStateWidth = stageFloatable.getWidth();
                floatingStateHeight = stageFloatable.getHeight();
                moveStateToFullScreen();

                maximizingProperty.set(true);
            } else if (station.get().maximize(this)) {
                maximizingProperty.set(true);
            }

        }

    }
 
    public boolean isMenuButtonEnable() {
        return content.isMenuButtonEnable();
    }
 
 
}