/*!
 *
 * Copyright (c) 2020-2024 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    Slider.qml
 * \author  (last)      Behrouz NematiPour
 * \date    (last)      26-Jan-2024
 * \author  (original)  Behrouz NematiPour
 * \date    (original)  18-Mar-2020
 *
 */

// Qt
import QtQuick 2.12

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

/*!
 * \brief   Denali project ProgressBar
 */
RangeRect { id: _root
    property real   value              : _progressRect.value
    property real   defaultValue       : _root.minimum

    property real   step               : 1
    property bool   stepSnap           : false
    property bool   stepSnapOnRelease  : true

    property bool   ticks              : false

    // TODO  suggest to BN that we should rename color to progressBarBgColor because slider's rangeRect has a color property also
    property alias  color              : _progressRect.color
    property alias  bgColor            : _root.color

    property alias  handler            : _handler
    property alias  handlerColor       : _handler.color
    property alias  handlerVisible     : _handler.visible

    property alias  diameter           : _handler.diameter

    property bool   isActive           : true
    property bool   inActiveZero       : false  // if inActiveZero:true, when is not active (inActive or active:false) sets to zero instead of minimum

    property alias  progressRectMargin : _progressRect.margin

    property int    tickMarksThickness : 2

    property bool   isRoundedEnds       : true
    property bool   hasBorder           : true
    property color  borderColor         : Colors.borderDisableButton

    signal activeChanged()
    signal handleSelected()
    signal sliderSelected()

    onDefaultValueChanged: refreshValue()

    // this function shall be used in case that any external value is forced to be set for the slider
    // like the OFF switch wants to externally set the slider value and bypass slider controlls and checks.
    // same is used in the main treatment Blood,dialyzer sliders to be set to the current value when get in to adjustment screen.
    function reset(vValue) {
        _root.value         = vValue
        _progressRect.value = vValue
    }

    function incrementValue (vInStepSegments) {
        updateValue(vInStepSegments, true)
    }

    function decrementValue (vInStepSegments) {
        updateValue(vInStepSegments, false)
    }

    function updateValue(vInStepSegments, vIsIncrement) {
        let amountChanged = 1
        if (vInStepSegments) {
            amountChanged = step
        }

        let newValue = Number.NaN
        if(vIsIncrement) {
            newValue = _progressRect.value + amountChanged
        } else {
            newValue = _progressRect.value - amountChanged
        }

        // Capping values based on min/max threshold
        if ( newValue < minimum ) newValue = minimum
        if ( newValue > maximum ) newValue = maximum

        // Update the slider's visual value
        _progressRect.previousSliderValue = newValue // for comparison purposes
        _progressRect.value               = newValue // visual value

        // update slider value with rounded new value
        update(newValue)
    }

    function setActiveVisuals(active) {
        if (active) {
            color        = Colors.sliderHighlightColor
            handlerColor = Colors.createTreatmentActive
        } else {
            color        = Colors.createTreatmentInactive
            handlerColor = Colors.createTreatmentInactive
        }
    }

    /*
      refreshValue() re-evaluates the _root.value and _progressRect.value based on the
      current active state. This function is used when the defaultValue property or
      isActive property changes.
    */
    function refreshValue() {
        // value is assigned a different value based on active-ness of the slider
        // This is to resolve the use of slider with a switch and arrows enabled.
        // It allows correct behavior when using arrow on a first initialize increment of
        // the slider when the slider has inActiveZero to true.
        if(!isActive) {
            _root.value = inActiveZero ? 0 : _root.defaultValue
        } else {
            _root.value = _root.defaultValue
        }

        // need to set the value of progressRect to reflect handle position
        _progressRect.value = _root.defaultValue
    }

    onIsActiveChanged: {
        setActiveVisuals(isActive)
        refreshValue () // re-evaluate values
        activeChanged() // emit
    }

    height              : Variables.sliderDefaultBodyHeight
    touchMargin         : 25
    leftRightTouchMargin: _handler.width/2

    radius  : _root.isRoundedEnds ? (height/2) : Variables.rangeRectRadius

    border.width: _root.hasBorder ? Variables.rangeRectBorderWidth : 0
    border.color: _root.isActive  ? Colors.borderButton : Colors.borderDisableButton

    minimum         : 0
    maximum         : 0

    // real-time bound change should effect the current set value
    onMinimumChanged: {
        if ( !isActive && inActiveZero ) { value = 0; return }
        if (value < minimum )
            value = minimum
    }
    onMaximumChanged: {
        if (value > maximum )
            value = maximum
    }

    minText {
        visible          : true
        anchors.topMargin: Variables.sliderTextMargin
        font.pixelSize   : Fonts.fontPixelSliderMarker
        font.bold        : false
    }
    maxText {
        visible          : true
        anchors.topMargin: Variables.sliderTextMargin
        font.pixelSize   : Fonts.fontPixelSliderMarker
        font.bold        : false
    }

    function calculateRoundedValue(vValue){
        return (Math.round(vValue / step) * step).toFixed(decimal)
    }

    function getValueOfX(x) {
        return ( x * ( maximum - minimum ) ) / width + minimum
    }

    function update(vValue) {
        _root.value = vValue
    }

    function calculateValue(x, isSnappingToTicks) {
        let mMinimum = Number(_root.minimum.toFixed(decimal))
        let mMaximum = Number(_root.maximum.toFixed(decimal))

        // the center of the handler is aligned on the snap point and half width shall be used to set as min not the entire width.
        // also half of the hadler is out of slider min position when set on min, which proves the same as above.
        if(x < ( _handler.width / 2 ) ) {
            // The outside of the slider, lower bound case
            return mMinimum
        }

        if(x > _root.width ) {
            // The outside of the slider, upper bound case
            return mMaximum
        }

        // Calculate the in-between :
        let mValue = 0
        let start = 0

        if ( ! stepSnap ) start = mMinimum

        // calculate the expected value based on x
        mValue = getValueOfX(x)

        // special handling for the case that the step segments are less than the handle's width
        let stepWidth = width / ((mMaximum - mMinimum) / step)
        if ( stepWidth < _handler.width ) {
            let valueStepCount = parseInt(mValue / step)
            mValue = valueStepCount * step
            return mValue.toFixed(decimal)
        }

        // For sliders with decimal min, max, values, we need to add refinement to
        // the value to achieve slider handle movement close to those of the whole number sliders
        if ( decimal > 0 ) {
            let additionalDecimalResolution = 3
            mValue = mValue.toFixed(decimal + additionalDecimalResolution)
        }

        if ( isSnappingToTicks ) {
            mValue = Math.round((mValue - start) / step) * step + start
            mValue = mValue.toFixed(decimal)
        }

        if ( mValue < mMinimum ) { return mMinimum; }
        if ( mValue > mMaximum ) { return mMaximum; }
                                   return mValue;
    }

    // used loader for performance since it may not always be required.
    // and can be a heavy Component
    Loader { id: _ticksLoader
        active          : ticks
        anchors.fill    : parent
        sourceComponent : TickMarks {
            decimal     : _root.decimal
            minimum     : _root.minimum
            maximum     : _root.maximum
            step        : _root.step
            stepSnap    : _root.stepSnap
            textColor   : _root.color
            lineTickMarkHeight      : _progressRect.height
            lineTickMarkThickness   : _root.tickMarksThickness
            isTickMarkRound : _root.isRoundedEnds
            yDisplacement   : _root.isRoundedEnds ? (-_handler.height/2 - _handler.border.width/2) : 0
            color           : _root.isActive ? Colors.borderButton : Colors.borderDisableButton
        }
    }

    ProgressRect { id: _progressRect
        property real previousSliderValue: Number.NaN
        value   : minimum
        color   : Colors.sliderHighlightColor
        decimal : _root.decimal

        minimum : _root.minimum
        maximum : _root.maximum
        leftRightTouchMargin: _handler.width/2

        radius  : _root.isRoundedEnds ? (height/2) : Variables.rangeRectRadius

        border.width: _root.hasBorder ? Variables.rangeRectBorderWidth : 0
        border.color: _root.isActive ? Colors.sliderProgressBorderActive : Colors.borderDisableButton

        // propagation is not working on drag !
        onDragged: {
            _root.dragged(vMouseEvent)
        }
        onPressed: {
            _root.pressed(vMouseEvent)
        }
        onReleased: {
            _root.released(vMouseEvent)
        }
    }

    function updateHandleValue(vCurrentPositionX)
    {
        // Passing false for snapping to get exact value in respect to x
        let newCurrentValue = calculateValue(vCurrentPositionX, false)

        // Check the cursor's current value to determine if the cursor had moved
        // Do not avoid type coercion, it will produce undesired results
        if(newCurrentValue != _progressRect.previousSliderValue) {

           // The cursor did move
            if(stepSnapOnRelease) {
                // check for snapping state to save an iteration of recalculation if snap is off
                newCurrentValue = calculateValue(vCurrentPositionX, stepSnapOnRelease)
            }

            // Update the slider's value since the cursor did move
            _progressRect.previousSliderValue = newCurrentValue // for comparison purposes
            _progressRect.value          = newCurrentValue // visual value

            // update slider value with rounded new value
            update(calculateRoundedValue(newCurrentValue))

        }
    }

    onDragged: {
        // On position change / dragging, the value is updated based on value calculated
        // Need to account for the extended touch areas
        let adjustedXPosition = vMouseEvent.x - _progressRect.leftRightTouchMargin
        let newCurrentValue = calculateValue(adjustedXPosition, !stepSnapOnRelease)
        _progressRect.value = newCurrentValue // update visual active slider bar

        update(calculateRoundedValue(newCurrentValue)) // displayed/slider value
    }

    onPressed: {
        // Indicate that the slider body was selected to provide handling/activation
        sliderSelected() // emit

        updateHandleValue(vMouseEvent.x - _progressRect.leftRightTouchMargin)
    }
    onReleased: {
        // Need to account for the extended touch areas
        updateHandleValue(vMouseEvent.x - _progressRect.leftRightTouchMargin)
    }

    Rectangle { id: _handler
        property bool handleSelected : false
        property real diameter       : Variables.progressbarHandler

        anchors.verticalCenter  : parent.verticalCenter
        anchors.horizontalCenter: _progressRect.right

        width   : diameter
        height  : diameter
        radius  : diameter
        color   : Colors.highlightProgressBar
        border  {
            width: Variables.progressbarHandlerBorderWidth
            color: Colors.textMain
        }
        MouseArea {
            anchors.fill: parent
            propagateComposedEvents: true
            onPressed: {
                mouse.accepted = false // allow propagtion to the lower mouse areas
                handleSelected() // emit
            }
        }
    }
}
