/**
 * DevExtreme (viz/range_selector/range_selector.js)
 * Version: 17.2.10 (build 18271)
 * Build date: Fri Sep 28 2018
 *
 * Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED
 * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
 */
"use strict";
var registerComponent = require("../../core/component_registrator"),
    typeUtils = require("../../core/utils/type"),
    extend = require("../../core/utils/extend").extend,
    each = require("../../core/utils/iterator").each,
    vizUtils = require("../core/utils"),
    dateUtils = require("../../core/utils/date"),
    adjust = require("../../core/utils/math").adjust,
    addInterval = dateUtils.addInterval,
    dateToMilliseconds = dateUtils.dateToMilliseconds,
    getSequenceByInterval = dateUtils.getSequenceByInterval,
    rangeModule = require("../translators/range"),
    axisModule = require("../axes/base_axis"),
    patchFontOptions = vizUtils.patchFontOptions,
    parseUtils = require("../components/parse_utils"),
    _normalizeEnum = vizUtils.normalizeEnum,
    formatHelper = require("../../format_helper"),
    commonModule = require("./common"),
    slidersControllerModule = require("./sliders_controller"),
    trackerModule = require("./tracker"),
    rangeViewModule = require("./range_view"),
    seriesDataSourceModule = require("./series_data_source"),
    themeManagerModule = require("./theme_manager"),
    tickGeneratorModule = require("../axes/tick_generator"),
    log = require("../../core/errors").log,
    _isDefined = typeUtils.isDefined,
    _isNumber = typeUtils.isNumeric,
    _isDate = typeUtils.isDate,
    _max = Math.max,
    _ceil = Math.ceil,
    _floor = Math.floor,
    START_VALUE = "startValue",
    END_VALUE = "endValue",
    DATETIME = "datetime",
    SELECTED_RANGE = "selectedRange",
    VALUE = "value",
    DISCRETE = "discrete",
    SEMIDISCRETE = "semidiscrete",
    STRING = "string",
    SELECTED_RANGE_CHANGED = SELECTED_RANGE + "Changed",
    VALUE_CHANGED = VALUE + "Changed",
    CONTAINER_BACKGROUND_COLOR = "containerBackgroundColor",
    SLIDER_MARKER = "sliderMarker",
    OPTION_BACKGROUND = "background",
    LOGARITHMIC = "logarithmic",
    INVISIBLE_POS = -1e3,
    SEMIDISCRETE_GRID_SPACING_FACTOR = 50,
    DEFAULT_AXIS_DIVISION_FACTOR = 30,
    DEFAULT_MINOR_AXIS_DIVISION_FACTOR = 15,
    logarithmBase = 10;

function calculateMarkerHeight(renderer, value, sliderMarkerOptions) {
    var formattedText = void 0 === value ? commonModule.consts.emptySliderMarkerText : commonModule.formatValue(value, sliderMarkerOptions),
        textBBox = getTextBBox(renderer, formattedText, sliderMarkerOptions.font);
    return _ceil(textBBox.height) + 2 * sliderMarkerOptions.paddingTopBottom + commonModule.consts.pointerSize
}

function calculateScaleLabelHalfWidth(renderer, value, scaleOptions, tickIntervalsInfo) {
    var formattedText = commonModule.formatValue(value, scaleOptions.label, tickIntervalsInfo, scaleOptions.valueType, scaleOptions.type, scaleOptions.logarithmBase),
        textBBox = getTextBBox(renderer, formattedText, scaleOptions.label.font);
    return _ceil(textBBox.width / 2)
}

function parseValue(value) {
    return {
        startValue: value[0],
        endValue: value[1]
    }
}

function parseSelectedRange(selectedRange) {
    return [selectedRange.startValue, selectedRange.endValue]
}

function parseSliderMarkersPlaceholderSize(placeholderSize) {
    var placeholderWidthLeft, placeholderWidthRight, placeholderHeight;
    if (_isNumber(placeholderSize)) {
        placeholderWidthLeft = placeholderWidthRight = placeholderHeight = placeholderSize
    } else {
        if (placeholderSize) {
            if (_isNumber(placeholderSize.height)) {
                placeholderHeight = placeholderSize.height
            }
            if (_isNumber(placeholderSize.width)) {
                placeholderWidthLeft = placeholderWidthRight = placeholderSize.width
            } else {
                if (placeholderSize.width) {
                    if (_isNumber(placeholderSize.width.left)) {
                        placeholderWidthLeft = placeholderSize.width.left
                    }
                    if (_isNumber(placeholderSize.width.right)) {
                        placeholderWidthRight = placeholderSize.width.right
                    }
                }
            }
        } else {
            return null
        }
    }
    return {
        widthLeft: placeholderWidthLeft,
        widthRight: placeholderWidthRight,
        height: placeholderHeight
    }
}

