/* globals define, jQuery, module, require, angular, moment */
/* jslint vars:true */

/**
 * @license angular-bootstrap-datetimepicker
 * Copyright 2016 Knight Rider Consulting, Inc. http://www.knightrider.com
 * License: MIT
 *
 * @author        Dale "Ducky" Lotts
 * @since        2013-Jul-8
 */


(function(root, factory) {
  'use strict';
  /* istanbul ignore if */
  if (typeof module !== 'undefined' && module.exports) {
    var ng = typeof angular === 'undefined' ? require('angular') : angular;
    var mt = typeof moment === 'undefined' ? require('moment') : moment;
    factory(ng, mt);
    module.exports = 'ui.bootstrap.datetimepicker';
    /* istanbul ignore next */
  } else if (typeof define === 'function' && /* istanbul ignore next */
    define.amd) {
    define(['angular', 'moment'], factory);
  } else {
    factory(root.angular, root.moment);
  }
}(this, function(angular, moment) {
  'use strict';
  angular.module('ui.bootstrap.datetimepicker', [])
    .service('dateTimePickerConfig', DateTimePickerConfigProvider)
    .service('dateTimePickerValidator', DateTimePickerValidatorService)
    .directive('datetimepicker', DatetimepickerDirective);

  DatetimepickerDirective.$inject = ['dateTimePickerConfig',
    'dateTimePickerValidator'
  ];

  function DatetimepickerDirective(defaultConfig, configurationValidator) {
    var directiveDefinition = {
      bindToController: false,
      controller: DirectiveController,
      controllerAs: 'dateTimePickerController',
      replace: true,
      require: 'ngModel',
      restrict: 'E',
      scope: {
        beforeRender: '&',
        onSetTime: '&'
      },
      templateUrl: 'templates/datetimepicker.html'
    };

    DirectiveController.$inject = ['$scope', '$element', '$attrs'];

    function DirectiveController($scope, $element, $attrs) {
      // Configuration
      var ngModelController = $element.controller('ngModel');

      var configuration = createConfiguration();
      $scope.screenReader = configuration.screenReader;

      // Behavior
      $scope.changeView = changeView;
      ngModelController.$render = $render;

      if (configuration.configureOn) {
        $scope.$on(configuration.configureOn, function() {
          configuration = createConfiguration();
          $scope.screenReader = configuration.screenReader;
          ngModelController.$render();
        });
      }

      if (configuration.renderOn) {
        $scope.$on(configuration.renderOn, ngModelController.$render);
      }

      // Implementation

      var viewToModelFactory = {
        year: yearModelFactory,

        month: monthModelFactory,

        day: dayModelFactory,

        hour: hourModelFactory,

        minute: minuteModelFactory,

        setTime: setTime
      };

      function changeView(viewName, dateObject, event) {
        if (event) {
          event.stopPropagation();
          event.preventDefault();
        }

        if (viewName && (dateObject.utcDateValue > -Infinity) && dateObject
          .selectable && viewToModelFactory[viewName]) {
          var result = viewToModelFactory[viewName](dateObject.utcDateValue);

          var weekDates = [];
          if (result.weeks) {
            for (var i = 0; i < result.weeks.length; i += 1) {
              var week = result.weeks[i];
              for (var j = 0; j < week.dates.length; j += 1) {
                var weekDate = week.dates[j];
                weekDates.push(weekDate);
              }
            }
          }

          $scope.beforeRender({
            $view: result.currentView,
            $dates: result.dates || weekDates,
            $leftDate: result.leftDate,
            $upDate: result.previousViewDate,
            $rightDate: result.rightDate
          });

          $scope.data = result;
        }
      }

      function yearModelFactory(milliseconds) {

        let selectedDate = moment.utc(milliseconds).startOf('year');
        let year = selectedDate.year();
        let startHalfCentury;
        if (year >= 0) {
          startHalfCentury = (parseInt(year / 50, 10) * 50) + 1;
        } else {
          startHalfCentury = (parseInt(year / 50, 10) * 50) - 49;
        }

        let startDate = moment.utc(startOfHalfCentury(milliseconds)).startOf(
          'year');

        let yearFormat = 'YYYY';
        let activeFormat = formatValue(ngModelController.$modelValue,
          yearFormat);
        let currentFormat = moment().format(yearFormat);

        let result = {
          'currentView': 'year',
          'nextView': configuration.minView === 'year' ? 'setTime' : 'month',
          'previousViewDate': new DateObject({
            utcDateValue: null,
            display: startHalfCentury.padLeft(4) + '  /  ' +
              (startHalfCentury + 49).padLeft(4)
          }),
          'leftDate': new DateObject({
            utcDateValue: moment.utc(startDate).subtract(49, 'year').valueOf()
          }),
          'rightDate': new DateObject({
            utcDateValue: moment.utc(startDate).add(51, 'year').valueOf()
          }),
          'dates': []
        };

        for (let i = 0; i < 50; i += 1) {
          let yearMoment = moment.utc(startDate).add(i, 'years');
          let dateValue = {
            'active': yearMoment.format(yearFormat) === activeFormat,
            'current': yearMoment.format(yearFormat) === currentFormat,
            'display': yearMoment.format(yearFormat),
            'future': yearMoment.year() > startHalfCentury + 49,
            'past': yearMoment.year() < startHalfCentury,
            'utcDateValue': yearMoment.valueOf()
          };

          result.dates.push(new DateObject(dateValue));
        }

        return result;
      }

      function monthModelFactory(milliseconds) {
        var startDate = moment.utc(milliseconds).startOf('year');
        var previousViewDate = startOfHalfCentury(milliseconds);

        var monthFormat = 'YYYY-MMM';
        var activeFormat = formatValue(ngModelController.$modelValue,
          monthFormat);
        var currentFormat = moment().format(monthFormat);

        var result = {
          'previousView': 'year',
          'currentView': 'month',
          'nextView': configuration.minView === 'month' ? 'setTime' : 'day',
          'previousViewDate': new DateObject({
            utcDateValue: previousViewDate.valueOf(),
            display: startDate.format('YYYY')
          }),
          'leftDate': new DateObject({
            utcDateValue: moment.utc(startDate).subtract(1, 'year').valueOf()
          }),
          'rightDate': new DateObject({
            utcDateValue: moment.utc(startDate).add(1, 'year').valueOf()
          }),
          'dates': []
        };

        for (var i = 0; i < 12; i += 1) {
          var monthMoment = moment.utc(startDate).add(i, 'months');
          var dateValue = {
            'active': monthMoment.format(monthFormat) === activeFormat,
            'current': monthMoment.format(monthFormat) === currentFormat,
            'display': monthMoment.format('MMM'),
            'utcDateValue': monthMoment.valueOf()
          };

          result.dates.push(new DateObject(dateValue));
        }

        return result;
      }

      function dayModelFactory(milliseconds) {
        var selectedDate = moment.utc(milliseconds);
        var startOfMonth = moment.utc(selectedDate).startOf('month');
        var previousViewDate = moment.utc(selectedDate).startOf('year');
        var endOfMonth = moment.utc(selectedDate).endOf('month');

        var startDate = moment.utc(startOfMonth).subtract(Math.abs(
          startOfMonth.weekday()), 'days');

        var dayFormat = 'YYYY-MMM-DD';
        var activeFormat = formatValue(ngModelController.$modelValue,
          dayFormat);
        var currentFormat = moment().format(dayFormat);

        var result = {
          'previousView': 'month',
          'currentView': 'day',
          'nextView': configuration.minView === 'day' ? 'setTime' : 'hour',
          'previousViewDate': new DateObject({
            utcDateValue: previousViewDate.valueOf(),
            display: startOfMonth.format('YYYY MMM')
          }),
          'leftDate': new DateObject({
            utcDateValue: moment.utc(startOfMonth).subtract(1,
              'months').valueOf()
          }),
          'rightDate': new DateObject({
            utcDateValue: moment.utc(startOfMonth).add(1, 'months').valueOf()
          }),
          'dayNames': [],
          'weeks': []
        };

        for (var dayNumber = 0; dayNumber < 7; dayNumber += 1) {
          result.dayNames.push(moment.utc().weekday(dayNumber).format('dd'));
        }

        for (var i = 0; i < 6; i += 1) {
          var week = {
            dates: []
          };
          for (var j = 0; j < 7; j += 1) {
            var dayMoment = moment.utc(startDate).add((i * 7) + j, 'days');
            var dateValue = {
              'active': dayMoment.format(dayFormat) === activeFormat,
              'current': dayMoment.format(dayFormat) === currentFormat,
              'display': dayMoment.format('D'),
              'future': dayMoment.isAfter(endOfMonth),
              'past': dayMoment.isBefore(startOfMonth),
              'utcDateValue': dayMoment.valueOf()
            };
            week.dates.push(new DateObject(dateValue));
          }
          result.weeks.push(week);
        }

        return result;
      }

      function hourModelFactory(milliseconds) {
        var selectedDate = moment.utc(milliseconds).startOf('day');
        var previousViewDate = moment.utc(selectedDate).startOf('month');

        var hourFormat = 'YYYY-MM-DD H';
        var activeFormat = formatValue(ngModelController.$modelValue,
          hourFormat);
        var currentFormat = moment().format(hourFormat);

        var result = {
          'previousView': 'day',
          'currentView': 'hour',
          'nextView': configuration.minView === 'hour' ? 'setTime' : 'minute',
          'previousViewDate': new DateObject({
            utcDateValue: previousViewDate.valueOf(),
            display: selectedDate.format('ll')
          }),
          'leftDate': new DateObject({
            utcDateValue: moment.utc(selectedDate).subtract(1, 'days')
              .valueOf()
          }),
          'rightDate': new DateObject({
            utcDateValue: moment.utc(selectedDate).add(1, 'days').valueOf()
          }),
          'dates': []
        };

        for (var i = 0; i < 24; i += 1) {
          var hourMoment = moment.utc(selectedDate).add(i, 'hours');
          var dateValue = {
            'active': hourMoment.format(hourFormat) === activeFormat,
            'current': hourMoment.format(hourFormat) === currentFormat,
            'display': hourMoment.format('LT'),
            'utcDateValue': hourMoment.valueOf()
          };

          result.dates.push(new DateObject(dateValue));
        }

        return result;
      }

      function minuteModelFactory(milliseconds) {
        var selectedDate = moment.utc(milliseconds).startOf('hour');
        var previousViewDate = moment.utc(selectedDate).startOf('day');

        var minuteFormat = 'YYYY-MM-DD H:mm';
        var activeFormat = formatValue(ngModelController.$modelValue,
          minuteFormat);
        var currentFormat = moment().format(minuteFormat);

        var result = {
          'previousView': 'hour',
          'currentView': 'minute',
          'nextView': 'setTime',
          'previousViewDate': new DateObject({
            utcDateValue: previousViewDate.valueOf(),
            display: selectedDate.format('lll')
          }),
          'leftDate': new DateObject({
            utcDateValue: moment.utc(selectedDate).subtract(1,
              'hours').valueOf()
          }),
          'rightDate': new DateObject({
            utcDateValue: moment.utc(selectedDate).add(1, 'hours').valueOf()
          }),
          'dates': []
        };

        var limit = 60 / configuration.minuteStep;

        for (var i = 0; i < limit; i += 1) {
          var hourMoment = moment.utc(selectedDate).add(i * configuration.minuteStep,
            'minute');
          var dateValue = {
            'active': hourMoment.format(minuteFormat) === activeFormat,
            'current': hourMoment.format(minuteFormat) === currentFormat,
            'display': hourMoment.format('LT'),
            'utcDateValue': hourMoment.valueOf()
          };

          result.dates.push(new DateObject(dateValue));
        }

        return result;
      }

      function setTime(milliseconds) {
        var tempDate = new Date(milliseconds);
        let yearFixer = 0;
        if (tempDate.getUTCFullYear() >= 0 && tempDate.getUTCFullYear() <
          1000) {
          yearFixer = 1000;
        }
        var newDate = new Date(tempDate.getUTCFullYear() + yearFixer,
          tempDate.getUTCMonth(),
          tempDate.getUTCDate(), tempDate.getUTCHours(), tempDate.getUTCMinutes(),
          tempDate.getUTCSeconds(), tempDate.getUTCMilliseconds());
        newDate.setUTCFullYear(newDate.getFullYear() - yearFixer);

        switch (configuration.modelType) {
        case 'Date':
          // No additional work needed
          break;
        case 'moment':
          newDate = moment([tempDate.getUTCFullYear(), tempDate.getUTCMonth(),
            tempDate.getUTCDate(), tempDate.getUTCHours(), tempDate.getUTCMinutes(),
            tempDate.getUTCSeconds(), tempDate.getUTCMilliseconds()
          ]);
          break;
        case 'milliseconds':
          newDate = milliseconds;
          break;
        default: // It is assumed that the modelType is a formatting string.
          newDate = moment([tempDate.getUTCFullYear(), tempDate.getUTCMonth(),
            tempDate.getUTCDate(), tempDate.getUTCHours(), tempDate.getUTCMinutes(),
            tempDate.getUTCSeconds(), tempDate.getUTCMilliseconds()
          ]).format(configuration.modelType);
        }

        var oldDate = ngModelController.$modelValue;
        ngModelController.$setViewValue(newDate);

        if (configuration.dropdownSelector) {
          jQuery(configuration.dropdownSelector).dropdown('toggle');
        }

        $scope.onSetTime({
          newDate: newDate,
          oldDate: oldDate
        });

        return viewToModelFactory[configuration.startView](milliseconds);
      }

      function $render() {
        $scope.changeView(configuration.startView, new DateObject({
          utcDateValue: getUTCTime(ngModelController.$viewValue)
        }));
      }

      function startOfHalfCentury(milliseconds) {
        let year = moment.utc(milliseconds).year();
        let startYear;
        if (year >= 0) {
          startYear = (parseInt(year / 50, 10) * 50) + 1;
        } else {
          startYear = (parseInt(year / 50, 10) * 50) - 49;
        }
        let result = moment.utc(milliseconds).year(startYear).startOf(
          'year');

        return result;
      }

      Number.prototype.padLeft = function(n, str) {
        return (this < 0 ? '-' : '') +
          Array(n - String(Math.abs(this)).length + 1)
            .join(str || '0') +
          (Math.abs(this));
      };

      function formatValue(timeValue, formatString) {
        if (timeValue) {
          return getMoment(timeValue).format(formatString);
        } else {
          return '';
        }
      }

      /**
       * Converts a time value into a moment.
       *
       * This function is now necessary because moment logs a warning when parsing a string without a format.
       * @param modelValue
       *  a time value in any of the supported formats (Date, moment, milliseconds, and string)
       * @returns {moment}
       *  representing the specified time value.
       */

      function getMoment(modelValue) {
        return moment(modelValue, angular.isString(modelValue) ?
          configuration.parseFormat : undefined);
      }

      /**
       * Converts a time value to UCT/GMT time.
       * @param modelValue
       *  a time value in any of the supported formats (Date, moment, milliseconds, and string)
       * @returns {number}
       *  number of milliseconds since 1/1/1970
       */

      function getUTCTime(modelValue) {
        var tempDate = new Date();
        if (modelValue) {
          var tempMoment = getMoment(modelValue);
          if (tempMoment.isValid()) {
            tempDate = tempMoment.toDate();
          } 
        }
        return tempDate.getTime() - (tempDate.getTimezoneOffset() * 60000);
      }

      function createConfiguration() {
        var directiveConfig = {};

        if ($attrs.datetimepickerConfig) {
          directiveConfig = $scope.$parent.$eval($attrs.datetimepickerConfig);
        }

        var configuration = angular.extend({}, defaultConfig,
          directiveConfig);

        configurationValidator.validate(configuration);

        return configuration;
      }
    }

    function DateObject() {
      var tempDate = new Date(arguments[0].utcDateValue);
      var localOffset = tempDate.getTimezoneOffset() * 60000;

      this.utcDateValue = tempDate.getTime();
      this.selectable = true;

      this.localDateValue = function localDateValue() {
        return this.utcDateValue + localOffset;
      };

      var validProperties = ['active', 'current', 'display', 'future',
        'past', 'selectable', 'utcDateValue'
      ];

      var constructorObject = arguments[0];

      Object.keys(constructorObject).filter(function(key) {
        return validProperties.indexOf(key) >= 0;
      }).forEach(function(key) {
        this[key] = constructorObject[key];
      }, this);
    }

    return directiveDefinition;
  }

  function DateTimePickerConfigProvider() {
    var defaultConfiguration = {
      configureOn: null,
      dropdownSelector: null,
      minuteStep: 5,
      minView: 'minute',
      modelType: 'Date',
      parseFormat: 'YYYY-MM-DDTHH:mm:ss.SSSZZ',
      renderOn: null,
      startView: 'day'
    };

    var defaultLocalization = {
      'bg': {
        previous: 'предишна',
        next: 'следваща'
      },
      'ca': {
        previous: 'anterior',
        next: 'següent'
      },
      'da': {
        previous: 'forrige',
        next: 'næste'
      },
      'de': {
        previous: 'vorige',
        next: 'weiter'
      },
      'en-au': {
        previous: 'previous',
        next: 'next'
      },
      'en-gb': {
        previous: 'previous',
        next: 'next'
      },
      'en': {
        previous: 'previous',
        next: 'next'
      },
      'es-us': {
        previous: 'atrás',
        next: 'siguiente'
      },
      'es': {
        previous: 'atrás',
        next: 'siguiente'
      },
      'fi': {
        previous: 'edellinen',
        next: 'seuraava'
      },
      'fr': {
        previous: 'précédent',
        next: 'suivant'
      },
      'hu': {
        previous: 'előző',
        next: 'következő'
      },
      'it': {
        previous: 'precedente',
        next: 'successivo'
      },
      'ja': {
        previous: '前へ',
        next: '次へ'
      },
      'ml': {
        previous: 'മുൻപുള്ളത്',
        next: 'അടുത്തത്'
      },
      'nl': {
        previous: 'vorige',
        next: 'volgende'
      },
      'pl': {
        previous: 'poprzednia',
        next: 'następna'
      },
      'pt-br': {
        previous: 'anteriores',
        next: 'próximos'
      },
      'pt': {
        previous: 'anterior',
        next: 'próximo'
      },
      'ro': {
        previous: 'anterior',
        next: 'următor'
      },
      'ru': {
        previous: 'предыдущая',
        next: 'следующая'
      },
      'sk': {
        previous: 'predošlá',
        next: 'ďalšia'
      },
      'sv': {
        previous: 'föregående',
        next: 'nästa'
      },
      'tr': {
        previous: 'önceki',
        next: 'sonraki'
      },
      'uk': {
        previous: 'назад',
        next: 'далі'
      },
      'zh-cn': {
        previous: '上一页',
        next: '下一页'
      },
      'zh-tw': {
        previous: '上一頁',
        next: '下一頁'
      }
    };

    var screenReader = defaultLocalization[moment.locale().toLowerCase()];

    return angular.extend({}, defaultConfiguration, {
      screenReader: screenReader
    });
  }

  DateTimePickerValidatorService.$inject = ['$log'];

  function DateTimePickerValidatorService($log) {
    return {
      validate: validator
    };

    function validator(configuration) {
      var validOptions = [
        'configureOn',
        'dropdownSelector',
        'minuteStep',
        'minView',
        'modelType',
        'parseFormat',
        'renderOn',
        'startView',
        'screenReader'
      ];

      var invalidOptions = Object.keys(configuration).filter(function(key) {
        return (validOptions.indexOf(key) < 0);
      });

      if (invalidOptions.length) {
        throw new Error('Invalid options: ' + invalidOptions.join(', '));
      }

      // Order of the elements in the validViews array is significant.
      var validViews = ['minute', 'hour', 'day', 'month', 'year'];

      if (validViews.indexOf(configuration.startView) < 0) {
        throw new Error('invalid startView value: ' + configuration.startView);
      }

      if (validViews.indexOf(configuration.minView) < 0) {
        throw new Error('invalid minView value: ' + configuration.minView);
      }

      if (validViews.indexOf(configuration.minView) > validViews.indexOf(
        configuration.startView)) {
        throw new Error('startView must be greater than minView');
      }

      if (!angular.isNumber(configuration.minuteStep)) {
        throw new Error('minuteStep must be numeric');
      }
      if (configuration.minuteStep <= 0 || configuration.minuteStep >= 60) {
        throw new Error(
          'minuteStep must be greater than zero and less than 60');
      }
      if (configuration.configureOn !== null && !angular.isString(
        configuration.configureOn)) {
        throw new Error('configureOn must be a string');
      }
      if (configuration.configureOn !== null && configuration.configureOn.length <
        1) {
        throw new Error('configureOn must not be an empty string');
      }
      if (configuration.renderOn !== null && !angular.isString(
        configuration.renderOn)) {
        throw new Error('renderOn must be a string');
      }
      if (configuration.renderOn !== null && configuration.renderOn.length <
        1) {
        throw new Error('renderOn must not be an empty string');
      }
      if (configuration.modelType !== null && !angular.isString(
        configuration.modelType)) {
        throw new Error('modelType must be a string');
      }
      if (configuration.modelType !== null && configuration.modelType.length <
        1) {
        throw new Error('modelType must not be an empty string');
      }
      if (configuration.modelType !== 'Date' && configuration.modelType !==
        'moment' && configuration.modelType !== 'milliseconds') {
        // modelType contains string format, overriding parseFormat with modelType
        configuration.parseFormat = configuration.modelType;
      }
      if (configuration.dropdownSelector !== null && !angular.isString(
        configuration.dropdownSelector)) {
        throw new Error('dropdownSelector must be a string');
      }

      /* istanbul ignore next */
      if (configuration.dropdownSelector !== null && ((typeof jQuery ===
          'undefined') || (typeof jQuery().dropdown !== 'function'))) {
        $log.error(
          'Please DO NOT specify the dropdownSelector option unless you are using jQuery AND Bootstrap.js. ' +
          'Please include jQuery AND Bootstrap.js, or write code to close the dropdown in the on-set-time callback. \n\n' +
          'The dropdownSelector configuration option is being removed because it will not function properly.'
        );
        delete configuration.dropdownSelector;
      }
    }
  }
})); // eslint-disable-line semi
