/**
 * @description
 * var app = angular.module('App', ['flow.provider'], function(flowFactoryProvider){
 *    flowFactoryProvider.defaults = {target: '/'};
 * });
 * @name flowFactoryProvider
 */
angular.module('flow.provider', [])
.provider('flowFactory', function() {
  'use strict';
  /**
   * Define the default properties for flow.js
   * @name flowFactoryProvider.defaults
   * @type {Object}
   */
  this.defaults = {};

  /**
   * Flow, MaybeFlow or NotFlow
   * @name flowFactoryProvider.factory
   * @type {function}
   * @return {Flow}
   */
  this.factory = function (options) {
    return new Flow(options);
  };

  /**
   * Define the default events
   * @name flowFactoryProvider.events
   * @type {Array}
   * @private
   */
  this.events = [];

  /**
   * Add default events
   * @name flowFactoryProvider.on
   * @function
   * @param {string} event
   * @param {Function} callback
   */
  this.on = function (event, callback) {
    this.events.push([event, callback]);
  };

  this.$get = function() {
    var fn = this.factory;
    var defaults = this.defaults;
    var events = this.events;
    return {
      'create': function(opts) {
        // combine default options with global options and options
        var flow = fn(angular.extend({}, defaults, opts));
        angular.forEach(events, function (event) {
          flow.on(event[0], event[1]);
        });
        return flow;
      }
    };
  };
});
angular.module('flow.init', ['flow.provider'])
  .controller('flowCtrl', ['$scope', '$attrs', '$parse', 'flowFactory',
  function ($scope, $attrs, $parse, flowFactory) {

    var options = angular.extend({}, $scope.$eval($attrs.flowInit));

    // use existing flow object or create a new one
    var flow  = $scope.$eval($attrs.flowObject) || flowFactory.create(options);

    var catchAllHandler = function(eventName){
      var args = Array.prototype.slice.call(arguments);
      args.shift();
      var event = $scope.$broadcast.apply($scope, ['flow::' + eventName, flow].concat(args));
      if ({
        'progress':1, 'filesSubmitted':1, 'fileSuccess': 1, 'fileError': 1, 'complete': 1
      }[eventName]) {
        $scope.$applyAsync();
      }
      if (event.defaultPrevented) {
        return false;
      }
    };

    flow.on('catchAll', catchAllHandler);
    $scope.$on('$destroy', function(){
        flow.off('catchAll', catchAllHandler);
    });

    $scope.$flow = flow;

    if ($attrs.hasOwnProperty('flowName')) {
      $parse($attrs.flowName).assign($scope, flow);
      $scope.$on('$destroy', function () {
        $parse($attrs.flowName).assign($scope);
      });
    }
  }])
  .directive('flowInit', [function() {
    return {
      scope: true,
      controller: 'flowCtrl'
    };
  }]);
angular.module('flow.btn', ['flow.init'])
.directive('flowBtn', [function() {
  return {
    'restrict': 'EA',
    'scope': false,
    'require': '^flowInit',
    'link': function(scope, element, attrs) {
      var isDirectory = attrs.hasOwnProperty('flowDirectory');
      var isSingleFile = attrs.hasOwnProperty('flowSingleFile');
      var inputAttrs = attrs.hasOwnProperty('flowAttrs') && scope.$eval(attrs.flowAttrs);
      scope.$flow.assignBrowse(element, isDirectory, isSingleFile, inputAttrs);
    }
  };
}]);
angular.module('flow.dragEvents', ['flow.init'])
/**
 * @name flowPreventDrop
 * Prevent loading files then dropped on element
 */
  .directive('flowPreventDrop', function() {
    return {
      'scope': false,
      'link': function(scope, element, attrs) {
        element.bind('drop dragover', function (event) {
          event.preventDefault();
        });
      }
    };
  })