function calculateIndents(renderer, scale, sliderMarkerOptions, indentOptions, tickIntervalsInfo) {
    var leftMarkerHeight, rightMarkerHeight, placeholderHeight, parsedPlaceholderSize, startTickValue, endTickValue, leftScaleLabelWidth = 0,
        rightScaleLabelWidth = 0,
        placeholderWidthLeft = 0,
        placeholderWidthRight = 0,
        ticks = "semidiscrete" === scale.type ? scale.customTicks : tickIntervalsInfo.ticks;
    indentOptions = indentOptions || {};
    parsedPlaceholderSize = parseSliderMarkersPlaceholderSize(sliderMarkerOptions.placeholderSize);
    if (parsedPlaceholderSize && void 0 === indentOptions.left && void 0 === indentOptions.right) {
        placeholderWidthLeft = parsedPlaceholderSize.widthLeft;
        placeholderWidthRight = parsedPlaceholderSize.widthRight
    } else {
        placeholderWidthLeft = indentOptions.left;
        placeholderWidthRight = indentOptions.right
    }
    if (parsedPlaceholderSize && void 0 === sliderMarkerOptions.placeholderHeight) {
        placeholderHeight = parsedPlaceholderSize.height
    } else {
        placeholderHeight = sliderMarkerOptions.placeholderHeight
    }
    if (sliderMarkerOptions.visible) {
        leftMarkerHeight = calculateMarkerHeight(renderer, scale.startValue, sliderMarkerOptions);
        rightMarkerHeight = calculateMarkerHeight(renderer, scale.endValue, sliderMarkerOptions);
        if (void 0 === placeholderHeight) {
            placeholderHeight = _max(leftMarkerHeight, rightMarkerHeight)
        }
    }
    if (scale.label.visible) {
        startTickValue = _isDefined(scale.startValue) ? ticks[0] : void 0;
        endTickValue = _isDefined(scale.endValue) ? ticks[ticks.length - 1] : void 0;
        leftScaleLabelWidth = calculateScaleLabelHalfWidth(renderer, startTickValue, scale, tickIntervalsInfo);
        rightScaleLabelWidth = calculateScaleLabelHalfWidth(renderer, endTickValue, scale, tickIntervalsInfo)
    }
    placeholderWidthLeft = void 0 !== placeholderWidthLeft ? placeholderWidthLeft : leftScaleLabelWidth;
    placeholderWidthRight = (void 0 !== placeholderWidthRight ? placeholderWidthRight : rightScaleLabelWidth) || 1;
    return {
        left: placeholderWidthLeft,
        right: placeholderWidthRight,
        top: placeholderHeight || 0,
        bottom: 0
    }
}

function calculateValueType(firstValue, secondValue) {
    var typeFirstValue = typeUtils.type(firstValue),
        typeSecondValue = typeUtils.type(secondValue),
        validType = function(type) {
            return typeFirstValue === type || typeSecondValue === type
        };
    return validType("date") ? DATETIME : validType("number") ? "numeric" : validType(STRING) ? STRING : ""
}

function showScaleMarkers(scaleOptions) {
    return scaleOptions.valueType === DATETIME && scaleOptions.marker.visible
}

function updateTranslatorRangeInterval(translatorRange, scaleOptions) {
    var intervalX = scaleOptions.minorTickInterval || scaleOptions.tickInterval;
    if ("datetime" === scaleOptions.valueType) {
        intervalX = dateUtils.dateToMilliseconds(intervalX)
    }
    translatorRange.addRange({
        interval: intervalX
    })
}

function checkLogarithmicOptions(options, defaultLogarithmBase, incidentOccurred) {
    var logarithmBase;
    if (!options) {
        return
    }
    logarithmBase = options.logarithmBase;
    if (options.type === LOGARITHMIC && logarithmBase <= 0 || logarithmBase && !_isNumber(logarithmBase)) {
        options.logarithmBase = defaultLogarithmBase;
        incidentOccurred("E2104")
    } else {
        if (options.type !== LOGARITHMIC) {
            options.logarithmBase = void 0
        }
    }
}

function calculateScaleAreaHeight(renderer, scaleOptions, visibleMarkers, tickIntervalsInfo) {
    var labelScaleOptions = scaleOptions.label,
        markerScaleOptions = scaleOptions.marker,
        placeholderHeight = scaleOptions.placeholderHeight,
        ticks = "semidiscrete" === scaleOptions.type ? scaleOptions.customTicks : tickIntervalsInfo.ticks,
        text = commonModule.formatValue(ticks[0], labelScaleOptions);
    if (placeholderHeight) {
        return placeholderHeight
    } else {
        return (labelScaleOptions.visible ? labelScaleOptions.topIndent + getTextBBox(renderer, text, labelScaleOptions.font).height : 0) + (visibleMarkers ? markerScaleOptions.topIndent + markerScaleOptions.separatorHeight : 0)
    }
}

function getMinorTickIntervalUnit(tickInterval, minorTickInterval, withCorrection) {
    var interval = dateUtils.getDateUnitInterval(minorTickInterval),
        majorUnit = dateUtils.getDateUnitInterval(tickInterval),
        idx = dateUtils.dateUnitIntervals.indexOf(interval);
    if (withCorrection && interval === majorUnit && idx > 0) {
        interval = dateUtils.dateUnitIntervals[idx - 1]
    }
    return interval
}

function getNextTickInterval(tickInterval, minorTickInterval, isDateType) {
    if (!tickInterval) {
        tickInterval = minorTickInterval
    } else {
        if (isDateType) {
            tickInterval = dateUtils.getNextDateUnit(tickInterval)
        } else {
            tickInterval += minorTickInterval
        }
    }
    return tickInterval
}

function calculateTickIntervalsForSemidiscreteScale(scaleOptions, min, max, screenDelta) {
    var interval, tickCountByInterval, tickCountByScreen, minorTickInterval = scaleOptions.minorTickInterval,
        tickInterval = scaleOptions.tickInterval,
        isDateType = "datetime" === scaleOptions.valueType,
        gridSpacingFactor = scaleOptions.axisDivisionFactor || {};
    if (!tickInterval) {
        do {
            interval = getNextTickInterval(tickInterval, minorTickInterval, isDateType);
            if (tickInterval !== interval) {
                tickInterval = interval
            } else {
                break
            }
            if (isDateType) {
                interval = dateToMilliseconds(tickInterval)
            }
            tickCountByInterval = _ceil((max - min) / interval);
            tickCountByScreen = _floor(screenDelta / (gridSpacingFactor[tickInterval] || SEMIDISCRETE_GRID_SPACING_FACTOR)) || 1
        } while (interval && tickCountByInterval > tickCountByScreen)
    }
    return {
        tickInterval: tickInterval,
        minorTickInterval: minorTickInterval,
        bounds: {
            minVisible: min,
            maxVisible: max
        },
        ticks: []
    }
}

