/*!
 *
 * Copyright (c) 2020-2022 Diality Inc. - All Rights Reserved.
 * \copyright
 * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN
 * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER.
 *
 * \file    RangeSlider.qml
 * \author  (last)      Behrouz NematiPour
 * \date    (last)      18-Jan-2022
 * \author  (original)  Behrouz NematiPour
 * \date    (original)  17-Sep-2020
 *
 */

// Qt
import QtQuick 2.12

// Project
//  Qml imports
import "qrc:/globals"

/*!
 * \brief   Denali project RangeSlider
 * \details This Component contains two handler for minimum and maximum
 *          which cannot pass each other.
 *          Can be used to adjust the minimum and maximum of a range.
 *
 */
RangeRect { id: _root
    enum HandlerOption {
        None    ,
        Min     ,
        Max
    }

    property alias  lowerText   : _rangeRect.minText
    property alias  upperText   : _rangeRect.maxText
    property alias  lowerTextHorizontalCenter: _rangeRect.minTextHorizontalCenter
    property alias  upperTextHorizontalCenter: _rangeRect.maxTextHorizontalCenter

    property alias  minValue    : _rangeRect.lowerBound             ///< value of the minimum slider handler
    property alias  maxValue    : _rangeRect.upperBound             ///< value of the maximum slider handler

    property bool   maxVerticalEdgeVisible: true
    property bool   minVerticalEdgeVisible: true

    property int    gapValue    : 0                                 ///< the gap between minValue and maxValue so the minValue shall always be gap value less than maxValue and vise versa.
    property real   minValueLowerBound    : _root.minimum           ///< min value cannot be lower  than this value
    property real   minValueUpperBound    : _root.maximum           ///< min value cannot be higher than this value
    property real   maxValueLowerBound    : _root.minimum           ///< max value cannot be lower  than this value
    property real   maxValueUpperBound    : _root.maximum           ///< max value cannot be higher than this value


    property int    curHandler  : RangeSlider.HandlerOption.None    ///< current active slider handler
    property int    diameter    : Variables.progressbarHandler      ///< handlers diameter

    property real   step        : 1                                 ///< each step difference
    property bool   stepSnap    : false                             ///< values within steps are not selectable if true

    property bool   ticks       : false                             ///< visible the tick marks

    property alias  color       : _rangeRect.color                  ///< the within range sliding color
    property alias  bgColor     : _root.color                       ///< the out of range sliding color

    property bool   minAdjusted : false                             ///< first time user adjustment happens
    property bool   maxAdjusted : false                             ///< first time user adjustment happens
    property bool   hasAdjust   : false                             ///< if set to true then component is grayed out until both min and max are adjusted

    onHasAdjustChanged  : {
        setAdjusted()
    }
    onMinAdjustedChanged: {
        setAdjusted()
    }
    onMaxAdjustedChanged: {
        setAdjusted()
    }

    /// root attributes
    clip            :  false

    height          : Variables.progressbarHeight
    touchMargin     : 25

    minimum         : 0
    maximum         : 0

    /// real-time bound change should effect the current set value
    onMinimumChanged: {
        if ( minValueLowerBound < minimum ) { minValueLowerBound = minimum }
        if ( maxValueLowerBound < minimum ) { maxValueLowerBound = minimum }
    }
    onMaximumChanged: {
        if ( minValueUpperBound > maximum ) { minValueUpperBound = maximum }
        if ( maxValueUpperBound > maximum ) { maxValueUpperBound = maximum }
    }

    onMinValueLowerBoundChanged: {
        if ( minValue < minValueLowerBound ) { minValue = minValueLowerBound }
    }
    onMinValueUpperBoundChanged: {
        if ( minValue > minValueUpperBound ) { minValue = minValueUpperBound }
    }
    onMaxValueLowerBoundChanged: {
        if ( maxValue < maxValueLowerBound ) { maxValue = maxValueLowerBound }
    }
    onMaxValueUpperBoundChanged: {
        if ( maxValue > maxValueUpperBound ) { maxValue = maxValueUpperBound }
    }

    /// Lable of the minimum of range
    minText {
        visible          : true
        anchors.topMargin: Variables.sliderTextMargin
        font.pixelSize   : Fonts.fontPixelSliderMarker
        font.bold        : false
    }

    ///< Lable of the maximum of range
    maxText {
        visible          : true
        anchors.topMargin: Variables.sliderTextMargin
        font.pixelSize   : Fonts.fontPixelSliderMarker
        font.bold        : false
    }

    ///
    /// \brief   grays out the rangebar and handler if not adjusted and hasAdjust set to true
    ///
    function setAdjusted() {
        function check() {
            _rangeRect   .color = minAdjusted && maxAdjusted ? Colors.createTreatmentActive : Colors.createTreatmentInactive
            _handlerLeft .color = minAdjusted ? Colors.createTreatmentActive: Colors.createTreatmentInactive
            _handlerRight.color = maxAdjusted ? Colors.createTreatmentActive: Colors.createTreatmentInactive
        }
        function active() {
            _rangeRect   .color = Colors.createTreatmentActive
            _handlerLeft .color = Colors.createTreatmentActive
            _handlerRight.color = Colors.createTreatmentActive
        }
        if ( ! hasAdjust ) {
            active()
        }
        else {
            check()
        }
    }

    ///
    /// \brief   calculate the value by position x
    /// \param   x : x cordinate
    ///
    function getValueOfX(x) {
        return ( x * ( maximum - minimum ) ) / width + minimum
    }

    function getXOfValue(vValue) {
        return ( ( vValue - minimum ) * width ) / ( maximum - minimum )
    }

    ///
    /// \brief   calculate the value by position x of the handler
    /// \details calculates the current value withing the range of minimum and maximum regarding the x cordinate of the active slider.
    /// \param   x : the active slider x cordinate.
    ///
    function setValue(x) {
        var value = 0
        if ( x <     0 ) { value = minimum;         return value; }
        if ( x > width ) { value = maximum;         return value; }

        value = getValueOfX(x)

        if ( step === 1 ) {                         return value; }

        var start = 0
        if ( ! stepSnap ) start = minimum

        value = Math.round((value - start) / step) * step + start

        if ( value < minimum ) { value = minimum;   return value; }
        if ( value > maximum ) { value = maximum;   return value; }
        return value;
    }

    ///
    /// \brief      updates correct lower or upper bound value regarding the x position
    /// \details    regarding the current mouse x position selects the correct handler and updated the bound value.
    /// \param      x : mouse x position.
    function setBound(x) {
        var value = setValue(x)
        // console.debug( maxValue - minValue, minValue, value, maxValue )
        if ( maxValue - minValue <= gapValue ) {
            // max correction if the values get too close together less than defined gap
            if ( curHandler === RangeSlider.HandlerOption.Max )  maxValue = minValue + gapValue
            // min correction if the values get too close together less than defined gap
            if ( curHandler === RangeSlider.HandlerOption.Min )  minValue = maxValue - gapValue
            // while value is between min and max do nothing and let the value gets out of the bound and then apply value.
            if ( minValue <= value && value <= maxValue ) return
        }

        var minDiff = Math.abs(minValue - value)
        var maxDiff = Math.abs(maxValue - value)
        if ( minDiff === maxDiff ) {
            if ( curHandler === RangeSlider.HandlerOption.Max ) checkLimitsMaxValueBounds(value)
            else                                                checkLimitsMinValueBounds(value)
        }
        else {
            // console.debug( minDiff, minValue, value, maxValue, maxDiff )
            if ( minDiff < maxDiff ) {
                // if (minDiff > limitGap) return // not sure if it needs but kept it as an idea.
                checkLimitsMinValueBounds(value)
            }
            else {
                // if (maxDiff > limitGap) return // not sure if it needs but kept it as an idea.
                checkLimitsMaxValueBounds(value)
            }
        }
    }

    function checkLimitsMinValueBounds( vValue ) {
        curHandler = RangeSlider.HandlerOption.Min
        if ( vValue < minValueLowerBound ) { minValue = minValueLowerBound; return }
        if ( vValue > minValueUpperBound ) { minValue = minValueUpperBound; return }
        setMinvalue(vValue)
    }

    function checkLimitsMaxValueBounds( vValue ) {
        curHandler = RangeSlider.HandlerOption.Max
        if ( vValue < maxValueLowerBound ) { maxValue = maxValueLowerBound; return }
        if ( vValue > maxValueUpperBound ) { maxValue = maxValueUpperBound; return }
        setMaxValue(vValue)
    }

    function setMinvalue(vValue) {
        minAdjusted = true
        minValue    = vValue
    }

    function setMaxValue(vValue) {
        maxAdjusted = true
        maxValue    = vValue
    }

    /// The main range rectangle bar
    RangeRect { id: _rangeRect
        property alias  lowerBound  : _rangeRect.minimum
        property alias  upperBound  : _rangeRect.maximum
        property real   minmaxDiff  : parent.maximum - parent.minimum

        x       : minmaxDiff ? ((parent.width * (lowerBound - parent.minimum)) / minmaxDiff) : minmaxDiff
        width   : minmaxDiff ? ((parent.width * (upperBound - lowerBound    )) / minmaxDiff) : minmaxDiff
        height  : parent.height

        radius  : 0
        decimal : _root.decimal

        minText {
            visible: false
            anchors.topMargin: -40
        }
        maxText {
            visible: false
            anchors.topMargin: -40
        }

        // propagation is not working on drag !
        // so it has to be implemented here as well
        onDragged: {
            setBound(vMouseEvent.x + _rangeRect.x)
            _root.clicked(vMouseEvent)
        }
        onClicked: {
            setBound(vMouseEvent.x + _rangeRect.x)
            _root.clicked(vMouseEvent)
        }
    }

    // used loader for performance since it may not always be required.
    // and can be a heavy Component
    /// Tick mark on the slider regarding the defined step within minimum and maximum in the range
    Loader { id: _ticksLoader
        active          : _root.ticks
        anchors.fill    : parent
        sourceComponent : TickMarks {
            decimal     : _root.decimal
            minimum     : _root.minimum
            maximum     : _root.maximum
            step        : _root.step
            stepSnap    : _root.stepSnap
            textColor   : _root.color
        }
    }

    onDragged: {
        setBound(vMouseEvent.x)
    }
    onClicked: {
        setBound(vMouseEvent.x)
    }

    /// Left most maximum range vertical edge
    Rectangle { id : _minVerticalEdge
        visible             : _root.minVerticalEdgeVisible
        color               : bgColor
        anchors.right       : _root.left
        anchors.top         : _root.top
        anchors.topMargin   : - (height/2 - _rangeRect.height/2)
        width               : 2
        height              : 30
    }

    /// Right most minimum range vertical edge
    Rectangle { id : _maxVerticalEdge
        visible             : _root.maxVerticalEdgeVisible
        color               : bgColor
        anchors.left        : _root.right
        anchors.top         : _root.top
        anchors.topMargin   : - (height/2 - _rangeRect.height/2)
        width               : 2
        height              : 30
    }

    /// Left most handler
    Rectangle { id: _handlerLeft
        property real diameter  : _root.diameter

        anchors.verticalCenter  : parent.verticalCenter
        anchors.horizontalCenter: _rangeRect.left

        width   : diameter
        height  : diameter
        radius  : diameter
        color   : Colors.highlightProgressBar
        border  {
            width   : 4
            color   : Colors.textMain
        }
    }

    /// Right most handler
    Rectangle { id: _handlerRight
        property real diameter      : _root.diameter

        anchors.verticalCenter      : parent.verticalCenter
        anchors.horizontalCenter    : _rangeRect.right

        width   : diameter
        height  : diameter
        radius  : diameter
        color   : Colors.highlightProgressBar
        border  {
            width   : 4
            color   : Colors.textMain
        }
    }
}
