/*
 ---

 script: Sortables.js

 name: Sortables

 description: Class for creating a drag and drop sorting interface for lists of items.

 license: MIT-style license

 authors:
 - Tom Occhino

 requires:
 - Core/Fx.Morph
 - Drag.Move

 provides: [Sortables]

 ...
 */

define('classes/utils/Sortables', function () {
    "use strict";

    return new Class({

        Implements: [Events, Options],

        options: {
            /*
             onSort: function(element, clone){},
             onStart: function(element, clone){},
             onComplete: function(element){},*/
            opacity        : 1,
            clone          : false,
            revert         : false,
            handle         : false,
            dragOptions    : {},
            unDraggableTags: ['button', 'input', 'a', 'textarea', 'select', 'option']
        },

        initialize: function (lists, options) {
            this.setOptions(options);

            this.elements = [];
            this.lists    = [];
            this.idle     = true;

            this.addLists($$(document.id(lists) || lists));

            if (!this.options.clone) {
                this.options.revert = false;
            }
            if (this.options.revert) {
                this.effect = new Fx.Morph(null, Object.merge({
                    duration: 250,
                    link    : 'cancel'
                }, this.options.revert));
            }
        },

        attach: function () {
            this.addLists(this.lists);
            return this;
        },

        detach: function () {
            this.lists = this.removeLists(this.lists);
            return this;
        },

        addItems: function () {
            Array.flatten(arguments).each(function (element) {
                this.elements.push(element);
                var start = element.retrieve('sortables:start', function (event) {
                    this.start.call(this, event, element);
                }.bind(this));
                (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
            }, this);
            return this;
        },

        addLists: function () {
            Array.flatten(arguments).each(function (list) {
                this.lists.include(list);
                this.addItems(list.getChildren());
            }, this);
            return this;
        },

        removeItems: function () {
            return $$(Array.flatten(arguments).map(function (element) {
                this.elements.erase(element);
                var start = element.retrieve('sortables:start');
                (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);

                return element;
            }, this));
        },

        removeLists: function () {
            return $$(Array.flatten(arguments).map(function (list) {
                this.lists.erase(list);
                this.removeItems(list.getChildren());

                return list;
            }, this));
        },

        getDroppableCoordinates: function (element) {
            var offsetParent = element.getOffsetParent();
            var position     = element.getPosition(offsetParent);
            var scroll       = {
                w           : window.getScroll(),
                offsetParent: offsetParent.getScroll()
            };
            position.x += scroll.offsetParent.x;
            position.y += scroll.offsetParent.y;

            if (offsetParent.getStyle('position') == 'fixed') {
                position.x -= scroll.w.x;
                position.y -= scroll.w.y;
            }

            return position;
        },

        getClone: function (event, element) {
            if (!this.options.clone) {
                return new Element(element.tagName).inject(document.body);
            }
            if (typeOf(this.options.clone) == 'function') {
                return this.options.clone.call(this, event, element, this.list);
            }
            var clone = element.clone(true).setStyles({
                margin    : 0,
                position  : 'absolute',
                visibility: 'hidden',
                width     : element.getStyle('width')
            }).addEvent('mousedown', function (event) {
                element.fireEvent('mousedown', event);
            });
            //prevent the duplicated radio inputs from unchecking the real one
            if (clone.get('html').test('radio')) {
                clone.getElements('input[type=radio]').each(function (input, i) {
                    input.set('name', 'clone_' + i);
                    if (input.get('checked')) {
                        element.getElements('input[type=radio]')[i].set('checked', true);
                    }
                });
            }

            return clone.inject(this.list).setPosition(this.getDroppableCoordinates(this.element));
        },

        getDroppables: function () {
            var droppables = this.list.getChildren().erase(this.clone).erase(this.element);
            if (!this.options.constrain) {
                droppables.append(this.lists).erase(this.list);
            }
            return droppables;
        },

        insert: function (dragging, element) {
            var where = 'inside';
            if (this.lists.contains(element)) {
                this.list            = element;
                this.drag.droppables = this.getDroppables();
            } else {
                where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
            }
            this.element.inject(element, where);
            this.fireEvent('sort', [this.element, this.clone]);
        },

        start: function (event, element) {
            if (
                !this.idle ||
                event.rightClick ||
                (!this.options.handle && this.options.unDraggableTags.contains(event.target.get('tag')))
            ) {
                return;
            }

            this.idle    = false;
            this.element = element;
            this.opacity = element.getStyle('opacity');
            this.list    = element.getParent();
            this.clone   = this.getClone(event, element);

            this.drag = new Drag.Move(this.clone, Object.merge({

                droppables: this.getDroppables()
            }, this.options.dragOptions)).addEvents({
                onSnap    : function () {
                    event.stop();
                    this.clone.setStyle('visibility', 'visible');
                    this.element.setStyle('opacity', this.options.opacity || 0);
                    this.fireEvent('start', [this.element, this.clone]);
                }.bind(this),
                onEnter   : this.insert.bind(this),
                onCancel  : this.end.bind(this),
                onComplete: this.end.bind(this)
            });

            this.clone.inject(this.element, 'before');
            this.drag.start(event);
        },

        end: function () {
            this.drag.detach();
            this.element.setStyle('opacity', this.opacity);
            var self = this;
            if (this.effect) {
                var dim   = this.element.getStyles('width', 'height'),
                    clone = this.clone,
                    pos   = clone.computePosition(this.getDroppableCoordinates(clone));

                var destroy = function () {
                    this.removeEvent('cancel', destroy);
                    clone.destroy();
                    self.reset();
                };

                this.effect.element = clone;
                this.effect.start({
                    top    : pos.top,
                    left   : pos.left,
                    width  : dim.width,
                    height : dim.height,
                    opacity: 0.25
                }).addEvent('cancel', destroy).chain(destroy);
            } else {
                this.clone.destroy();
                self.reset();
            }

        },

        reset: function () {
            this.idle = true;
            this.fireEvent('complete', this.element);
        },

        serialize: function () {
            var params = Array.link(arguments, {
                modifier: Type.isFunction,
                index   : function (obj) {
                    return obj !== null;
                }
            });
            var serial = this.lists.map(function (list) {
                return list.getChildren().map(params.modifier || function (element) {
                        return element.get('id');
                    }, this);
            }, this);

            var index = params.index;
            if (this.lists.length == 1) {
                index = 0;
            }
            return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial;
        }

    });
});