function updateTickIntervals(scaleOptions, screenDelta, incidentOccurred, range) {
    var result, ticksInfo, length, min = _isDefined(range.minVisible) ? range.minVisible : range.min,
        max = _isDefined(range.maxVisible) ? range.maxVisible : range.max,
        categoriesInfo = scaleOptions._categoriesInfo,
        bounds = {};
    if (scaleOptions.type === SEMIDISCRETE) {
        result = calculateTickIntervalsForSemidiscreteScale(scaleOptions, min, max, screenDelta)
    } else {
        ticksInfo = tickGeneratorModule.tickGenerator({
            axisType: scaleOptions.type,
            dataType: scaleOptions.valueType,
            logBase: scaleOptions.logarithmBase,
            axisDivisionFactor: scaleOptions.axisDivisionFactor,
            minorAxisDivisionFactor: scaleOptions.minorAxisDivisionFactor,
            calculateMinors: true,
            allowDecimals: scaleOptions.allowDecimals,
            endOnTick: scaleOptions.endOnTick,
            incidentOccurred: incidentOccurred
        })({
            min: min,
            max: max,
            categories: _isDefined(categoriesInfo) ? categoriesInfo.categories : []
        }, screenDelta, scaleOptions.tickInterval, scaleOptions.forceUserTickInterval, void 0, scaleOptions.minorTickInterval, scaleOptions.minorTickCount);
        length = ticksInfo.ticks.length;
        bounds.minVisible = ticksInfo.ticks[0] < min ? ticksInfo.ticks[0] : min;
        bounds.maxVisible = ticksInfo.ticks[length - 1] > max ? ticksInfo.ticks[length - 1] : max;
        result = {
            tickInterval: ticksInfo.tickInterval,
            minorTickInterval: 0 === scaleOptions.minorTickInterval ? 0 : ticksInfo.minorTickInterval,
            bounds: bounds,
            ticks: ticksInfo.ticks
        }
    }
    return result
}

function calculateTranslatorRange(seriesDataSource, scaleOptions) {
    var minValue, maxValue, isEqualDates, categories, categoriesInfo, rangeForCategories, inverted = false,
        startValue = scaleOptions.startValue,
        endValue = scaleOptions.endValue,
        translatorRange = seriesDataSource ? seriesDataSource.getBoundRange().arg : new rangeModule.Range,
        isDate = "datetime" === scaleOptions.valueType,
        minRange = scaleOptions.minRange;
    if (scaleOptions.type === DISCRETE) {
        rangeForCategories = new rangeModule.Range({
            minVisible: startValue,
            maxVisible: endValue
        });
        rangeForCategories.addRange(translatorRange);
        translatorRange = rangeForCategories;
        categories = seriesDataSource ? seriesDataSource.argCategories : scaleOptions.categories || !seriesDataSource && startValue && endValue && [startValue, endValue];
        categories = categories || [];
        scaleOptions._categoriesInfo = categoriesInfo = vizUtils.getCategoriesInfo(categories, startValue, endValue)
    }
    if (scaleOptions.type === SEMIDISCRETE) {
        startValue = scaleOptions.startValue = correctValueByInterval(scaleOptions.startValue, isDate, minRange);
        endValue = scaleOptions.endValue = correctValueByInterval(scaleOptions.endValue, isDate, minRange);
        translatorRange.minVisible = correctValueByInterval(translatorRange.minVisible, isDate, minRange);
        translatorRange.maxVisible = correctValueByInterval(translatorRange.maxVisible, isDate, minRange);
        translatorRange.min = correctValueByInterval(translatorRange.min, isDate, minRange);
        translatorRange.max = correctValueByInterval(translatorRange.max, isDate, minRange)
    }
    if (_isDefined(startValue) && _isDefined(endValue)) {
        inverted = categoriesInfo ? categoriesInfo.inverted : startValue > endValue;
        minValue = categoriesInfo ? categoriesInfo.start : inverted ? endValue : startValue;
        maxValue = categoriesInfo ? categoriesInfo.end : inverted ? startValue : endValue
    } else {
        if (_isDefined(startValue) || _isDefined(endValue)) {
            minValue = startValue;
            maxValue = endValue
        } else {
            if (categoriesInfo) {
                minValue = categoriesInfo.start;
                maxValue = categoriesInfo.end
            }
        }
    }
    isEqualDates = _isDate(minValue) && _isDate(maxValue) && minValue.getTime() === maxValue.getTime();
    if (scaleOptions.type === SEMIDISCRETE || minValue !== maxValue && !isEqualDates) {
        translatorRange.addRange({
            invert: inverted,
            min: minValue,
            max: maxValue,
            minVisible: minValue,
            maxVisible: maxValue,
            dataType: scaleOptions.valueType
        })
    }
    translatorRange.addRange({
        categories: !seriesDataSource ? categories : void 0,
        base: scaleOptions.logarithmBase,
        axisType: scaleOptions.type
    });
    seriesDataSource && translatorRange.sortCategories(categories);
    if (!translatorRange.isDefined()) {
        if (isEqualDates) {
            scaleOptions.valueType = "numeric"
        }
        translatorRange.setStubData(scaleOptions.valueType)
    }
    return translatorRange
}

function startEndNotDefined(start, end) {
    return !_isDefined(start) || !_isDefined(end)
}

function getTextBBox(renderer, text, fontOptions) {
    var textElement = renderer.text(text, INVISIBLE_POS, INVISIBLE_POS).css(patchFontOptions(fontOptions)).append(renderer.root);
    var textBBox = textElement.getBBox();
    textElement.remove();
    return textBBox
}

