/**
 * RightJS-UI Sortable v2.2.0
 * http://rightjs.org/ui/sortable
 *
 * Copyright (C) 2009-2011 Nikolay Nemshilov
 */
var Sortable = RightJS.Sortable = (function(document, RightJS) {
/**
 * This module defines the basic widgets constructor
 * it creates an abstract proxy with the common functionality
 * which then we reuse and override in the actual widgets
 *
 * Copyright (C) 2010-2011 Nikolay Nemshilov
 */

/**
 * The widget units constructor
 *
 * @param String tag-name or Object methods
 * @param Object methods
 * @return Widget wrapper
 */
function Widget(tag_name, methods) {
  if (!methods) {
    methods = tag_name;
    tag_name = 'DIV';
  }

  /**
   * An Abstract Widget Unit
   *
   * Copyright (C) 2010 Nikolay Nemshilov
   */
  var AbstractWidget = new RightJS.Class(RightJS.Element.Wrappers[tag_name] || RightJS.Element, {
    /**
     * The common constructor
     *
     * @param Object options
     * @param String optional tag name
     * @return void
     */
    initialize: function(key, options) {
      this.key = key;
      var args = [{'class': 'rui-' + key}];

      // those two have different constructors
      if (!(this instanceof RightJS.Input || this instanceof RightJS.Form)) {
        args.unshift(tag_name);
      }
      this.$super.apply(this, args);

      if (RightJS.isString(options)) {
        options = RightJS.$(options);
      }

      // if the options is another element then
      // try to dynamically rewrap it with our widget
      if (options instanceof RightJS.Element) {
        this._ = options._;
        if ('$listeners' in options) {
          options.$listeners = options.$listeners;
        }
        options = {};
      }
      this.setOptions(options, this);

      return (RightJS.Wrapper.Cache[RightJS.$uid(this._)] = this);
    },

  // protected

    /**
     * Catches the options
     *
     * @param Object user-options
     * @param Element element with contextual options
     * @return void
     */
    setOptions: function(options, element) {
      if (element) {
        options = RightJS.Object.merge(options, new Function("return "+(
          element.get('data-'+ this.key) || '{}'
        ))());
      }

      if (options) {
        RightJS.Options.setOptions.call(this, RightJS.Object.merge(this.options, options));
      }

      return this;
    }
  });

  /**
   * Creating the actual widget class
   *
   */
  var Klass = new RightJS.Class(AbstractWidget, methods);

  // creating the widget related shortcuts
  RightJS.Observer.createShortcuts(Klass.prototype, Klass.EVENTS || RightJS([]));

  return Klass;
}


/**
 * Sortable initialization script
 *
 * Copyright (C) 2010 Nikolay Nemshilov
 */
var R        = RightJS,
    $        = RightJS.$,
    $w       = RightJS.$w,
    isString = RightJS.isString,
    isArray  = RightJS.isArray,
    Object   = RightJS.Object;




/**
 * The Sortable unit
 *
 * Copyright (C) 2009-2011 Nikolay Nemshilov
 */
var Sortable = new Widget('UL', {
  extend: {
    version: '2.2.0',

    EVENTS: $w('start change finish'),

    Options: {
      url:        null,       // the Xhr requests url address, might contain the '%{id}' placeholder
      method:     'put',      // the Xhr requests method

      Xhr:        {},         // additional Xhr options

      idParam:    'id',       // the id value name
      posParam:   'position', // the position value name
      parseId:    true,       // if the id attribute should be converted into an integer before sending

      dragClass:  'dragging', // the in-process class name
      accept:     null,       // a reference or a list of references to the other sortables between which you can drag the items
      minLength:  1,          // minimum number of items on the list when the feature works

      itemCss:    'li',       // the draggable item's css rule
      handleCss:  'li',       // the draggables handle element css rule

      cssRule:    '*[data-sortable]' // css-rule for automatically initializable sortables
    },

    current: false, // a reference to the currently active sortable

    /**
     * Typecasting the list element for Sortable
     *
     * @param Element list
     * @return Sortable list
     */
    cast: function(element) {
      element = $(element._);
      if (!(element instanceof Sortable)) {
        element = new Sortable(element);
      }
      return element;
    }
  },

  /**
   * basic constructor
   *
   * @param mixed element reference
   * @param Object options
   * @return void
   */
  initialize: function(element, options) {
    this.$super('sortable', element)
      .setOptions(options)
      .addClass('rui-sortable')
      .on('finish', this._tryXhr)
      .on('selectstart', 'stopEvent'); // disable select under IE
  },

  /**
   * some additional options processing
   *
   * @param Object options
   * @param Element optional context
   * @return Sortable this
   */
  setOptions: function(options, context) {
    this.$super(options, context);

    options = this.options;

    // Preprocessing the acceptance list
    var list = options.accept || [];
    if (!isArray(list)) { list = [list]; }

    options.accept = R([this].concat(list)).map($).uniq();

    return this;
  },

  // returns a list of draggable items
  items: function() {
    return this.children(this.options.itemCss);
  },

// protected

  // starts the drag
  startDrag: function(event) {
    // don't let to drag out the last item
    if (this.items().length <= this.options.minLength) { return; }

    // trying to find the list-item upon which the user pressed the mouse
    var item   = event.find(this.options.itemCss),
        handle = event.find(this.options.handleCss);

    if (item && handle) {
      this._initDrag(item, event.position());
      Sortable.current = this;
      this.fire('start', this._evOpts(event));
    }
  },

  // moves the item
  moveItem: function(event) {
    var event_pos = event.position(),
        item   = this.itemClone._.style,
        top    = event_pos.y - this.yRDiff,
        left   = event_pos.x - this.xRDiff,
        right  = left + this.cloneWidth,
        bottom = top  + this.cloneHeight;

    // moving the clone
    item.top  = (event_pos.y - this.yDiff) + 'px';
    item.left = (event_pos.x - this.xDiff) + 'px';

    // checking for an overlaping item
    var over_item = this.suspects.first(function(suspect) {
      return (
        (top    > suspect.top    && top    < suspect.topHalf) ||
        (bottom < suspect.bottom && bottom > suspect.topHalf)
      ) && (
        (left   > suspect.left   && left   < suspect.leftHalf) ||
        (right  < suspect.right  && right  > suspect.leftHalf)
      );
    });

    if (over_item) {
      item = over_item.item;
      item.insert(this.item, item.prevSiblings().include(this.item) ? 'after' : 'before');
      this._findSuspects();

      this.fire('change', this._evOpts(event, item));
    }
  },

  // finalizes the drag
  finishDrag: function(event) {
    if (this.itemClone) {
      this.itemClone.remove();
      this.item.setStyle('visibility:visible');
    }
    Sortable.current = false;
    this.fire('finish', this._evOpts(event));
  },

  // returns the event options
  _evOpts: function(event, item) {
    item = item || this.item;
    var list = Sortable.cast(item.parent());

    return {
      list: list,
      item: item,
      event: event,
      index: list.items().indexOf(item)
    };
  },

  _initDrag: function(item, event_pos) {
    var dims = this.dimensions(), item_dims = item.dimensions();

    // creating the draggable clone
    var clone = item.clone().setStyle({
      margin:   0,
      zIndex:   9999,
      position: 'absolute',
      top:      '0px',
      left:     '0px'
    })
    .addClass(this.options.dragClass).insertTo(this)
    .setHeight(this.cloneHeight = item_dims.height)
    .setWidth(this.cloneWidth = item_dims.width);

    // adjusting the clone position to compensate relative fields and margins
    var clone_pos = clone.position(),
        real_x    = item_dims.left - clone_pos.x,
        real_y    = item_dims.top  - clone_pos.y;

    clone.moveTo(real_x, real_y);

    this.item = item.setStyle('visibility:hidden');
    this.itemClone = clone;

    // mouse event-position diffs
    this.xDiff  = event_pos.x - real_x;
    this.yDiff  = event_pos.y - real_y;
    this.xRDiff = event_pos.x - clone.position().x;
    this.yRDiff = event_pos.y - clone.position().y;

    // collecting the list of interchangable items with their positions
    this._findSuspects();
  },

  // collects the precached list of suspects
  _findSuspects: function() {
    var suspects = this.suspects = R([]), item = this.item, clone = this.itemClone;
    this.options.accept.each(function(list) {
      Sortable.cast(list).items().each(function(element) {
        if (element !== item && element !== clone) {
          var dims = element.dimensions();

          // caching the sizes
          suspects.push({
            item:     element,
            top:      dims.top,
            left:     dims.left,
            right:    dims.left + dims.width,
            bottom:   dims.top  + dims.height,
            topHalf:  dims.top  + dims.height/2,
            leftHalf: dims.left + dims.width/2
          });
        }
      });
    });
  },

  // tries to send an Xhr request about the element relocation
  _tryXhr: function(event) {
    if (this.options.url) {
      var url = R(this.options.url), params = {}, item = event.item, position = event.index + 1;

      // building the Xhr request options
      var options = Object.merge({
        method: this.options.method,
        params: {}
      }, this.options.Xhr);

      // grabbing the id
      var id = item.get('id') || '';
      if (this.options.parseId && id) {
        id = (id.match(/\d+/) || [''])[0];
      }

      // assigning the parameters
      if (url.include('%{id}')) {
        url = url.replace('%{id}', id);
      } else {
        params[this.options.idParam] = id;
      }
      params[this.options.posParam] = position;

      // merging the params with possible Xhr params
      if (isString(options.params)) {
        options.params += '&'+Object.toQueryString(params);
      } else {
        options.params = Object.merge(options.params, params);
      }

      // calling the server
      RightJS.Xhr.load(url, options);
    }
  }
});


/**
 * Document level hooks for sortables
 *
 * Copyright (C) 2009-2010 Nikolay Nemshilov
 */
$(document).on({
  mousedown: function(event) {
    var element = event.find(Sortable.Options.cssRule+",*.rui-sortable");

    if (element) {
      Sortable.cast(element).startDrag(event);
    }
  },

  mousemove: function(event) {
    if (Sortable.current) {
      Sortable.current.moveItem(event);
    }
  },

  mouseup: function(event) {
    if (Sortable.current) {
      Sortable.current.finishDrag(event);
    }
  }
});

$(window).onBlur(function() {
  if (Sortable.current) {
    Sortable.current.finishDrag();
  }
});


var embed_style = document.createElement('style'),                 
    embed_rules = document.createTextNode(".rui-sortable{user-select:none;-moz-user-select:none;-webkit-user-select:none}");      
                                                                   
embed_style.type = 'text/css';                                     
document.getElementsByTagName('head')[0].appendChild(embed_style); 
                                                                   
if(embed_style.styleSheet) {                                       
  embed_style.styleSheet.cssText = embed_rules.nodeValue;          
} else {                                                           
  embed_style.appendChild(embed_rules);                            
}                                                                  


return Sortable;
})(document, RightJS);