/*****************************************************************************
 * Open MCT, Copyright (c) 2014-2018, United States Government
 * as represented by the Administrator of the National Aeronautics and Space
 * Administration. All rights reserved.
 *
 * Open MCT is licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 * Open MCT includes source code licensed under additional open source
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 * this source code distribution or the Licensing information page available
 * at runtime from the About dialog for additional information.
 *****************************************************************************/

define([

], function () {

    var TICK_SPACING_PX = 150;

    /* format number as percent; 0.0-1.0 to "0%"-"100%" */
    function toPercent(p) {
        return (100 * p) + "%";
    }

    function clamp(value, low, high) {
        return Math.max(low, Math.min(high, value));
    }

    function copyBounds(bounds) {
        return {
            start: bounds.start,
            end: bounds.end
        };
    }

    /**
     * Controller used by the `time-controller` template.
     * @memberof platform/commonUI/general
     * @constructor
     * @param $scope the Angular scope for this controller
     * @param {FormatService} formatService the service to user to format
     *        domain values
     * @param {string} defaultFormat the format to request when no
     *        format has been otherwise specified
     * @param {Function} now a function to return current system time
     */
    function TimeRangeController($scope, $timeout, formatService, defaultFormat, now) {
        this.$scope = $scope;
        this.formatService = formatService;
        this.defaultFormat = defaultFormat;
        this.now = now;

        this.tickCount = 2;
        this.innerMinimumSpan = 1000; // 1 second
        this.outerMinimumSpan = 1000; // 1 second
        this.initialDragValue = undefined;
        this.formatter = formatService.getFormat(defaultFormat);
        this.formStartChanged = false;
        this.formEndChanged = false;
        this.$timeout = $timeout;

        this.$scope.ticks = [];

        this.updateViewFromModel(this.$scope.ngModel);
        this.updateFormModel();

        [
            'updateViewFromModel',
            'updateSpanWidth',
            'updateOuterStart',
            'updateOuterEnd',
            'updateFormat',
            'validateStart',
            'validateEnd',
            'onFormStartChange',
            'onFormEndChange'
        ].forEach(function (boundFn) {
            this[boundFn] = this[boundFn].bind(this);
        }, this);

        this.$scope.$watchCollection("ngModel", this.updateViewFromModel);
        this.$scope.$watch("spanWidth", this.updateSpanWidth);
        this.$scope.$watch("ngModel.outer.start", this.updateOuterStart);
        this.$scope.$watch("ngModel.outer.end", this.updateOuterEnd);
        this.$scope.$watch("parameters.format", this.updateFormat);
        this.$scope.$watch("formModel.start", this.onFormStartChange);
        this.$scope.$watch("formModel.end", this.onFormEndChange);
    }

    TimeRangeController.prototype.formatTimestamp = function (ts) {
        return this.formatter.format(ts);
    };

    TimeRangeController.prototype.updateTicks = function () {
        var i, p, ts, start, end, span;
        end = this.$scope.ngModel.outer.end;
        start = this.$scope.ngModel.outer.start;
        span = end - start;
        this.$scope.ticks = [];
        for (i = 0; i < this.tickCount; i += 1) {
            p = i / (this.tickCount - 1);
            ts = p * span + start;
            this.$scope.ticks.push(this.formatTimestamp(ts));
        }
    };

    TimeRangeController.prototype.updateSpanWidth = function (w) {
        this.tickCount = Math.max(Math.floor(w / TICK_SPACING_PX), 2);
        this.updateTicks();
    };

    TimeRangeController.prototype.updateViewForInnerSpanFromModel = function (
        ngModel
    ) {
        var span = ngModel.outer.end - ngModel.outer.start;

        // Expose readable dates for the knobs
        this.$scope.startInnerText = this.formatTimestamp(ngModel.inner.start);
        this.$scope.endInnerText = this.formatTimestamp(ngModel.inner.end);

        // And positions for the knobs
        this.$scope.startInnerPct =
            toPercent((ngModel.inner.start - ngModel.outer.start) / span);
        this.$scope.endInnerPct =
            toPercent((ngModel.outer.end - ngModel.inner.end) / span);
    };

    TimeRangeController.prototype.defaultBounds = function () {
        var t = this.now();
        return {
            start: t - 24 * 3600 * 1000, // One day
            end: t
        };
    };


    TimeRangeController.prototype.updateViewFromModel = function (ngModel) {
        ngModel = ngModel || {};
        ngModel.outer = ngModel.outer || this.defaultBounds();
        ngModel.inner = ngModel.inner || copyBounds(ngModel.outer);

        // Stick it back is scope (in case we just set defaults)
        this.$scope.ngModel = ngModel;

        this.updateViewForInnerSpanFromModel(ngModel);
        this.updateTicks();
    };

    TimeRangeController.prototype.startLeftDrag = function () {
        this.initialDragValue = this.$scope.ngModel.inner.start;
    };

    TimeRangeController.prototype.startRightDrag = function () {
        this.initialDragValue = this.$scope.ngModel.inner.end;
    };

    TimeRangeController.prototype.startMiddleDrag = function () {
        this.initialDragValue = {
            start: this.$scope.ngModel.inner.start,
            end: this.$scope.ngModel.inner.end
        };
    };

    TimeRangeController.prototype.toMillis = function (pixels) {
        var span =
            this.$scope.ngModel.outer.end - this.$scope.ngModel.outer.start;
        return (pixels / this.$scope.spanWidth) * span;
    };

    TimeRangeController.prototype.leftDrag = function (pixels) {
        var delta = this.toMillis(pixels);
        this.$scope.ngModel.inner.start = clamp(
            this.initialDragValue + delta,
            this.$scope.ngModel.outer.start,
            this.$scope.ngModel.inner.end - this.innerMinimumSpan
        );
        this.updateViewFromModel(this.$scope.ngModel);
    };

    TimeRangeController.prototype.rightDrag = function (pixels) {
        var delta = this.toMillis(pixels);
        this.$scope.ngModel.inner.end = clamp(
            this.initialDragValue + delta,
            this.$scope.ngModel.inner.start + this.innerMinimumSpan,
            this.$scope.ngModel.outer.end
        );
        this.updateViewFromModel(this.$scope.ngModel);
    };

    TimeRangeController.prototype.middleDrag = function (pixels) {
        var delta = this.toMillis(pixels),
            edge = delta < 0 ? 'start' : 'end',
            opposite = delta < 0 ? 'end' : 'start';

        // Adjust the position of the edge in the direction of drag
        this.$scope.ngModel.inner[edge] = clamp(
            this.initialDragValue[edge] + delta,
            this.$scope.ngModel.outer.start,
            this.$scope.ngModel.outer.end
        );
        // Adjust opposite knob to maintain span
        this.$scope.ngModel.inner[opposite] =
            this.$scope.ngModel.inner[edge] +
            this.initialDragValue[opposite] -
            this.initialDragValue[edge];

        this.updateViewFromModel(this.$scope.ngModel);
    };

    TimeRangeController.prototype.updateFormModel = function () {
        this.$scope.formModel = {
            start: ((this.$scope.ngModel || {}).outer || {}).start,
            end: ((this.$scope.ngModel || {}).outer || {}).end
        };
    };

    TimeRangeController.prototype.updateOuterStart = function () {
        var ngModel = this.$scope.ngModel;

        ngModel.inner.start =
            Math.max(ngModel.outer.start, ngModel.inner.start);
        ngModel.inner.end = Math.max(
            ngModel.inner.start + this.innerMinimumSpan,
            ngModel.inner.end
        );

        this.updateFormModel();
        this.updateViewForInnerSpanFromModel(ngModel);
        this.updateTicks();
    };

    TimeRangeController.prototype.updateOuterEnd = function () {
        var ngModel = this.$scope.ngModel;

        ngModel.inner.end =
            Math.min(ngModel.outer.end, ngModel.inner.end);
        ngModel.inner.start = Math.min(
            ngModel.inner.end - this.innerMinimumSpan,
            ngModel.inner.start
        );

        this.updateFormModel();
        this.updateViewForInnerSpanFromModel(ngModel);
        this.updateTicks();
    };

    TimeRangeController.prototype.updateFormat = function (key) {
        this.formatter = this.formatService.getFormat(key || this.defaultFormat);
        this.updateViewForInnerSpanFromModel(this.$scope.ngModel);
        this.updateTicks();
    };

    TimeRangeController.prototype.updateBoundsFromForm = function () {
        var self = this;

        //Allow Angular to trigger watches and determine whether values have changed.
        this.$timeout(function () {
            if (self.formStartChanged) {
                self.$scope.ngModel.outer.start =
                    self.$scope.ngModel.inner.start =
                        self.$scope.formModel.start;
                self.formStartChanged = false;
            }
            if (self.formEndChanged) {
                self.$scope.ngModel.outer.end =
                    self.$scope.ngModel.inner.end =
                        self.$scope.formModel.end;
                self.formEndChanged = false;
            }
        });
    };

    TimeRangeController.prototype.onFormStartChange = function (
        newValue,
        oldValue
    ) {
        if (!this.formStartChanged && newValue !== oldValue) {
            this.formStartChanged = true;
        }
    };

    TimeRangeController.prototype.onFormEndChange = function (
        newValue,
        oldValue
    ) {
        if (!this.formEndChanged && newValue !== oldValue) {
            this.formEndChanged = true;
        }
    };

    TimeRangeController.prototype.validateStart = function (startValue) {
        return startValue <=
            this.$scope.formModel.end - this.outerMinimumSpan;
    };

    TimeRangeController.prototype.validateEnd = function (endValue) {
        return endValue >=
            this.$scope.formModel.start + this.outerMinimumSpan;
    };

    return TimeRangeController;
});