function getDateMarkerVisibilityChecker(screenDelta) {
    return function(isDateScale, isMarkerVisible, min, max, tickInterval) {
        if (isMarkerVisible && isDateScale) {
            if (tickInterval.years || tickInterval.months >= 6 || screenDelta / SEMIDISCRETE_GRID_SPACING_FACTOR < _ceil((max - min) / dateToMilliseconds("year")) + 1) {
                isMarkerVisible = false
            }
        }
        return isMarkerVisible
    }
}

function updateScaleOptions(scaleOptions, seriesDataSource, translatorRange, tickIntervalsInfo, checkDateMarkerVisibility) {
    var bounds, isEmptyInterval, intervals, categoriesInfo = scaleOptions._categoriesInfo,
        isDateTime = scaleOptions.valueType === DATETIME;
    if (seriesDataSource && !seriesDataSource.isEmpty() && !translatorRange.stubData) {
        bounds = tickIntervalsInfo.bounds;
        translatorRange.addRange(bounds);
        scaleOptions.startValue = translatorRange.invert ? bounds.maxVisible : bounds.minVisible;
        scaleOptions.endValue = translatorRange.invert ? bounds.minVisible : bounds.maxVisible
    }
    scaleOptions.marker.visible = checkDateMarkerVisibility(isDateTime && scaleOptions.type.indexOf(DISCRETE) === -1, scaleOptions.marker.visible, scaleOptions.startValue, scaleOptions.endValue, tickIntervalsInfo.tickInterval);
    if (categoriesInfo) {
        scaleOptions.startValue = categoriesInfo.start;
        scaleOptions.endValue = categoriesInfo.end
    }
    if (scaleOptions.type.indexOf(DISCRETE) === -1) {
        isEmptyInterval = _isDate(scaleOptions.startValue) && _isDate(scaleOptions.endValue) && scaleOptions.startValue.getTime() === scaleOptions.endValue.getTime() || scaleOptions.startValue === scaleOptions.endValue
    }
    scaleOptions.isEmpty = startEndNotDefined(scaleOptions.startValue, scaleOptions.endValue) || isEmptyInterval;
    if (scaleOptions.isEmpty) {
        scaleOptions.startValue = scaleOptions.endValue = void 0
    } else {
        scaleOptions.minorTickInterval = tickIntervalsInfo.minorTickInterval;
        scaleOptions.tickInterval = tickIntervalsInfo.tickInterval;
        if (isDateTime && (!_isDefined(scaleOptions.label.format) || scaleOptions.type === SEMIDISCRETE && scaleOptions.minorTickInterval !== scaleOptions.tickInterval)) {
            if (scaleOptions.type === DISCRETE) {
                scaleOptions.label.format = formatHelper.getDateFormatByTicks(tickIntervalsInfo.ticks)
            } else {
                if (!scaleOptions.marker.visible) {
                    scaleOptions.label.format = formatHelper.getDateFormatByTickInterval(scaleOptions.startValue, scaleOptions.endValue, scaleOptions.tickInterval)
                } else {
                    scaleOptions.label.format = dateUtils.getDateFormatByTickInterval(scaleOptions.tickInterval)
                }
            }
        }
    }
    if (scaleOptions.type === SEMIDISCRETE) {
        intervals = getIntervalCustomTicks(scaleOptions);
        scaleOptions.customMinorTicks = intervals.altIntervals;
        scaleOptions.customTicks = intervals.intervals;
        scaleOptions.customBoundTicks = [scaleOptions.customTicks[0]]
    }
}

function prepareScaleOptions(scaleOption, seriesDataSource, incidentOccurred, containerColor) {
    var parser, parsedValue = 0,
        valueType = parseUtils.correctValueType(_normalizeEnum(scaleOption.valueType)),
        validateStartEndValues = function(field, parser) {
            var messageToIncidentOccurred = field === START_VALUE ? "start" : "end";
            if (_isDefined(scaleOption[field])) {
                parsedValue = parser(scaleOption[field]);
                if (_isDefined(parsedValue)) {
                    scaleOption[field] = parsedValue
                } else {
                    scaleOption[field] = void 0;
                    incidentOccurred("E2202", [messageToIncidentOccurred])
                }
            }
        };
    if (seriesDataSource) {
        valueType = seriesDataSource.getCalculatedValueType() || valueType
    }
    if (!valueType) {
        valueType = calculateValueType(scaleOption.startValue, scaleOption.endValue) || "numeric"
    }
    if (valueType === STRING || scaleOption.categories) {
        scaleOption.type = DISCRETE;
        valueType = STRING
    }
    scaleOption.containerColor = containerColor;
    scaleOption.valueType = valueType;
    scaleOption.dataType = valueType;
    parser = parseUtils.getParser(valueType);
    validateStartEndValues(START_VALUE, parser);
    validateStartEndValues(END_VALUE, parser);
    checkLogarithmicOptions(scaleOption, logarithmBase, incidentOccurred);
    if (!scaleOption.type) {
        scaleOption.type = "continuous"
    }
    void 0 === scaleOption.tickInterval && (scaleOption.tickInterval = scaleOption.majorTickInterval);
    scaleOption.minorTick.visible && (scaleOption.minorTick.visible = scaleOption.showMinorTicks);
    scaleOption.parser = parser;
    if (scaleOption.type === SEMIDISCRETE) {
        scaleOption.minorTick.visible = false;
        scaleOption.minorTickInterval = scaleOption.minRange;
        scaleOption.marker.visible = false;
        scaleOption.maxRange = void 0
    }
    scaleOption.axisDivisionFactor = _isDefined(scaleOption.axisDivisionFactor) ? scaleOption.axisDivisionFactor : DEFAULT_AXIS_DIVISION_FACTOR;
    scaleOption.minorAxisDivisionFactor = _isDefined(scaleOption.minorAxisDivisionFactor) ? scaleOption.minorAxisDivisionFactor : DEFAULT_MINOR_AXIS_DIVISION_FACTOR;
    return scaleOption
}

