/*
 * Copyright (c) 2016. See AUTHORS file.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.mbrlabs.mundus.editor.ui.widgets;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Cursor;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.ui.SplitPane;
import com.badlogic.gdx.scenes.scene2d.ui.WidgetGroup;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.scenes.scene2d.utils.Layout;
import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.kotcrab.vis.ui.FocusManager;
import com.kotcrab.vis.ui.VisUI;
import com.kotcrab.vis.ui.util.CursorManager;
import com.kotcrab.vis.ui.widget.VisSplitPane;

/**
 * This is a slightly modified version of kotcrab's VisSplitPane and fixes an
 * input issue. touchDown() in line 95 originally returned true, which prevents
 * the event to be passed on
 *
 * Extends functionality of standard {@link SplitPane}. Style supports handle
 * over {@link Drawable}. Due to scope of changes made this widget is not
 * compatible with {@link SplitPane}.
 * 
 * @author mzechner
 * @author Nathan Sweet
 * @author Kotcrab
 * @author mbrlabs
 *
 * @see SplitPane
 */
public class MundusSplitPane extends WidgetGroup {
    VisSplitPane.VisSplitPaneStyle style;
    private Actor firstWidget, secondWidget;
    boolean vertical;
    float splitAmount = 0.5f, minAmount, maxAmount = 1;
    // private float oldSplitAmount;

    private Rectangle firstWidgetBounds = new Rectangle();
    private Rectangle secondWidgetBounds = new Rectangle();
    Rectangle handleBounds = new Rectangle();
    private Rectangle firstScissors = new Rectangle();
    private Rectangle secondScissors = new Rectangle();

    Vector2 lastPoint = new Vector2();
    Vector2 handlePosition = new Vector2();

    private boolean mouseOnHandle;

    /**
     * @param firstWidget
     *            May be null.
     * @param secondWidget
     *            May be null.
     */
    public MundusSplitPane(Actor firstWidget, Actor secondWidget, boolean vertical) {
        this(firstWidget, secondWidget, vertical, "default-" + (vertical ? "vertical" : "horizontal"));
    }

    /**
     * @param firstWidget
     *            May be null.
     * @param secondWidget
     *            May be null.
     */
    public MundusSplitPane(Actor firstWidget, Actor secondWidget, boolean vertical, String styleName) {
        this(firstWidget, secondWidget, vertical, VisUI.getSkin().get(styleName, VisSplitPane.VisSplitPaneStyle.class));
    }

    /**
     * @param firstWidget
     *            May be null.
     * @param secondWidget
     *            May be null.
     */
    public MundusSplitPane(Actor firstWidget, Actor secondWidget, boolean vertical,
            VisSplitPane.VisSplitPaneStyle style) {
        this.firstWidget = firstWidget;
        this.secondWidget = secondWidget;
        this.vertical = vertical;
        setStyle(style);
        setFirstWidget(firstWidget);
        setSecondWidget(secondWidget);
        setSize(getPrefWidth(), getPrefHeight());
        initialize();
    }

