/*!
 *
 * Copyright (c) 2020-2023 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)      23-Nov-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    : 0
    property real   minValueUpperBound    : 0
    property real   maxValueLowerBound    : 0
    property real   maxValueUpperBound    : 0


    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

    property bool showTickmarks     : true                          ///< Indicate whether tickMarks should be shown
    property bool isTickMarksRound  : true                          ///< Indicate whether the tickmakrs are round

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

    /// root attributes
    clip            :  false
    color: Colors.transparent

    height          : Variables.progressbarHeight
    touchMargin     : 25

    isRoundedEnds   : true
    hasBorder       : true

    minimum         : 0
    maximum         : 0

    /// 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.sliderHighlightColor : 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) {
        let mValue = 0
        if ( x <     0 ) { mValue = minimum;         return mValue; }
        if ( x > width ) { mValue = maximum;         return mValue; }

        mValue = getValueOfX(x)

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

        let start = 0
        if ( ! stepSnap ) start = minimum
        mValue = Math.round((mValue - start) / step) * step + start

        let decimals = Math.round(-Math.log10(step))
        if (decimals > 0) mValue = mValue.toFixed(decimals)

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

    ///
    /// \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) {
        let mValue = 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 <= mValue && mValue <= maxValue ) return
        }

        let minDiff = Math.abs(minValue - mValue)
        let maxDiff = Math.abs(maxValue - mValue)
        if ( minDiff === maxDiff ) {
            if ( curHandler === RangeSlider.HandlerOption.Max ) checkLimitsMaxValueBounds(mValue)
            else                                                checkLimitsMinValueBounds(mValue)
        }
        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(mValue)
            }
            else {
                // if (maxDiff > limitGap) return // not sure if it needs but kept it as an idea.
                checkLimitsMaxValueBounds(mValue)
            }
        }
    }

    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

        decimal : _root.decimal
        isRoundedEnds   : _root.isRoundedEnds
        hasBorder       : _root.hasBorder

        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 : _root.showTickmarks ? _tickMarkFactory : undefined
    }

    Component { id:_tickMarkFactory
        TickMarks {
            decimal     : _root.decimal
            minimum     : _root.minimum
            maximum     : _root.maximum
            step        : _root.step
            stepSnap    : _root.stepSnap
            textColor   : _root.color
            lineTickMarkHeight:  _rangeRect.height
            isTickMarkRound : _root.isTickMarksRound
            yDisplacement   : _root.isTickMarksRound ? (-_rangeRect.height) : 0
        }
    }

    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
        }
    }
}