function correctValueByInterval(value, isDate, interval) {
    if (_isDefined(value)) {
        value = isDate ? dateUtils.correctDateWithUnitBeginning(new Date(value), interval) : adjust(_floor(adjust(value / interval)) * interval)
    }
    return value
}

function getIntervalCustomTicks(options) {
    var min = options.startValue,
        max = options.endValue,
        isDate = "datetime" === options.valueType,
        tickInterval = options.tickInterval,
        res = {
            intervals: []
        };
    if (!_isDefined(min) || !_isDefined(max)) {
        return res
    }
    res.intervals = getSequenceByInterval(min, max, options.minorTickInterval);
    if (tickInterval !== options.minorTickInterval) {
        res.altIntervals = res.intervals;
        min = correctValueByInterval(min, isDate, tickInterval);
        max = correctValueByInterval(max, isDate, tickInterval);
        res.intervals = getSequenceByInterval(min, max, tickInterval);
        res.intervals[0] = res.altIntervals[0]
    }
    return res
}

function getPrecisionForSlider(startValue, endValue, screenDelta) {
    var d = Math.abs(endValue - startValue) / screenDelta,
        tail = d - Math.floor(d);
    return tail > 0 ? Math.ceil(Math.abs(adjust(vizUtils.getLog(tail, 10)))) : 0
}
var dxRangeSelector = require("../core/base_widget").inherit({
    _eventsMap: {
        onSelectedRangeChanged: {
            name: SELECTED_RANGE_CHANGED
        },
        onValueChanged: {
            name: VALUE_CHANGED
        }
    },
    _setDeprecatedOptions: function() {
        this.callBase.apply(this, arguments);
        extend(this._deprecatedOptions, {
            "sliderMarker.padding": {
                since: "15.1",
                message: "Use the 'paddingTopBottom' and 'paddingLeftRight' options instead"
            },
            "sliderMarker.placeholderSize": {
                since: "15.1",
                message: "Use the 'placeholderHeight' and 'indent' options instead"
            },
            "scale.majorTickInterval": {
                since: "15.2",
                message: "Use the 'tickInterval' options instead"
            },
            "scale.showMinorTicks": {
                since: "15.2",
                message: "Use the 'minorTick.visible' options instead"
            },
            selectedRange: {
                since: "16.2",
                message: "Use the 'value' option instead"
            },
            onSelectedRangeChanged: {
                since: "16.2",
                message: "Use the 'onValueChanged' option instead"
            },
            "behavior.callSelectedRangeChanged": {
                since: "16.2",
                message: "Use the 'behavior.callValueChanged' option instead"
            },
            "scale.useTicksAutoArrangement": {
                since: "17.1",
                message: "Use the 'scale.label.overlappingBehavior' option instead"
            }
        })
    },
    _rootClassPrefix: "dxrs",
    _rootClass: "dxrs-range-selector",
    _dataIsReady: function() {
        return this._dataIsLoaded()
    },
    _initialChanges: ["DATA_SOURCE", "SELECTED_RANGE", "VALUE", "DISABLED"],
    _themeDependentChanges: ["MOSTLY_TOTAL"],
    _initCore: function() {
        var rangeViewGroup, slidersGroup, scaleGroup, scaleBreaksGroup, trackersGroup, that = this,
            renderer = that._renderer,
            root = renderer.root;
        root.css({
            "touch-action": "pan-y",
            "-ms-touch-action": "pan-y"
        });
        that._clipRect = renderer.clipRect();
        rangeViewGroup = renderer.g().attr({
            "class": "dxrs-view"
        }).append(root);
        slidersGroup = renderer.g().attr({
            "class": "dxrs-slidersContainer",
            "clip-path": that._clipRect.id
        }).append(root);
        scaleGroup = renderer.g().attr({
            "class": "dxrs-scale",
            "clip-path": that._clipRect.id
        }).append(root);
        scaleBreaksGroup = renderer.g().attr({
            "class": "dxrs-scale-breaks"
        }).append(root);
        trackersGroup = renderer.g().attr({
            "class": "dxrs-trackers"
        }).append(root);
        that._axis = new AxisWrapper({
            renderer: renderer,
            root: scaleGroup,
            scaleBreaksGroup: scaleBreaksGroup,
            updateSelectedRange: function(range) {
                that.setValue(parseSelectedRange(range))
            },
            incidentOccurred: that._incidentOccurred
        });
        that._rangeView = new rangeViewModule.RangeView({
            renderer: renderer,
            root: rangeViewGroup,
            translator: that._axis.getTranslator()
        });
        that._slidersController = new slidersControllerModule.SlidersController({
            renderer: renderer,
            root: slidersGroup,
            trackersGroup: trackersGroup,
            updateSelectedRange: function(range, lastSelectedRange) {
                if (!that._rangeOption) {
                    that._suppressDeprecatedWarnings();
                    that.option(SELECTED_RANGE, range);
                    that._resumeDeprecatedWarnings();
                    that.option(VALUE, parseSelectedRange(range))
                }
                if (that._options.onSelectedRangeChanged || that.hasEvent("selectedRangeChanged")) {
                    that._eventTrigger(SELECTED_RANGE_CHANGED, {
                        startValue: range.startValue,
                        endValue: range.endValue
                    })
                }
                that._eventTrigger(VALUE_CHANGED, {
                    value: parseSelectedRange(range),
                    previousValue: parseSelectedRange(lastSelectedRange)
                })
            },
            translator: that._axis.getTranslator()
        });
        that._tracker = new trackerModule.Tracker({
            renderer: renderer,
            controller: that._slidersController
        })
    },
    _getDefaultSize: function() {
        return {
            width: 400,
            height: 160
        }
    },
    _disposeCore: function() {
        this._axis.dispose();
        this._slidersController.dispose();
        this._tracker.dispose()
    },
    _createThemeManager: function() {
        return new themeManagerModule.ThemeManager
    },
    _applySize: function(rect) {
        this._clientRect = rect.slice();
        this._change(["MOSTLY_TOTAL"])
    },
    _optionChangesMap: {
        scale: "SCALE",
        selectedRange: "SELECTED_RANGE",
        value: "VALUE",
        dataSource: "DATA_SOURCE",
        disabled: "DISABLED"
    },
    _optionChangesOrder: ["SCALE", "DATA_SOURCE"],
    _change_SCALE: function() {
        this._change(["MOSTLY_TOTAL"])
    },
    _change_DATA_SOURCE: function() {
        if (this._initialized || this._options.dataSource) {
            this._options[SELECTED_RANGE] = this._options[VALUE] = null;
            this._updateDataSource()
        }
    },
    _customChangesOrder: ["MOSTLY_TOTAL", "SELECTED_RANGE", "VALUE", "SLIDER_SELECTION", "DISABLED"],
    _change_MOSTLY_TOTAL: function() {
        this._applyMostlyTotalChange()
    },
    _change_SLIDER_SELECTION: function() {
        var that = this,
            range = that._options[SELECTED_RANGE],
            value = that._options[VALUE];
        that._slidersController.setSelectedRange(value ? parseValue(value) : range && range)
    },
    _change_SELECTED_RANGE: function() {
        var that = this,
            option = that._rangeOption && that._rangeOption[SELECTED_RANGE];
        if (option) {
            that._options[SELECTED_RANGE] = option;
            that.setValue(parseSelectedRange(option))
        }
    },
    _change_VALUE: function() {
        var that = this,
            option = that._rangeOption && that._rangeOption[VALUE];
        if (option) {
            that._options[VALUE] = option;
            that.setValue(option)
        }
    },
    _change_DISABLED: function() {
        var renderer = this._renderer,
            root = renderer.root;
        if (this.option("disabled")) {
            root.attr({
                "pointer-events": "none",
                filter: renderer.getGrayScaleFilter().id
            })
        } else {
            root.attr({
                "pointer-events": null,
                filter: null
            })
        }
    },
    _validateRange: function(start, end) {
        var that = this,
            translator = that._axis.getTranslator();
        if (_isDefined(start) && !translator.isValid(start) || _isDefined(end) && !translator.isValid(end)) {
            that._incidentOccurred("E2203")
        }
    },
    _applyChanges: function() {
        var that = this,
            selectedRange = that._options[SELECTED_RANGE],
            value = that._options[VALUE];
        if (that._changes.has("VALUE") && value) {
            that._rangeOption = {
                value: [value[0], value[1]]
            }
        } else {
            if (that._changes.has("SELECTED_RANGE") && selectedRange) {
                that._rangeOption = {
                    selectedRange: selectedRange
                }
            }
        }
        that.callBase.apply(that, arguments);
        that._rangeOption = null;
        that.__isResizing = false
    },
    _applyMostlyTotalChange: function() {
        var currentAnimationEnabled, that = this,
            renderer = that._renderer,
            rect = that._clientRect;
        if (that.__isResizing) {
            currentAnimationEnabled = renderer.animationEnabled();
            renderer.updateAnimationOptions({
                enabled: false
            })
        }
        that._clipRect.attr({
            x: rect[0],
            y: rect[1],
            width: rect[2] - rect[0],
            height: rect[3] - rect[1]
        });
        that._updateContent({
            left: rect[0],
            top: rect[1],
            width: rect[2] - rect[0],
            height: rect[3] - rect[1]
        });
        if (that.__isResizing) {
            renderer.updateAnimationOptions({
                enabled: currentAnimationEnabled
            })
        }
        that._drawn()
    },
    _dataSourceChangedHandler: function() {
        this._requestChange(["MOSTLY_TOTAL"])
    },
    _updateContent: function(canvas) {
        var sliderMarkerOptions, indents, rangeContainerCanvas, that = this,
            chartOptions = that.option("chart"),
            seriesDataSource = that._createSeriesDataSource(chartOptions),
            isCompactMode = !(seriesDataSource && seriesDataSource.isShowChart() || that.option("background.image.url")),
            scaleOptions = prepareScaleOptions(that._getOption("scale"), seriesDataSource, that._incidentOccurred, this._getOption("containerBackgroundColor", true)),
            argTranslatorRange = calculateTranslatorRange(seriesDataSource, scaleOptions),
            tickIntervalsInfo = updateTickIntervals(scaleOptions, canvas.width, that._incidentOccurred, argTranslatorRange),
            chartThemeManager = seriesDataSource && seriesDataSource.isShowChart() && seriesDataSource.getThemeManager();
        if (chartThemeManager) {
            checkLogarithmicOptions(chartOptions && chartOptions.valueAxis, chartThemeManager.getOptions("valueAxis").logarithmBase, that._incidentOccurred)
        }
        updateScaleOptions(scaleOptions, seriesDataSource, argTranslatorRange, tickIntervalsInfo, getDateMarkerVisibilityChecker(canvas.width));
        updateTranslatorRangeInterval(argTranslatorRange, scaleOptions);
        sliderMarkerOptions = that._prepareSliderMarkersOptions(scaleOptions, canvas.width, tickIntervalsInfo);
        indents = calculateIndents(that._renderer, scaleOptions, sliderMarkerOptions, that.option("indent"), tickIntervalsInfo);
        rangeContainerCanvas = {
            left: canvas.left + indents.left,
            top: canvas.top + indents.top,
            width: canvas.left + indents.left + _max(canvas.width - indents.left - indents.right, 1),
            height: _max(!isCompactMode ? canvas.height - indents.top - indents.bottom - calculateScaleAreaHeight(that._renderer, scaleOptions, showScaleMarkers(scaleOptions), tickIntervalsInfo) : commonModule.HEIGHT_COMPACT_MODE, 0),
            right: 0,
            bottom: 0
        };
        that._axis.update(scaleOptions, isCompactMode, rangeContainerCanvas, argTranslatorRange, seriesDataSource);
        scaleOptions.minorTickInterval = scaleOptions.isEmpty ? 0 : scaleOptions.minorTickInterval;
        that._updateElements(scaleOptions, sliderMarkerOptions, isCompactMode, rangeContainerCanvas, seriesDataSource);
        if (chartThemeManager) {
            chartThemeManager.dispose()
        }
    },
    _updateElements: function(scaleOptions, sliderMarkerOptions, isCompactMode, canvas, seriesDataSource) {
        var that = this,
            behavior = that._getOption("behavior"),
            shutterOptions = that._getOption("shutter"),
            isNotSemiDiscrete = scaleOptions.type !== SEMIDISCRETE;
        shutterOptions.color = shutterOptions.color || that._getOption(CONTAINER_BACKGROUND_COLOR, true);
        that._rangeView.update(that.option("background"), that._themeManager.theme("background"), canvas, isCompactMode, behavior.animationEnabled && that._renderer.animationEnabled(), seriesDataSource);
        that._isUpdating = true;
        that._slidersController.update([canvas.top, canvas.top + canvas.height], behavior, isCompactMode, that._getOption("sliderHandle"), sliderMarkerOptions, shutterOptions, {
            minRange: isNotSemiDiscrete ? that.option("scale.minRange") : void 0,
            maxRange: isNotSemiDiscrete ? that.option("scale.maxRange") : void 0
        }, that._axis.getFullTicks(), that._getOption("selectedRangeColor", true));
        that._requestChange(["SLIDER_SELECTION"]);
        that._isUpdating = false;
        that._tracker.update(!that._axis.getTranslator().isEmptyValueRange(), behavior)
    },
    _createSeriesDataSource: function(chartOptions) {
        var seriesDataSource, that = this,
            dataSource = that._dataSourceItems(),
            scaleOptions = that._getOption("scale"),
            valueType = scaleOptions.valueType || calculateValueType(scaleOptions.startValue, scaleOptions.endValue),
            valueAxis = new axisModule.Axis({
                renderer: that._renderer,
                axisType: "xyAxes",
                drawingType: "linear"
            });
        valueAxis.updateOptions({
            isHorizontal: false,
            label: {}
        });
        if (dataSource || chartOptions && chartOptions.series) {
            chartOptions = extend({}, chartOptions, {
                theme: that.option("theme")
            });
            seriesDataSource = new seriesDataSourceModule.SeriesDataSource({
                renderer: that._renderer,
                dataSource: dataSource,
                valueType: _normalizeEnum(valueType),
                axisType: scaleOptions.type,
                chart: chartOptions,
                dataSourceField: that.option("dataSourceField"),
                incidentOccurred: that._incidentOccurred,
                categories: scaleOptions.categories,
                argumentAxis: that._axis,
                valueAxis: valueAxis
            })
        }
        return seriesDataSource
    },
    _prepareSliderMarkersOptions: function(scaleOptions, screenDelta, tickIntervalsInfo) {
        var that = this,
            minorTickInterval = tickIntervalsInfo.minorTickInterval,
            tickInterval = tickIntervalsInfo.tickInterval,
            interval = tickInterval,
            endValue = scaleOptions.endValue,
            startValue = scaleOptions.startValue,
            sliderMarkerOptions = that._getOption(SLIDER_MARKER),
            sliderMarkerUserOption = that.option(SLIDER_MARKER) || {},
            doNotSnap = !that._getOption("behavior").snapToTicks,
            isTypeDiscrete = scaleOptions.type === DISCRETE,
            isValueTypeDatetime = scaleOptions.valueType === DATETIME;
        sliderMarkerOptions.borderColor = that._getOption(CONTAINER_BACKGROUND_COLOR, true);
        if (!sliderMarkerOptions.format) {
            if (doNotSnap && _isNumber(scaleOptions.startValue)) {
                sliderMarkerOptions.format = {
                    type: "fixedPoint",
                    precision: getPrecisionForSlider(startValue, endValue, screenDelta)
                }
            }
            if (isValueTypeDatetime && !isTypeDiscrete) {
                if (_isDefined(minorTickInterval) && 0 !== minorTickInterval) {
                    interval = getMinorTickIntervalUnit(tickInterval, minorTickInterval, doNotSnap)
                }
                if (!scaleOptions.marker.visible) {
                    if (_isDefined(startValue) && _isDefined(endValue)) {
                        sliderMarkerOptions.format = formatHelper.getDateFormatByTickInterval(startValue, endValue, interval)
                    }
                } else {
                    sliderMarkerOptions.format = dateUtils.getDateFormatByTickInterval(interval)
                }
            }
            if (isValueTypeDatetime && isTypeDiscrete && tickIntervalsInfo.ticks.length) {
                sliderMarkerOptions.format = formatHelper.getDateFormatByTicks(tickIntervalsInfo.ticks)
            }
        }
        if (void 0 !== sliderMarkerUserOption.padding && void 0 === sliderMarkerUserOption.paddingLeftRight && void 0 === sliderMarkerUserOption.paddingTopBottom) {
            sliderMarkerOptions.paddingLeftRight = sliderMarkerOptions.paddingTopBottom = sliderMarkerUserOption.padding
        }
        return sliderMarkerOptions
    },
    getSelectedRange: function() {
        log("W0002", this.NAME, "getSelectedRange", "16.2", "Use the 'getValue' method instead");
        return parseValue(this.getValue())
    },
    getValue: function() {
        return parseSelectedRange(this._slidersController.getSelectedRange())
    },
    setSelectedRange: function(range) {
        log("W0002", this.NAME, "setSelectedRange", "16.2", "Use the 'setValue' method instead");
        this.setValue(parseSelectedRange(range))
    },
    setValue: function(value) {
        var current;
        if (!this._isUpdating && value) {
            this._validateRange(value[0], value[1]);
            current = this._slidersController.getSelectedRange();
            if (!current || current.startValue !== value[0] || current.endValue !== value[1]) {
                this._slidersController.setSelectedRange(parseValue(value))
            }
        }
    },
    resetSelectedRange: function() {
        this.setValue([])
    },
    _setContentSize: function() {
        this.__isResizing = 2 === this._changes.count();
        this.callBase.apply(this, arguments)
    }
});
each(["selectedRangeColor", "containerBackgroundColor", "sliderMarker", "sliderHandle", "shutter", OPTION_BACKGROUND, "behavior", "chart", "indent"], function(_, name) {
    dxRangeSelector.prototype._optionChangesMap[name] = "MOSTLY_TOTAL"
});

