/*
 jQuery UI Sortable plugin wrapper
 https://github.com/angular-ui/ui-sortable

 @param [ui-sortable] {object} Options to pass to $.fn.sortable() merged onto ui.config
*/
angular.module('ui.sortable', [])
  .value('uiSortableConfig',{})
  .directive('uiSortable', [ 'uiSortableConfig', '$log',
        function(uiSortableConfig, log) {
        return {
          require: '?ngModel',
          link: function(scope, element, attrs, ngModel) {

              function combineCallbacks(first,second){
                  if( second && (typeof second === "function") ){
                      return function(e,ui){
                          first(e,ui);
                          second(e,ui);
                      };
                  }
                  return first;
              }

            var opts = {};

            var callbacks = {
                receive: null,
                remove:null,
                start:null,
                stop:null,
                update:null
            };
            
            var apply = function(e, ui) {
              if (ui.item.sortable.resort || ui.item.sortable.relocate) {
                scope.$apply();
              }
            };

            angular.extend(opts, uiSortableConfig);

            if (ngModel) {

              ngModel.$render = function() {
                element.sortable( "refresh" );
              };

              callbacks.start = function(e, ui) {
                // Save position of dragged item
                ui.item.sortable = { index: ui.item.index() };
              };

              callbacks.update = function(e, ui) {
                // For some reason the reference to ngModel in stop() is wrong
                ui.item.sortable.resort = ngModel;
              };

              callbacks.receive = function(e, ui) {
                ui.item.sortable.relocate = true;
                // added item to array into correct position and set up flag
                ngModel.$modelValue.splice(ui.item.index(), 0, ui.item.sortable.moved);
              };

              callbacks.remove = function(e, ui) {
                // copy data into item
                if (ngModel.$modelValue.length === 1) {
                  ui.item.sortable.moved = ngModel.$modelValue.splice(0, 1)[0];
                } else {
                  ui.item.sortable.moved =  ngModel.$modelValue.splice(ui.item.sortable.index, 1)[0];
                }
              };

              callbacks.stop = function(e, ui) {
                // digest all prepared changes
                if (ui.item.sortable.resort && !ui.item.sortable.relocate) {

                  // Fetch saved and current position of dropped element
                  var end, start;
                  start = ui.item.sortable.index;
                  end = ui.item.index();

                  // Reorder array and apply change to scope
                  ui.item.sortable.resort.$modelValue.splice(end, 0, ui.item.sortable.resort.$modelValue.splice(start, 1)[0]);

                }
              };

              scope.$watch(attrs.uiSortable, function(newVal, oldVal){
                  angular.forEach(newVal, function(value, key){

                      if( callbacks[key] ){
                          // wrap the callback
                          value = combineCallbacks( callbacks[key], value );
                          
                          if ( key === 'stop' ){
                              // call apply after stop
                              value = combineCallbacks( value, apply );
                          }
                      }

                      element.sortable('option', key, value);
                  });
              }, true);

              angular.forEach(callbacks, function(value, key ){

                    opts[key] = combineCallbacks(value, opts[key]);
              });
              
              // call apply after stop
              opts.stop = combineCallbacks( opts.stop, apply );

            } else {
              log.info('ui.sortable: ngModel not provided!', element);
            }

            // Create sortable
            element.sortable(opts);
          }
        };
      }
]);
