angular.module('cgBusy',[]);

//loosely modeled after angular-promise-tracker
angular.module('cgBusy').factory('_cgBusyTrackerFactory',['$timeout','$q',function($timeout,$q){

    return function(){

        var tracker = {};
        tracker.promises = [];
        tracker.delayPromise = null;
        tracker.durationPromise = null;
        tracker.delayJustFinished = false;

        tracker.reset = function(options){
            tracker.minDuration = options.minDuration;

            tracker.promises = [];
            angular.forEach(options.promises,function(p){
                if (!p || p.$cgBusyFulfilled) {
                    return;
                }
                addPromiseLikeThing(p);
            });

            if (tracker.promises.length === 0) {
                //if we have no promises then dont do the delay or duration stuff
                return;
            }

            tracker.delayJustFinished = false;
            if (options.delay) {
                tracker.delayPromise = $timeout(function(){
                    tracker.delayPromise = null;
                    tracker.delayJustFinished = true;
                },parseInt(options.delay,10));
            }
            if (options.minDuration) {
                tracker.durationPromise = $timeout(function(){
                    tracker.durationPromise = null;
                },parseInt(options.minDuration,10) + (options.delay ? parseInt(options.delay,10) : 0));
            }            
        };

        tracker.isPromise = function(promiseThing){
            var then = promiseThing && (promiseThing.then || promiseThing.$then ||
                (promiseThing.$promise && promiseThing.$promise.then));

            return typeof then !== 'undefined';            
        };

        tracker.callThen = function(promiseThing,success,error){
            var promise;
            if (promiseThing.then || promiseThing.$then){
                promise = promiseThing;
            } else if (promiseThing.$promise){
                promise = promiseThing.$promise;
            } else if (promiseThing.denodeify){
                promise = $q.when(promiseThing);
            }
                       
            var then = (promise.then || promise.$then);

            then.call(promise,success,error);
        };

        var addPromiseLikeThing = function(promise){

            if (!tracker.isPromise(promise)) {
                throw new Error('cgBusy expects a promise (or something that has a .promise or .$promise');
            }

            if (tracker.promises.indexOf(promise) !== -1){
                return;
            }
            tracker.promises.push(promise);

            tracker.callThen(promise, function(){
                promise.$cgBusyFulfilled = true;
                if (tracker.promises.indexOf(promise) === -1) {
                    return;
                }
                tracker.promises.splice(tracker.promises.indexOf(promise),1);
            },function(){
                promise.$cgBusyFulfilled = true;
                if (tracker.promises.indexOf(promise) === -1) {
                    return;
                }
                tracker.promises.splice(tracker.promises.indexOf(promise),1);
            });
        };

        tracker.active = function(){
            if (tracker.delayPromise){
                return false;
            }

            if (!tracker.delayJustFinished){
                if (tracker.durationPromise){
                    return true;
                }
                return tracker.promises.length > 0;
            } else {
                //if both delay and min duration are set, 
                //we don't want to initiate the min duration if the 
                //promise finished before the delay was complete
                tracker.delayJustFinished = false;
                if (tracker.promises.length === 0) {
                    tracker.durationPromise = null;
                }
                return tracker.promises.length > 0;
            }
        };

        return tracker;

    };
}]);

angular.module('cgBusy').value('cgBusyDefaults',{});

angular.module('cgBusy').directive('cgBusy',['$compile','$templateCache','cgBusyDefaults','$http','_cgBusyTrackerFactory',
    function($compile,$templateCache,cgBusyDefaults,$http,_cgBusyTrackerFactory){
        return {
            restrict: 'A',
            link: function(scope, element, attrs, fn) {

                //Apply position:relative to parent element if necessary
                var position = element.css('position');
                if (position === 'static' || position === '' || typeof position === 'undefined'){
                    element.css('position','relative');
                }

                var templateElement;
                var backdropElement;
                var currentTemplate;
                var templateScope;
                var backdrop;
                var tracker = _cgBusyTrackerFactory();

                var defaults = {
                    templateUrl: 'angular-busy.html',
                    delay:0,
                    minDuration:0,
                    backdrop: true,
                    message:'Please Wait...',
                    wrapperClass: 'cg-busy cg-busy-animation'
                };

                angular.extend(defaults,cgBusyDefaults);

                scope.$watchCollection(attrs.cgBusy,function(options){

                    if (!options) {
                        options = {promise:null};
                    }

                    if (angular.isString(options)) {
                        throw new Error('Invalid value for cg-busy. cgBusy no longer accepts string ids to represent promises/trackers.');
                    }

                    //is it an array (of promises) or one promise
                    if (angular.isArray(options) || tracker.isPromise(options)) {
                        options = {promise:options};
                    }

                    options = angular.extend(angular.copy(defaults),options);

                    if (!options.templateUrl){
                        options.templateUrl = defaults.templateUrl;
                    }

                    if (!angular.isArray(options.promise)){
                        options.promise = [options.promise];
                    }

                    // options.promise = angular.isArray(options.promise) ? options.promise : [options.promise];
                    // options.message = options.message ? options.message : 'Please Wait...';
                    // options.template = options.template ? options.template : cgBusyTemplateName;
                    // options.minDuration = options.minDuration ? options.minDuration : 0;
                    // options.delay = options.delay ? options.delay : 0;

                    if (!templateScope) {
                        templateScope = scope.$new();
                    }

                    templateScope.$message = options.message;

                    if (!angular.equals(tracker.promises,options.promise)) {
                        tracker.reset({
                            promises:options.promise,
                            delay:options.delay,
                            minDuration: options.minDuration
                        });
                    }

                    templateScope.$cgBusyIsActive = function() {
                        return tracker.active();
                    };


                    if (!templateElement || currentTemplate !== options.templateUrl || backdrop !== options.backdrop) {

                        if (templateElement) {
                            templateElement.remove();
                        }
                        if (backdropElement){
                            backdropElement.remove();
                        }

                        currentTemplate = options.templateUrl;
                        backdrop = options.backdrop;

                        $http.get(currentTemplate,{cache: $templateCache}).success(function(indicatorTemplate){

                            options.backdrop = typeof options.backdrop === 'undefined' ? true : options.backdrop;

                            if (options.backdrop){
                                var backdrop = '<div class="cg-busy cg-busy-backdrop cg-busy-backdrop-animation ng-hide" ng-show="$cgBusyIsActive()"></div>';
                                backdropElement = $compile(backdrop)(templateScope);
                                element.append(backdropElement);
                            }

                            var template = '<div class="'+options.wrapperClass+' ng-hide" ng-show="$cgBusyIsActive()">' + indicatorTemplate + '</div>';
                            templateElement = $compile(template)(templateScope);

                            angular.element(templateElement.children()[0])
                                .css('position','absolute')
                                .css('top',0)
                                .css('left',0)
                                .css('right',0)
                                .css('bottom',0);
                            element.append(templateElement);

                        }).error(function(data){
                            throw new Error('Template specified for cgBusy ('+options.templateUrl+') could not be loaded. ' + data);
                        });
                    }

                },true);
            }
        };
    }
]);