function prepareAxisOptions(scaleOptions, isCompactMode, height, axisPosition) {
    scaleOptions.label.overlappingBehavior = {
        mode: _isDefined(scaleOptions.useTicksAutoArrangement) ? scaleOptions.useTicksAutoArrangement ? "hide" : "none" : scaleOptions.label.overlappingBehavior
    };
    scaleOptions.marker.label.font = scaleOptions.label.font;
    scaleOptions.color = scaleOptions.marker.color = scaleOptions.tick.color;
    scaleOptions.opacity = scaleOptions.marker.opacity = scaleOptions.tick.opacity;
    scaleOptions.width = scaleOptions.marker.width = scaleOptions.tick.width;
    scaleOptions.placeholderSize = (scaleOptions.placeholderHeight || 0) + axisPosition;
    scaleOptions.argumentType = scaleOptions.valueType;
    scaleOptions.visible = isCompactMode;
    scaleOptions.minorTick.showCalculatedTicks = scaleOptions.isHorizontal = true;
    scaleOptions.calculateMinors = true;
    scaleOptions.semiDiscreteInterval = scaleOptions.minRange;
    if (!isCompactMode) {
        scaleOptions.minorTick.length = scaleOptions.tick.length = height
    }
    scaleOptions.label.indentFromAxis = scaleOptions.label.topIndent + axisPosition;
    return scaleOptions
}

