jQuery.event.props.push( "dataTransfer" );
jQuery.event.props.push("effectAllowed");
jQuery.event.props.push("srcElement");
jQuery.event.props.push("pageX");
jQuery.event.props.push("pageY");

(function ( $ ) {
    var previousTimeout = null;

    function eventPreventDefaultAndStopPropagation(event){
        event.stopPropagation();
        event.preventDefault();
        event.stopImmediatePropagation();
    }

    function DroppableClass(element, options){
        this.parseOptions(options);
        this.domElement = element;
        this.disabled = false;
        this.previousX = null;
        this.previousY = null;
        this.excludedSelectors = [];
        this.customData = [];
    }

    DroppableClass.prototype.parseOptions = function (options){
        for(var option in options) {
            if(option === 'dragenter' && typeof options[option] === 'function') {
                this._dragenter = options[option];
            } else if(option === 'dragover' && typeof options[option] === 'function') {
                this._dragover = options[option];
            } else if(option === 'dragleave' && typeof options[option] === 'function'){
                this._dragleave = options[option];
            } else if(option === 'drop' && typeof options[option] === 'function'){
                this._drop = options[option];
            } else if(option === 'hoverClass' && typeof options[option] === 'string'){
                this._hoverClass = options[option];
            } else if(option === 'tolerance' && typeof options[option] === 'number') {
                this._tolerance = options[option];
            } else if(option === 'excludedSelectors' && typeof options[option] === 'string') {
                this._excludedSelectors = options[option] !== '' ? options[option].split(',') : [];
            }
        }
    };

    DroppableClass.prototype.checkEventTarget = function(element, event) {
        return event.target === element;
    };

    DroppableClass.prototype.disableEvents = function(){
        this.disabled = true;
        for(var event in this.events){
            this.domElement.off(event);
        }

    };

    DroppableClass.prototype.disableEspecificEvent = function(eventName) {
        this.domElement.off(eventName);
    };

    DroppableClass.prototype.enableEspecificEvent = function(eventName) {
        if(typeof this.events[eventName] === 'function') {
            this.domElement.on(eventName, this.events[eventName]);
        }
    };

    DroppableClass.prototype.enableEvents = function() {
        this.disabled = false;
        for(var event in this.events){
            this.domElement.on(event, this.events[event]);
        }
    };

    DroppableClass.prototype.destroy = function() {
        for(var event in this.events){
            this.domElement.off(event, this.events[event]);
        }

        this.domElement.removeData('droppableInstance');
    };

    DroppableClass.prototype.isDisabled = function() {
        return this.disabled;
    };

    DroppableClass.prototype.checkParents = function(){
        var allParentsEnabled = true;
        var parent = $(this.domElement).parent();
        if(this.isDisabled()) {
            allParentsEnabled = false;
        }

        while(allParentsEnabled && !parent.is('body')) {
            var instance = $(parent).data('droppableInstance');
            if( typeof instance !== 'undefined' && instance.isDisabled()){
                allParentsEnabled = false;
            } else {
                parent = $(parent).parent();
            }
        }

        return allParentsEnabled;
    };

    DroppableClass.prototype.calculateContainerRectangle = function(container) {
        return { top: $(container).offset().top, left: $(container).offset().left, width: $(container).width(), height: $(container).height()}
    };

    DroppableClass.prototype.getPlaceHolder = function() {
        return '<li class="cs-placeholder"></li>'
    };

    DroppableClass.prototype.putPlaceHolder = function(placeholder, container, event, tolerance) {

        if(previousTimeout !== null){
            clearTimeout(previousTimeout);
        }
        var _self = this;

        previousTimeout = setTimeout(function() {
            $(container).closest('body').find('.cs-placeholder').remove();
            var containerChildNodes = $(container).children();
            containerChildNodes = _self.excludeNodes(containerChildNodes);

            if(containerChildNodes.length > 0){
                var bestMatch = _self.getBestMatch(containerChildNodes, event, container.get(0), tolerance);

                if(bestMatch.action === 'prepend') {
                    $(bestMatch.element).before($(placeholder).addClass(bestMatch.class));
                } else if(bestMatch.action === 'append') {
                    $(bestMatch.element).after($(placeholder).addClass(bestMatch.class));
                }
            }
        }, 100);

    };

    DroppableClass.prototype.excludeNodes = function(nodes) {
        if(nodes.length > 0){
            var _self = this;
            for(var i = 0; i < _self._excludedSelectors.length; i++){
                if(nodes.length === 0){
                    i = _self._excludedSelectors.length;
                }

                nodes = $.grep(nodes, function(element){
                    return !$(element).is(_self._excludedSelectors[i]);
                });
            }
        }
        return nodes;
    };

    DroppableClass.prototype.calculateDistance = function(x,y, element, elementRectangle) {
        return Math.floor(Math.sqrt(Math.pow(x - (elementRectangle.left+(elementRectangle.width/2)), 2) + Math.pow(y - (elementRectangle.top+(elementRectangle.height/2)), 2)));
    };

    DroppableClass.prototype.getBestMatch = function(nodes, event){
        var current = null,
            currentRect =  null,
            x = event.pageX,
            y = event.pageY,
            minDist = null,
            minElement = null,
            minElementRect = null,
            distance = null;

        for(var i = 0; i < nodes.length; i++){
            current = nodes[i];
            currentRect = this.calculateContainerRectangle(current);
            distance = this.calculateDistance(x, y, current, currentRect);

            if(minDist === null || minDist > distance){
                minDist = distance;
                minElement = current;
                minElementRect = currentRect;
            }
        }

        var action = 'append';
        var orientation = 'vertical';

        if(minElementRect.left > x || minElementRect.top > y) {
            action = 'prepend';
        }

        if($(minElement).prev().length === 0 || ($(minElement).next().length === 0 && action === 'append') || this.calculateContainerRectangle($(minElement).prev()).top < minElementRect.top ) {
            orientation = 'horizontal';
        }

        //console.timeEnd('calculate');
        return {'element' : minElement, 'action' : action, 'finded' : true, 'class' : orientation};
    };

    DroppableClass.prototype.events = {
        dragenter: function (event) {
            var instance = $(this).data('droppableInstance');
            if(typeof instance === null) {
                throw "CS generic error";
            }

            eventPreventDefaultAndStopPropagation(event);
            if(instance.checkParents(this, event)){
                $(this).addClass(instance._hoverClass);
                instance._dragenter.call(this, event, event.target);
            }
        },
        dragover: function (event) {
            var instance = $(this).data('droppableInstance');
            eventPreventDefaultAndStopPropagation(event);

            if(typeof instance === null) {
                throw "CS generic exception";
            }

            if(instance.checkEventTarget(this, event) && event.pageX !== instance.previousX && event.pageY !== instance.previousY) {
                    /*var nodes = instance.excludeNodes([this]);
                    var nodes = instance.excludeNodes([this]);
                    if(nodes.length === 0) {
                        event.dataTransfer.dropEffect = 'none';
                        $(this).removeClass(instance._hoverClass);
                        return false;
                    }*/

                instance.putPlaceHolder(instance.getPlaceHolder(), $(this), event, instance._tolerance);
                instance.previousX = event.pageX;
                instance.previousY = event.pageY;
            }

            if(instance.checkEventTarget(this, event) && instance.checkParents()) {
                instance._dragover.call(this, event, event.target);
            } else {
                event.dataTransfer.dropEffect = 'none';
                $(this).removeClass(instance._hoverClass);
            }
        },
        dragleave: function (event) {
            if(previousTimeout !== null){
                clearTimeout(previousTimeout);
            }
            var instance = $(this).data('droppableInstance');
            eventPreventDefaultAndStopPropagation(event);
            $(this).find('.cs-placeholder').remove();
            if(instance.checkEventTarget(this, event) && instance.checkParents()) {
                $(this).removeClass(instance._hoverClass);
                instance._dragleave.call(this, event, event.target);
            }
        },
        drop: function (event) {

            if(previousTimeout !== null){
                clearTimeout(previousTimeout);
            }

            var instance = $(this).data('droppableInstance');

            if(instance.checkParents()) {
                eventPreventDefaultAndStopPropagation(event);
                var html = event.dataTransfer.getData("text/plain");

                $(this).removeClass(instance._hoverClass);

                if(instance.checkEventTarget(this, event)) {
                    html = $(html);
                    var newItem = null;
                    var selector = event.dataTransfer.getData('text/cs-selector');

                    if($(event.target).closest('body').find('.cs-placeholder').length > 0) {
                        if(typeof selector !== 'undefined' && selector !== '' && selector !== null){
                            newItem = $(event.target).closest('body').find('.'+selector);
                            $(event.target).closest('body').find('.cs-placeholder').replaceWith(newItem);
                        } else {
                            $(event.target).closest('body').find('.cs-placeholder').replaceWith(html);
                            newItem = html;
                        }
                    } else {
                        if(typeof selector !== 'undefined' && selector !== '' && selector !== null) {
                            newItem = $(event.target).closest('body').find('.'+selector);
                            newItem.appendTo(event.target);
                        } else {
                            newItem = $(html).appendTo(event.target);
                        }
                    }

                    instance._drop.call(event.target, event, newItem);
                }
            }
        }
    };

    $.fn.CSDroppable = function (options) {

        if(typeof options === 'string') {
            this.each(function(){
                var instance = $(this).data('droppableInstance');
                if(typeof instance !== 'undefined' && instance !== null){
                    if(options === 'disable') {
                        instance.disableEvents();
                    } else if(options === 'enable') {
                        instance.enableEvents();
                    } else if(options === 'destroy') {
                        instance.destroy();
                    }
                }
            });
        } else {
            var opts = $.extend({}, $.fn.CSDroppable.defaults, options);
            this.each(function(){
                var instance = $(this).data('droppableInstance');

                if(typeof instance !== 'undefined') {
                    instance.destroy();
                }

                instance = new DroppableClass($(this), opts);
                $(this).data('droppableInstance', instance);
                for(var event in instance.events){
                    $(this).on(event, instance.events[event]);
                }
            });
        }

        return this;
    };

    $.fn.CSDroppable.defaults = {
        hoverClass: 'select-hover',
        excludedSelectors: '',
        dragenter: function(){},
        dragover: function(){},
        dragleave: function(){},
        drop: function(){},
        tolerance: 5
    };

    function DraggableClass(element, opts){
        this.parseOptions(opts);
        this.domElement = element;
        this.customData = [];
    }

    DraggableClass.prototype.parseOptions = function(options){
        for(var option in options) {
            if(option === 'dragstart' && typeof options[option] === 'function') {
                this._dragstart = options[option];
            } else if(option === 'drag' && typeof options[option] === 'function') {
                this._drag = options[option];
            } else if(option === 'dragend' && typeof options[option] === 'function'){
                this._dragend = options[option];
            } else if(option === 'render' && typeof options[option] === 'function') {
                this._render = options[option];
            } else if(option === 'helper' && typeof options[option] === 'string') {
                this._helper = options[option];
            }
        }
    };

    DraggableClass.prototype.events = {
        dragstart: function (event) {
            event.stopPropagation();
            event.stopImmediatePropagation();
            var instance =  $(this).data('draggableInstance');
            var droppableInstace = $(this).data('droppableInstance');

            if(instance._helper === 'move') {
                var tmpClass = 'cs-draggable-element'+(new Date().getTime());
                $(instance.domElement).addClass(tmpClass);
                event.dataTransfer.setData('text/cs-selector', tmpClass);
            } else {
                var html = $(this).data('draggableInstance')._render.call(event.target, event, event.target);
                event.dataTransfer.setData('text/plain', html);
                if(typeof droppableInstace !== 'undefined'){
                    droppableInstace.disableEvents();
                }
            }

            instance._dragstart.call(this, event, event.target);
        },
        drag: function (event) {
            eventPreventDefaultAndStopPropagation(event);
            $(this).data('draggableInstance')._drag.call(this, event, event.target);

        },
        dragend: function (event) {
            eventPreventDefaultAndStopPropagation(event);
            var instance = $(this).data('draggableInstance');
            var droppableInstace = $(this).data('droppableInstance');

            if(typeof droppableInstace !== 'undefined'){
                droppableInstace.enableEvents();
            }

            if(instance._helper === 'move') {
                $(this).removeClass(function(index, cssClass){
                    return (cssClass.match (/(^|\s)cs-draggable-element\S+/g) || []).join(' ');
                });
            }

            instance._dragend.call(this, event, event.target);
        },
        destroy: function(event){
            eventPreventDefaultAndStopPropagation(event);
            $(this).data('draggableInstance').destroy();
        }
    };

    DraggableClass.prototype.destroy = function(){

        for(var event in this.events){
            $(this.domElement).off(event, this.events[event]);
        }

        $(this.domElement).removeData('draggableInstance');
        $(this.domElement).prop('draggable', false);
    };

    DraggableClass.prototype.disable = function() {
        $(this.domElement).prop('draggable', false);
    };

    DraggableClass.prototype.enable = function() {
        $(this.domElement).prop('draggable', true);
    };

    $.fn.CSDraggable = function(options) {
        $.fn.CSDraggable.disableNonDraggableItems();

        if(typeof options === 'string') {
            this.each(function(){
                var instance = $(this).data('draggableInstance');
                if(typeof instance !== 'undefined' && typeof instance !== null){
                    if(options === 'destroy') {
                        instance.destroy();
                    } else if(options === 'disable') {
                        instance.disable();
                    } else if(options === 'enable') {
                        instance.enable();
                    }
                }
            });

        } else {
            var opts = $.extend( {}, $.fn.CSDraggable.defaults, options );
            this.each(function(){
                var instance = $(this).data('draggableInstance');

                if(typeof instance !== 'undefined') {
                    instance.destroy();
                }

                $(this)
                    .prop('draggable', true)
                    .on('click', function(event){event.stopPropagation();})
                    .on('mousedown', function(event){event.stopPropagation();})
                    .on('mouseup', function(event){event.stopPropagation();});
                instance = new DraggableClass($(this), opts);
                $(this).data('draggableInstance', instance);
                for(var event in instance.events){
                    $(this).on(event, instance.events[event]);
                }
            });
        }

        return this;
    };

    $.fn.CSDraggable.disableNonDraggableItems = function(){
        $('a, img').each(function(){
          if(!$(this).data('draggableInstance')){
            $(this).prop('draggable', false);
          }
        });
    };

    $.fn.CSDraggable.defaults = {
        dragstart: function(element, event) {

        },
        drag: function(element, event) {

        },
        dragend: function(element, event) {

        },
        render: function(element){
            return $(element).html();
        },
        helper: 'original'
    };

    $.CSDragAndDropStore =  {
        customData : {},
        setData : function(key, value) {
            this.customData[key] = value;
        },
        getData : function(key) {
            return this.customData[key];
        },
        removeData: function(key) {
            delete this.customData[key];
        }
    }

})(jQuery);