    private void initialize() {
        addListener(new ClickListener() {
            Cursor.SystemCursor currentCursor;
            Cursor.SystemCursor targetCursor;

            @Override
            public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
                // originally returned true
                return false;
            }

            @Override
            public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
                CursorManager.restoreDefaultCursor();
                currentCursor = null;
            }

            @Override
            public boolean mouseMoved(InputEvent event, float x, float y) {
                if (handleBounds.contains(x, y)) {
                    if (vertical) {
                        targetCursor = Cursor.SystemCursor.VerticalResize;
                    } else {
                        targetCursor = Cursor.SystemCursor.HorizontalResize;
                    }

                    if (currentCursor != targetCursor) {
                        Gdx.graphics.setSystemCursor(targetCursor);
                        currentCursor = targetCursor;
                    }
                } else {
                    if (currentCursor != null) {
                        CursorManager.restoreDefaultCursor();
                        currentCursor = null;
                    }
                }

                return false;
            }
        });

        addListener(new InputListener() {
            int draggingPointer = -1;

            @Override
            public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
                // TODO potential bug with libgdx scene2d?
                // fixes issue when split bar could be still dragged even when
                // touchable is set to childrenOnly, probably scene2d issue
                if (isTouchable() == false) return false;

                if (draggingPointer != -1) return false;
                if (pointer == 0 && button != 0) return false;
                if (handleBounds.contains(x, y)) {
                    FocusManager.resetFocus(getStage());

                    draggingPointer = pointer;
                    lastPoint.set(x, y);
                    handlePosition.set(handleBounds.x, handleBounds.y);
                    return true;
                }
                return false;
            }

            @Override
            public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
                if (pointer == draggingPointer) draggingPointer = -1;
            }

            @Override
            public boolean mouseMoved(InputEvent event, float x, float y) {
                mouseOnHandle = handleBounds.contains(x, y);
                return false;
            }

            @Override
            public void touchDragged(InputEvent event, float x, float y, int pointer) {
                if (pointer != draggingPointer) return;

                Drawable handle = style.handle;
                if (!vertical) {
                    float delta = x - lastPoint.x;
                    float availWidth = getWidth() - handle.getMinWidth();
                    float dragX = handlePosition.x + delta;
                    handlePosition.x = dragX;
                    dragX = Math.max(0, dragX);
                    dragX = Math.min(availWidth, dragX);
                    splitAmount = dragX / availWidth;
                    if (splitAmount < minAmount) splitAmount = minAmount;
                    if (splitAmount > maxAmount) splitAmount = maxAmount;
                    lastPoint.set(x, y);
                } else {
                    float delta = y - lastPoint.y;
                    float availHeight = getHeight() - handle.getMinHeight();
                    float dragY = handlePosition.y + delta;
                    handlePosition.y = dragY;
                    dragY = Math.max(0, dragY);
                    dragY = Math.min(availHeight, dragY);
                    splitAmount = 1 - (dragY / availHeight);
                    if (splitAmount < minAmount) splitAmount = minAmount;
                    if (splitAmount > maxAmount) splitAmount = maxAmount;
                    lastPoint.set(x, y);
                }
                invalidate();
            }
        });
    }

    /**
     * Returns the split pane's style. Modifying the returned style may not have
     * an effect until {@link #setStyle(VisSplitPane.VisSplitPaneStyle)} is
     * called.
     */
    public VisSplitPane.VisSplitPaneStyle getStyle() {
        return style;
    }

    public void setStyle(VisSplitPane.VisSplitPaneStyle style) {
        this.style = style;
        invalidateHierarchy();
    }

    @Override
    public void layout() {
        if (!vertical)
            calculateHorizBoundsAndPositions();
        else
            calculateVertBoundsAndPositions();

        Actor firstWidget = this.firstWidget;
        if (firstWidget != null) {
            Rectangle firstWidgetBounds = this.firstWidgetBounds;
            firstWidget.setBounds(firstWidgetBounds.x, firstWidgetBounds.y, firstWidgetBounds.width,
                    firstWidgetBounds.height);
            if (firstWidget instanceof Layout) ((Layout) firstWidget).validate();
        }
        Actor secondWidget = this.secondWidget;
        if (secondWidget != null) {
            Rectangle secondWidgetBounds = this.secondWidgetBounds;
            secondWidget.setBounds(secondWidgetBounds.x, secondWidgetBounds.y, secondWidgetBounds.width,
                    secondWidgetBounds.height);
            if (secondWidget instanceof Layout) ((Layout) secondWidget).validate();
        }
    }

    @Override
    public float getPrefWidth() {
        float width = 0;
        if (firstWidget != null)
            width = firstWidget instanceof Layout ? ((Layout) firstWidget).getPrefWidth() : firstWidget.getWidth();
        if (secondWidget != null)
            width += secondWidget instanceof Layout ? ((Layout) secondWidget).getPrefWidth() : secondWidget.getWidth();
        if (!vertical) width += style.handle.getMinWidth();
        return width;
    }

    @Override
    public float getPrefHeight() {
        float height = 0;
        if (firstWidget != null)
            height = firstWidget instanceof Layout ? ((Layout) firstWidget).getPrefHeight() : firstWidget.getHeight();
        if (secondWidget != null) height += secondWidget instanceof Layout ? ((Layout) secondWidget).getPrefHeight()
                : secondWidget.getHeight();
        if (vertical) height += style.handle.getMinHeight();
        return height;
    }

    @Override
    public float getMinWidth() {
        return 0;
    }

    @Override
    public float getMinHeight() {
        return 0;
    }

    /**
     * @return first widgets bounds, changing returned rectangle values does not
     *         have any effect
     */
    public Rectangle getFirstWidgetBounds() {
        return new Rectangle(firstWidgetBounds);
    }

    /**
     * @return seconds widgets bounds, changing returned rectangle values does
     *         not have any effect
     */
    public Rectangle getSecondWidgetBounds() {
        return new Rectangle(secondWidgetBounds);
    }

    public void setVertical(boolean vertical) {
        this.vertical = vertical;
    }

    private void calculateHorizBoundsAndPositions() {
        Drawable handle = style.handle;

        float height = getHeight();

        float availWidth = getWidth() - handle.getMinWidth();
        float leftAreaWidth = (int) (availWidth * splitAmount);
        float rightAreaWidth = availWidth - leftAreaWidth;
        float handleWidth = handle.getMinWidth();

        firstWidgetBounds.set(0, 0, leftAreaWidth, height);
        secondWidgetBounds.set(leftAreaWidth + handleWidth, 0, rightAreaWidth, height);
        handleBounds.set(leftAreaWidth, 0, handleWidth, height);
    }

    private void calculateVertBoundsAndPositions() {
        Drawable handle = style.handle;

        float width = getWidth();
        float height = getHeight();

        float availHeight = height - handle.getMinHeight();
        float topAreaHeight = (int) (availHeight * splitAmount);
        float bottomAreaHeight = availHeight - topAreaHeight;
        float handleHeight = handle.getMinHeight();

        firstWidgetBounds.set(0, height - topAreaHeight, width, topAreaHeight);
        secondWidgetBounds.set(0, 0, width, bottomAreaHeight);
        handleBounds.set(0, bottomAreaHeight, width, handleHeight);
    }

    @Override
    public void draw(Batch batch, float parentAlpha) {
        validate();

        Color color = getColor();

        applyTransform(batch, computeTransform());
        // Matrix4 transform = batch.getTransformMatrix();
        if (firstWidget != null) {
            getStage().calculateScissors(firstWidgetBounds, firstScissors);
            if (ScissorStack.pushScissors(firstScissors)) {
                if (firstWidget.isVisible()) firstWidget.draw(batch, parentAlpha * color.a);
                batch.flush();
                ScissorStack.popScissors();
            }
        }
        if (secondWidget != null) {
            getStage().calculateScissors(secondWidgetBounds, secondScissors);
            if (ScissorStack.pushScissors(secondScissors)) {
                if (secondWidget.isVisible()) secondWidget.draw(batch, parentAlpha * color.a);
                batch.flush();
                ScissorStack.popScissors();
            }
        }

        Drawable handle = style.handle;
        if (mouseOnHandle && isTouchable() && style.handleOver != null) handle = style.handleOver;
        batch.setColor(color.r, color.g, color.b, parentAlpha * color.a);
        handle.draw(batch, handleBounds.x, handleBounds.y, handleBounds.width, handleBounds.height);
        resetTransform(batch);

    }

    @Override
    public Actor hit(float x, float y, boolean touchable) {
        if (touchable && getTouchable() == Touchable.disabled) return null;
        if (handleBounds.contains(x, y)) {
            return this;
        } else {
            return super.hit(x, y, touchable);
        }
    }

    /**
     * @param split
     *            The split amount between the min and max amount.
     */
    public void setSplitAmount(float split) {
        this.splitAmount = Math.max(Math.min(maxAmount, split), minAmount);
        invalidate();
    }

    public float getSplit() {
        return splitAmount;
    }

    public void setMinSplitAmount(float minAmount) {
        if (minAmount < 0) throw new GdxRuntimeException("minAmount has to be >= 0");
        if (minAmount >= maxAmount) throw new GdxRuntimeException("minAmount has to be < maxAmount");
        this.minAmount = minAmount;
    }

    public void setMaxSplitAmount(float maxAmount) {
        if (maxAmount > 1) throw new GdxRuntimeException("maxAmount has to be >= 0");
        if (maxAmount <= minAmount) throw new GdxRuntimeException("maxAmount has to be > minAmount");
        this.maxAmount = maxAmount;
    }

    /**
     * @param firstWidget
     *            May be null
     * @param secondWidget
     *            May be null
     */
    public void setWidgets(Actor firstWidget, Actor secondWidget) {
        setFirstWidget(firstWidget);
        setSecondWidget(secondWidget);
    }

    /**
     * @param widget
     *            May be null.
     */
    public void setFirstWidget(Actor widget) {
        if (firstWidget != null) super.removeActor(firstWidget);
        firstWidget = widget;
        if (widget != null) super.addActor(widget);
        invalidate();
    }

    /**
     * @param widget
     *            May be null.
     */
    public void setSecondWidget(Actor widget) {
        if (secondWidget != null) super.removeActor(secondWidget);
        secondWidget = widget;
        if (widget != null) super.addActor(widget);
        invalidate();
    }

    @Override
    public void addActor(Actor actor) {
        throw new UnsupportedOperationException("Manual actor manipulation not supported");
    }

    @Override
    public void addActorAt(int index, Actor actor) {
        throw new UnsupportedOperationException("Manual actor manipulation not supported");
    }

    @Override
    public void addActorBefore(Actor actorBefore, Actor actor) {
        throw new UnsupportedOperationException("Manual actor manipulation not supported");
    }

    @Override
    public boolean removeActor(Actor actor) {
        throw new UnsupportedOperationException("Manual actor manipulation not supported");
    }

    public static class VisSplitPaneStyle extends SplitPane.SplitPaneStyle {
        /** Optional **/
        public Drawable handleOver;

        public VisSplitPaneStyle() {
        }

        public VisSplitPaneStyle(VisSplitPane.VisSplitPaneStyle style) {
            super(style);
            this.handleOver = style.handleOver;
        }

        public VisSplitPaneStyle(Drawable handle, Drawable handleOver) {
            super(handle);
            this.handleOver = handleOver;
        }
    }
}