/**
 * @name flowDragEnter
 * executes `flowDragEnter` and `flowDragLeave` events
 */
  .directive('flowDragEnter', ['$timeout', function($timeout) {
    return {
      'scope': false,
      'link': function(scope, element, attrs) {
        var promise;
        var enter = false;
        element.bind('dragover', function (event) {
          if (!isFileDrag(event)) {
            return ;
          }
          if (!enter) {
            scope.$apply(attrs.flowDragEnter);
            enter = true;
          }
          $timeout.cancel(promise);
          event.preventDefault();
        });
        element.bind('dragleave drop', function (event) {
          $timeout.cancel(promise);
          promise = $timeout(function () {
            scope.$eval(attrs.flowDragLeave);
            promise = null;
            enter = false;
          }, 100);
        });
        function isFileDrag(dragEvent) {
          var fileDrag = false;
          var dataTransfer = dragEvent.dataTransfer || dragEvent.originalEvent.dataTransfer;
          angular.forEach(dataTransfer && dataTransfer.types, function(val) {
            if (val === 'Files') {
              fileDrag = true;
            }
          });
          return fileDrag;
        }
      }
    };
  }]);

angular.module('flow.drop', ['flow.init'])
.directive('flowDrop', function() {
  return {
    'scope': false,
    'require': '^flowInit',
    'link': function(scope, element, attrs) {
      if (attrs.flowDropEnabled) {
        scope.$watch(attrs.flowDropEnabled, function (value) {
          if (value) {
            assignDrop();
          } else {
            unAssignDrop();
          }
        });
      } else {
        assignDrop();
      }
      function assignDrop() {
        scope.$flow.assignDrop(element);
      }
      function unAssignDrop() {
        scope.$flow.unAssignDrop(element);
      }
    }
  };
});

!function (angular) {'use strict';
  var module = angular.module('flow.events', ['flow.init']);
  var events = {
    fileSuccess: ['$file', '$message'],
    fileProgress: ['$file'],
    fileAdded: ['$file', '$event'],
    filesAdded: ['$files', '$event'],
    filesSubmitted: ['$files', '$event'],
    fileRetry: ['$file'],
    fileRemoved: ['$file'],
    fileError: ['$file', '$message'],
    uploadStart: [],
    complete: [],
    progress: [],
    error: ['$message', '$file']
  };

  angular.forEach(events, function (eventArgs, eventName) {
    var name = 'flow' + capitaliseFirstLetter(eventName);
    if (name == 'flowUploadStart') {
      name = 'flowUploadStarted';// event alias
    }
    module.directive(name, [function() {
      return {
        require: '^flowInit',
        controller: ['$scope', '$attrs', function ($scope, $attrs) {
          $scope.$on('flow::' + eventName, function () {
            var funcArgs = Array.prototype.slice.call(arguments);
            var event = funcArgs.shift();// remove angular event
            // remove flow object and ignore event if it is from parent directive
            if ($scope.$flow !== funcArgs.shift()) {
              return ;
            }
            var args = {};
            angular.forEach(eventArgs, function(value, key) {
              args[value] = funcArgs[key];
            });
            if ($scope.$eval($attrs[name], args) === false) {
              event.preventDefault();
            }
          });
        }]
      };
    }]);
  });

  function capitaliseFirstLetter(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
  }
}(angular);

angular.module('flow.img', ['flow.init'])
.directive('flowImg', [function() {
  return {
    'scope': false,
    'require': '^flowInit',
    'link': function(scope, element, attrs) {
      var file = attrs.flowImg;
      scope.$watch(file, function (file) {
        if (!file) {
          return ;
        }
        var fileReader = new FileReader();
        fileReader.readAsDataURL(file.file);
        fileReader.onload = function (event) {
          scope.$apply(function () {
            attrs.$set('src', event.target.result);
          });
        };
      });
    }
  };
}]);
angular.module('flow.transfers', ['flow.init'])
.directive('flowTransfers', [function() {
  return {
    'scope': true,
    'require': '^flowInit',
    'link': function(scope) {
      scope.transfers = scope.$flow.files;
    }
  };
}]);
angular.module('flow', ['flow.provider', 'flow.init', 'flow.events', 'flow.btn',
  'flow.drop', 'flow.transfers', 'flow.img', 'flow.dragEvents']);