/* angular-moment.js / v0.8.2 / (c) 2013, 2014 Uri Shaked / MIT Licence */

'format global'; /* global define */
'deps angular';
'deps moment';

(function () {
    'use strict';

    function angularMoment(angular, moment) {

        /**
         * @ngdoc overview
         * @name angularMoment
         *
         * @description
         * angularMoment module provides moment.js functionality for angular.js apps.
         */
        return angular.module('angularMoment', [])

        /**
         * @ngdoc object
         * @name angularMoment.config:angularMomentConfig
         *
         * @description
         * Common configuration of the angularMoment module
         */
            .constant('angularMomentConfig', {
                /**
                 * @ngdoc property
                 * @name angularMoment.config.angularMomentConfig#preprocess
                 * @propertyOf angularMoment.config:angularMomentConfig
                 * @returns {string} The default preprocessor to apply
                 *
                 * @description
                 * Defines a default preprocessor to apply (e.g. 'unix', 'etc', ...). The default value is null,
                 * i.e. no preprocessor will be applied.
                 */
                preprocess: null, // e.g. 'unix', 'utc', ...

                /**
                 * @ngdoc property
                 * @name angularMoment.config.angularMomentConfig#timezone
                 * @propertyOf angularMoment.config:angularMomentConfig
                 * @returns {string} The default timezone
                 *
                 * @description
                 * The default timezone (e.g. 'Europe/London'). Empty string by default (does not apply
                 * any timezone shift).
                 */
                timezone: '',

                /**
                 * @ngdoc property
                 * @name angularMoment.config.angularMomentConfig#format
                 * @propertyOf angularMoment.config:angularMomentConfig
                 * @returns {string} The pre-conversion format of the date
                 *
                 * @description
                 * Specify the format of the input date. Essentially it's a
                 * default and saves you from specifying a format in every
                 * element. Overridden by element attr. Null by default.
                 */
                format: null
            })

        /**
         * @ngdoc object
         * @name angularMoment.object:moment
         *
         * @description
         * moment global (as provided by the moment.js library)
         */
            .constant('moment', moment)

        /**
         * @ngdoc object
         * @name angularMoment.config:amTimeAgoConfig
         * @module angularMoment
         *
         * @description
         * configuration specific to the amTimeAgo directive
         */
            .constant('amTimeAgoConfig', {
                /**
                 * @ngdoc property
                 * @name angularMoment.config.amTimeAgoConfig#withoutSuffix
                 * @propertyOf angularMoment.config:amTimeAgoConfig
                 * @returns {boolean} Whether to include a suffix in am-time-ago directive
                 *
                 * @description
                 * Defaults to false.
                 */
                withoutSuffix: false,

                /**
                 * @ngdoc property
                 * @name angularMoment.config.amTimeAgoConfig#serverTime
                 * @propertyOf angularMoment.config:amTimeAgoConfig
                 * @returns {number} Server time in milliseconds since the epoch
                 *
                 * @description
                 * If set, time ago will be calculated relative to the given value.
                 * If null, local time will be used. Defaults to null.
                 */
                serverTime: null,

                /**
                 * @ngdoc property
                 * @name angularMoment.config.amTimeAgoConfig#format
                 * @propertyOf angularMoment.config:amTimeAgoConfig
                 * @returns {string} The format of the date to be displayed in the title of the element. If null,
                 * 		the directive set the title of the element.
                 *
                 * @description
                 * Specify the format of the date when displayed. null by default.
                 */
                titleFormat: null
            })

        /**
         * @ngdoc directive
         * @name angularMoment.directive:amTimeAgo
         * @module angularMoment
         *
         * @restrict A
         */
            .directive('amTimeAgo', ['$window', 'moment', 'amMoment', 'amTimeAgoConfig', 'angularMomentConfig', function ($window, moment, amMoment, amTimeAgoConfig, angularMomentConfig) {

                return function (scope, element, attr) {
                    var activeTimeout = null;
                    var currentValue;
                    var currentFormat = angularMomentConfig.format;
                    var withoutSuffix = amTimeAgoConfig.withoutSuffix;
                    var titleFormat = amTimeAgoConfig.titleFormat;
                    var localDate = new Date().getTime();
                    var preprocess = angularMomentConfig.preprocess;
                    var modelName = attr.amTimeAgo.replace(/^::/, '');
                    var isBindOnce = (attr.amTimeAgo.indexOf('::') === 0);
                    var isTimeElement = ('TIME' === element[0].nodeName.toUpperCase());
                    var unwatchChanges;

                    function getNow() {
                        var now;
                        if (amTimeAgoConfig.serverTime) {
                            var localNow = new Date().getTime();
                            var nowMillis = localNow - localDate + amTimeAgoConfig.serverTime;
                            now = moment(nowMillis);
                        }
                        else {
                            now = moment();
                        }
                        return now;
                    }

                    function cancelTimer() {
                        if (activeTimeout) {
                            $window.clearTimeout(activeTimeout);
                            activeTimeout = null;
                        }
                    }

                    function updateTime(momentInstance) {
                        element.text(momentInstance.from(getNow(), withoutSuffix));

                        if (titleFormat && !element.attr('title')) {
                            element.attr('title', momentInstance.local().format(titleFormat));
                        }

                        if (!isBindOnce) {

                            var howOld = Math.abs(getNow().diff(momentInstance, 'minute'));
                            var secondsUntilUpdate = 3600;
                            if (howOld < 1) {
                                secondsUntilUpdate = 1;
                            } else if (howOld < 60) {
                                secondsUntilUpdate = 30;
                            } else if (howOld < 180) {
                                secondsUntilUpdate = 300;
                            }

                            activeTimeout = $window.setTimeout(function () {
                                updateTime(momentInstance);
                            }, secondsUntilUpdate * 1000);
                        }
                    }

                    function updateDateTimeAttr(value) {
                        if (isTimeElement) {
                            element.attr('datetime', value);
                        }
                    }

                    function updateMoment() {
                        cancelTimer();
                        if (currentValue) {
                            var momentValue = amMoment.preprocessDate(currentValue, preprocess, currentFormat);
                            updateTime(momentValue);
                            updateDateTimeAttr(momentValue.toISOString());
                        }
                    }

                    unwatchChanges = scope.$watch(modelName, function (value) {
                        if ((typeof value === 'undefined') || (value === null) || (value === '')) {
                            cancelTimer();
                            if (currentValue) {
                                element.text('');
                                updateDateTimeAttr('');
                                currentValue = null;
                            }
                            return;
                        }

                        currentValue = value;
                        updateMoment();

                        if (value !== undefined && isBindOnce) {
                            unwatchChanges();
                        }
                    });

                    if (angular.isDefined(attr.amWithoutSuffix)) {
                        scope.$watch(attr.amWithoutSuffix, function (value) {
                            if (typeof value === 'boolean') {
                                withoutSuffix = value;
                                updateMoment();
                            } else {
                                withoutSuffix = amTimeAgoConfig.withoutSuffix;
                            }
                        });
                    }

                    attr.$observe('amFormat', function (format) {
                        if (typeof format !== 'undefined') {
                            currentFormat = format;
                            updateMoment();
                        }
                    });

                    attr.$observe('amPreprocess', function (newValue) {
                        preprocess = newValue;
                        updateMoment();
                    });

                    scope.$on('$destroy', function () {
                        cancelTimer();
                    });

                    scope.$on('amMoment:localeChanged', function () {
                        updateMoment();
                    });
                };
            }])

        /**
         * @ngdoc service
         * @name angularMoment.service.amMoment
         * @module angularMoment
         */
            .service('amMoment', ['moment', '$rootScope', '$log', 'angularMomentConfig', function (moment, $rootScope, $log, angularMomentConfig) {
                var that = this;
                /**
                 * @ngdoc property
                 * @name angularMoment:amMoment#preprocessors
                 * @module angularMoment
                 *
                 * @description
                 * Defines the preprocessors for the preprocessDate method. By default, the following preprocessors
                 * are defined: utc, unix.
                 */
                this.preprocessors = {
                    utc: moment.utc,
                    unix: moment.unix
                };

                /**
                 * @ngdoc function
                 * @name angularMoment.service.amMoment#changeLocale
                 * @methodOf angularMoment.service.amMoment
                 *
                 * @description
                 * Changes the locale for moment.js and updates all the am-time-ago directive instances
                 * with the new locale. Also broadcasts a `amMoment:localeChanged` event on $rootScope.
                 *
                 * @param {string} locale 2-letter language code (e.g. en, es, ru, etc.)
                 */
                this.changeLocale = function (locale) {
                    var result = (moment.locale || moment.lang)(locale);
                    if (angular.isDefined(locale)) {
                        $rootScope.$broadcast('amMoment:localeChanged');

                        // The following event is deprecated and will be removed in an upcoming
                        // major release.
                        $rootScope.$broadcast('amMoment:languageChange');
                    }
                    return result;
                };

                /**
                 * @ngdoc function
                 * @name angularMoment.service.amMoment#changeLanguage
                 * @methodOf angularMoment.service.amMoment
                 * @deprecated Please use changeLocale() instead.
                 *
                 * @description
                 * Deprecated. Please use changeLocale() instead.
                 */
                this.changeLanguage = function (lang) {
                    $log.warn('angular-moment: Usage of amMoment.changeLanguage() is deprecated. Please use changeLocale()');
                    return that.changeLocale(lang);
                };

                /**
                 * @ngdoc function
                 * @name angularMoment.service.amMoment#preprocessDate
                 * @methodOf angularMoment.service.amMoment
                 *
                 * @description
                 * Preprocess a given value and convert it into a Moment instance appropriate for use in the
                 * am-time-ago directive and the filters.
                 *
                 * @param {*} value The value to be preprocessed
                 * @param {string} preprocess The name of the preprocessor the apply (e.g. utc, unix)
                 * @param {string=} format Specifies how to parse the value (see {@link http://momentjs.com/docs/#/parsing/string-format/})
                 * @return {Moment} A value that can be parsed by the moment library
                 */
                this.preprocessDate = function (value, preprocess, format) {
                    if (angular.isUndefined(preprocess)) {
                        preprocess = angularMomentConfig.preprocess;
                    }
                    if (this.preprocessors[preprocess]) {
                        return this.preprocessors[preprocess](value, format);
                    }
                    if (preprocess) {
                        $log.warn('angular-moment: Ignoring unsupported value for preprocess: ' + preprocess);
                    }
                    if (!isNaN(parseFloat(value)) && isFinite(value)) {
                        // Milliseconds since the epoch
                        return moment(parseInt(value, 10));
                    }
                    // else just returns the value as-is.
                    return moment(value, format);
                };

                /**
                 * @ngdoc function
                 * @name angularMoment.service.amMoment#applyTimezone
                 * @methodOf angularMoment.service.amMoment
                 *
                 * @description
                 * Apply a timezone onto a given moment object - if moment-timezone.js is included
                 * Otherwise, it'll not apply any timezone shift.
                 *
                 * @param {Moment} aMoment a moment() instance to apply the timezone shift to
                 * @returns {Moment} The given moment with the timezone shift applied
                 */
                this.applyTimezone = function (aMoment) {
                    var timezone = angularMomentConfig.timezone;
                    if (aMoment && timezone) {
                        if (aMoment.tz) {
                            aMoment = aMoment.tz(timezone);
                        } else {
                            $log.warn('angular-moment: timezone specified but moment.tz() is undefined. Did you forget to include moment-timezone.js?');
                        }
                    }
                    return aMoment;
                };
            }])

        /**
         * @ngdoc filter
         * @name angularMoment.filter:amCalendar
         * @module angularMoment
         */
            .filter('amCalendar', ['moment', 'amMoment', function (moment, amMoment) {
                return function (value, preprocess) {
                    if (typeof value === 'undefined' || value === null) {
                        return '';
                    }

                    value = amMoment.preprocessDate(value, preprocess);
                    var date = moment(value);
                    if (!date.isValid()) {
                        return '';
                    }

                    return amMoment.applyTimezone(date).calendar();
                };
            }])

        /**
         * @ngdoc filter
         * @name angularMoment.filter:amDateFormat
         * @module angularMoment
         * @function
         */
            .filter('amDateFormat', ['moment', 'amMoment', function (moment, amMoment) {
                return function (value, format, preprocess) {
                    if (typeof value === 'undefined' || value === null) {
                        return '';
                    }

                    value = amMoment.preprocessDate(value, preprocess);
                    var date = moment(value);
                    if (!date.isValid()) {
                        return '';
                    }

                    return amMoment.applyTimezone(date).format(format);
                };
            }])

        /**
         * @ngdoc filter
         * @name angularMoment.filter:amDurationFormat
         * @module angularMoment
         * @function
         */
            .filter('amDurationFormat', ['moment', function (moment) {
                return function (value, format, suffix) {
                    if (typeof value === 'undefined' || value === null) {
                        return '';
                    }

                    return moment.duration(value, format).humanize(suffix);
                };
            }]);
    }

    angularMoment(angular, window["moment"]);
})(); 