function createDateMarkersEvent(scaleOptions, markerTrackers, setSelectedRange) {
    each(markerTrackers, function(_, value) {
        value.on("dxpointerdown", onPointerDown)
    });

    function onPointerDown(e) {
        var range = e.target.range,
            minRange = scaleOptions.minRange ? addInterval(range.startValue, scaleOptions.minRange) : void 0,
            maxRange = scaleOptions.maxRange ? addInterval(range.startValue, scaleOptions.maxRange) : void 0;
        if (!(minRange && minRange > range.endValue || maxRange && maxRange < range.endValue)) {
            setSelectedRange(range)
        }
    }
}

function AxisWrapper(params) {
    this._axis = new axisModule.Axis({
        renderer: params.renderer,
        axesContainerGroup: params.root,
        scaleBreaksGroup: params.scaleBreaksGroup,
        incidentOccurred: params.incidentOccurred,
        axisType: "xyAxes",
        drawingType: "linear",
        widgetClass: "dxrs",
        axisClass: "range-selector",
        isArgumentAxis: true
    });
    this._updateSelectedRangeCallback = params.updateSelectedRange
}
AxisWrapper.prototype = {
    constructor: AxisWrapper,
    dispose: function() {
        this._axis.dispose()
    },
    calculateInterval: function(value, prevValue) {
        return this._axis.calculateInterval(value, prevValue)
    },
    update: function(options, isCompactMode, canvas, businessRange, seriesDataSource) {
        var axis = this._axis;
        axis.updateOptions(prepareAxisOptions(options, isCompactMode, canvas.height, canvas.height / 2 - Math.ceil(options.width / 2)));
        axis.validate();
        axis.setBusinessRange(businessRange);
        if (void 0 !== seriesDataSource && seriesDataSource.isShowChart()) {
            axis.setMarginOptions(seriesDataSource.getMarginOptions(canvas))
        }
        axis.draw(canvas);
        axis.shift({
            left: 0,
            bottom: -canvas.height / 2 + canvas.top
        });
        if (axis.getMarkerTrackers()) {
            createDateMarkersEvent(options, axis.getMarkerTrackers(), this._updateSelectedRangeCallback)
        }
        axis.drawScaleBreaks({
            start: canvas.top,
            end: canvas.top + canvas.height
        })
    },
    getFullTicks: function() {
        return this._axis.getFullTicks()
    },
    getTranslator: function() {
        return this._axis.getTranslator()
    },
    getViewport: function() {}
};
registerComponent("dxRangeSelector", dxRangeSelector);
module.exports = dxRangeSelector;
dxRangeSelector.addPlugin(require("../core/export").plugin);
dxRangeSelector.addPlugin(require("../core/title").plugin);
dxRangeSelector.addPlugin(require("../core/loading_indicator").plugin);
dxRangeSelector.addPlugin(require("../core/data_source").plugin);
