// Copyright (c) 2021 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.

import QtQuick 2.2
import QtQuick.Layouts 1.1

import UM 1.0 as UM
import Cura 1.0 as Cura

Item
{
    id: sliderRoot

    // Handle properties
    property real handleSize: UM.Theme.getSize("slider_handle").width
    property real handleRadius: handleSize / 2
    property real minimumRangeHandleSize: handleSize / 2
    property color upperHandleColor: UM.Theme.getColor("slider_handle")
    property color lowerHandleColor: UM.Theme.getColor("slider_handle")
    property color rangeHandleColor: UM.Theme.getColor("slider_groove_fill")
    property color handleActiveColor: UM.Theme.getColor("slider_handle_active")
    property var activeHandle: upperHandle

    // Track properties
    property real trackThickness: UM.Theme.getSize("slider_groove").width // width of the slider track
    property real trackRadius: UM.Theme.getSize("slider_groove_radius").width
    property color trackColor: UM.Theme.getColor("slider_groove")

    // value properties
    property real maximumValue: 100
    property real minimumValue: 0
    property real minimumRange: 0 // minimum range allowed between min and max values
    property bool roundValues: true
    property real upperValue: maximumValue
    property real lowerValue: minimumValue

    property bool layersVisible: true
    property bool manuallyChanged: true     // Indicates whether the value was changed manually or during simulation

    function getUpperValueFromSliderHandle()
    {
        return upperHandle.getValue()
    }

    function setUpperValue(value)
    {
        upperHandle.setValue(value)
        updateRangeHandle()
    }

    function getLowerValueFromSliderHandle()
    {
        return lowerHandle.getValue()
    }

    function setLowerValue(value)
    {
        lowerHandle.setValue(value)
        updateRangeHandle()
    }

    function updateRangeHandle()
    {
        rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
    }

    // set the active handle to show only one label at a time
    function setActiveHandle(handle)
    {
        activeHandle = handle
    }

    function normalizeValue(value)
    {
        return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
    }

    // Slider track
    Rectangle
    {
        id: track

        width: sliderRoot.trackThickness
        height: sliderRoot.height - sliderRoot.handleSize
        radius: sliderRoot.trackRadius
        anchors.centerIn: sliderRoot
        color: sliderRoot.trackColor
        visible: sliderRoot.layersVisible
    }

    // Range handle
    Item
    {
        id: rangeHandle

        y: upperHandle.y + upperHandle.height
        width: sliderRoot.handleSize
        height: sliderRoot.minimumRangeHandleSize
        anchors.horizontalCenter: sliderRoot.horizontalCenter
        visible: sliderRoot.layersVisible

        // Set the new value when dragging
        function onHandleDragged()
        {
            sliderRoot.manuallyChanged = true

            upperHandle.y = y - upperHandle.height
            lowerHandle.y = y + height

            var upperValue = sliderRoot.getUpperValueFromSliderHandle()
            var lowerValue = sliderRoot.getLowerValueFromSliderHandle()

            // set both values after moving the handle position
            UM.SimulationView.setCurrentLayer(upperValue)
            UM.SimulationView.setMinimumLayer(lowerValue)
        }

        function setValueManually(value)
        {
            sliderRoot.manuallyChanged = true
            upperHandle.setValue(value)
        }

        function setValue(value)
        {
            var range = sliderRoot.upperValue - sliderRoot.lowerValue
            value = Math.min(value, sliderRoot.maximumValue)
            value = Math.max(value, sliderRoot.minimumValue + range)

            UM.SimulationView.setCurrentLayer(value)
            UM.SimulationView.setMinimumLayer(value - range)
        }

        Rectangle
        {
            width: sliderRoot.trackThickness
            height: parent.height + sliderRoot.handleSize
            anchors.centerIn: parent
            radius: sliderRoot.trackRadius
            color: sliderRoot.rangeHandleColor
        }

        MouseArea
        {
            anchors.fill: parent

            drag
            {
                target: parent
                axis: Drag.YAxis
                minimumY: upperHandle.height
                maximumY: sliderRoot.height - (rangeHandle.height + lowerHandle.height)
            }

            onPositionChanged: parent.onHandleDragged()
            onPressed:
            {
                sliderRoot.setActiveHandle(rangeHandle)
                sliderRoot.forceActiveFocus()
            }
        }

    }

    onHeightChanged : {
        // After a height change, the pixel-position of the handles is out of sync with the property value
        setLowerValue(lowerValue)
        setUpperValue(upperValue)
    }

    // Upper handle
    Rectangle
    {
        id: upperHandle

        y: sliderRoot.height - (sliderRoot.minimumRangeHandleSize + 2 * sliderRoot.handleSize)
        width: sliderRoot.handleSize
        height: sliderRoot.handleSize
        anchors.horizontalCenter: sliderRoot.horizontalCenter
        radius: sliderRoot.handleRadius
        color: upperHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.upperHandleColor
        visible: sliderRoot.layersVisible

        function onHandleDragged()
        {
            sliderRoot.manuallyChanged = true

            // don't allow the lower handle to be higher than the upper handle
            if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize)
            {
                lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize
            }

            // update the range handle
            sliderRoot.updateRangeHandle()

            // set the new value after moving the handle position
            UM.SimulationView.setCurrentLayer(getValue())
        }

        // get the upper value based on the slider position
        function getValue()
        {
            var result = y / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))
            result = sliderRoot.maximumValue + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumValue))
            result = sliderRoot.roundValues ? Math.round(result) : result
            return result
        }

        function setValueManually(value)
        {
            sliderRoot.manuallyChanged = true
            upperHandle.setValue(value)
        }

        // set the slider position based on the upper value
        function setValue(value)
        {
            // Normalize values between range, since using arrow keys will create out-of-the-range values
            value = sliderRoot.normalizeValue(value)

            UM.SimulationView.setCurrentLayer(value)

            var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue)
            // In case there is only one layer, the diff value results in a NaN, so this is for catching this specific case
            if (isNaN(diff))
            {
                diff = 0
            }
            var newUpperYPosition = Math.round(diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)))
            y = newUpperYPosition

            // update the range handle
            sliderRoot.updateRangeHandle()
        }

        Keys.onUpPressed: upperHandleLabel.setValue(upperHandleLabel.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
        Keys.onDownPressed: upperHandleLabel.setValue(upperHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))

        // dragging
        MouseArea
        {
            anchors.fill: parent

            drag
            {
                target: parent
                axis: Drag.YAxis
                minimumY: 0
                maximumY: sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)
            }

            onPositionChanged: parent.onHandleDragged()
            onPressed:
            {
                sliderRoot.setActiveHandle(upperHandle)
                upperHandleLabel.forceActiveFocus()
            }
        }

        SimulationSliderLabel
        {
            id: upperHandleLabel

            height: sliderRoot.handleSize
            anchors.bottom: parent.top
            anchors.bottomMargin: UM.Theme.getSize("narrow_margin").height
            anchors.horizontalCenter: parent.horizontalCenter
            target: Qt.point(parent.width / 2, 1)
            visible: sliderRoot.activeHandle == parent || sliderRoot.activeHandle == rangeHandle

            // custom properties
            maximumValue: sliderRoot.maximumValue
            value: sliderRoot.upperValue
            busy: UM.SimulationView.busy
            setValue: upperHandle.setValueManually // connect callback functions
        }
    }

    // Lower handle
    Rectangle
    {
        id: lowerHandle

        y: sliderRoot.height - sliderRoot.handleSize
        width: parent.handleSize
        height: parent.handleSize
        anchors.horizontalCenter: parent.horizontalCenter
        radius: sliderRoot.handleRadius
        color: lowerHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.lowerHandleColor

        visible: sliderRoot.layersVisible

        function onHandleDragged()
        {
            sliderRoot.manuallyChanged = true

            // don't allow the upper handle to be lower than the lower handle
            if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize)
            {
                upperHandle.y = y - (upperHandle.height + sliderRoot.minimumRangeHandleSize)
            }

            // update the range handle
            sliderRoot.updateRangeHandle()

            // set the new value after moving the handle position
            UM.SimulationView.setMinimumLayer(getValue())
        }

        // get the lower value from the current slider position
        function getValue()
        {
            var result = (y - (sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize));
            result = sliderRoot.maximumValue - sliderRoot.minimumRange + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumRange))
            result = sliderRoot.roundValues ? Math.round(result) : result
            return result
        }

        function setValueManually(value)
        {
            sliderRoot.manuallyChanged = true
            lowerHandle.setValue(value)
        }

        // set the slider position based on the lower value
        function setValue(value)
        {
            // Normalize values between range, since using arrow keys will create out-of-the-range values
            value = sliderRoot.normalizeValue(value)

            UM.SimulationView.setMinimumLayer(value)

            var diff = (value - sliderRoot.maximumValue) / (sliderRoot.minimumValue - sliderRoot.maximumValue)
            // In case there is only one layer, the diff value results in a NaN, so this is for catching this specific case
            if (isNaN(diff))
            {
                diff = 0
            }
            var newLowerYPosition = Math.round((sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize) + diff * (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)))
            y = newLowerYPosition

            // update the range handle
            sliderRoot.updateRangeHandle()
        }

        Keys.onUpPressed: lowerHandleLabel.setValue(lowerHandleLabel.value + ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
        Keys.onDownPressed: lowerHandleLabel.setValue(lowerHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))

        // dragging
        MouseArea
        {
            anchors.fill: parent

            drag
            {
                target: parent
                axis: Drag.YAxis
                minimumY: upperHandle.height + sliderRoot.minimumRangeHandleSize
                maximumY: sliderRoot.height - parent.height
            }

            onPositionChanged: parent.onHandleDragged()
            onPressed:
            {
                sliderRoot.setActiveHandle(lowerHandle)
                lowerHandleLabel.forceActiveFocus()
            }
        }

        SimulationSliderLabel
        {
            id: lowerHandleLabel

            height: sliderRoot.handleSize
            anchors.top: parent.bottom
            anchors.topMargin: UM.Theme.getSize("narrow_margin").height
            anchors.horizontalCenter: parent.horizontalCenter
            target: Qt.point(parent.width / 2, -1)
            visible: sliderRoot.activeHandle == parent || sliderRoot.activeHandle == rangeHandle

            // custom properties
            maximumValue: sliderRoot.maximumValue
            value: sliderRoot.lowerValue
            busy: UM.SimulationView.busy
            setValue: lowerHandle.setValueManually // connect callback functions
        }
    }
}
