/**
 * Flexsort: create flexible layouts that can be reordered by the user
 * @author Seth Kinast
 * @requires jquery.masonry
 * @requires jquery.ui.sortable (in WSM only)
 * Usage: jQuery('#flexsort-container').flexsort();
 */
(function($) {
    $.fn.flexsort = function(options) {
        var isWSM = !(typeof Cobalt.WSM == 'undefined');
        var pageReloadRequired = false;

        var defaults = {
            itemClass: 'cell',
            columnWidth: 60,
            gutterWidth: 20,
            isResizable: false
        };

        options = $.extend(defaults, options);
        options.itemSelector = '.'+options.itemClass;

        function packCells(container) {
            container.masonry({
               itemSelector: options.itemSelector,
               columnWidth: options.columnWidth,
               gutterWidth: options.gutterWidth,
               isResizable: options.isResizable
            });
        };

        function getCellName(widgetName) {
            var timestamp = new Date().getTime(),
                rx = /[^\w\d]+/g,
                cellName = "flex-"+widgetName.replace(rx,"_")+"-"+timestamp;
            return cellName;
        };

        function getWidgetLoadingGuard() {
            var guard = $("#widget-loading-placeholder").clone().attr('id','').show();
            return guard;
        }

        function makeWidgetCellsSortable(container) {
            if(container.hasClass('no-edit')) return;
            
            // Get all connected sortable classes
            var connectedSortables = container.attr('class').replace('flexible','').trim().split(' ');
            for(var x=0;x<connectedSortables.length;x++) {
                connectedSortables[x] = '.'+connectedSortables[x];
            }
            
            // Does the container have a max-height set? This is important for
            // deciding how much stuff can fit into this particular container.
            var maxHeight = parseInt(container.css('max-height')) +
                            parseInt(container.css('padding-top')) +
                            parseInt(container.css('padding-bottom'));
            
            container.sortable({
                connectWith: connectedSortables.join(','),
                tolerance: 'pointer',
                handle: $.support.touch ? '.ui-move-handle' : false,
                // Distance: px a draggable must be moved before dragging starts
                distance: 15,
                // Helper: the element the user actually moves around
                helper: function(e,elem) {
                    var clone = $(elem).clone();
                    clone.removeClass(options.itemClass).addClass("being-dragged");
                    elem.removeClass(options.itemClass);
                    return clone;
                },
                // Placeholder: the drop target shown while the user is dragging
                placeholder: {
                    element: function(currentItem) {
                        return $("<div class='"+options.itemClass+" masonry-brick placeholder' style='height: "
                                + (currentItem.height()) +"px; width: "
                                + (currentItem.width()) +"px;'></div>")[0];
                    },
                    update: function(container, p) {
                        return;
                    }
                },
                // Start: when sorting begins (user picks up a widget)
                start: function(e,ui) {
                    ui.item.find('script').remove();
                    $(window).trigger('containerFluxStarted');
                    $("#treasure-chest").trigger('treasureChestDisable');
                },
                // Receive: a linked container receives a widget from another container
                receive: function(e,ui) {
                    if(container[0].scrollHeight > maxHeight) {
                        $(ui.sender).sortable('cancel');
                    }
                },
                // Stop: when sorting has ended (user has dropped the widget)
                //       and update is complete
                stop: function(e,ui) {
                    var isNewItem = ui.item.hasClass('treasure-widget-preview');                    
                    
                    $("#treasure-chest").trigger('treasureChestEnable');
                    
                    // Take advantage of NaN always making comparisons false.
                    if(container[0].scrollHeight > maxHeight) {
                        //console.log('scrollHeight is %s', container[0].scrollHeight);
                        //console.log('maxHeight is %s', maxHeight);
                        $(this).sortable('cancel');
                        if(isNewItem) {
                            ui.item.remove();
                        }
                        $(window).trigger('containerFluxEnded');
                        return;
                    }
                    
                    // Was this a new widget dropped in?
                    if(isNewItem) {
                        // Create an instance of the widget in a cell. We rely on
                        // automagic creation of an instance using the shorthand
                        // ${widget.name=My Widget} notation.
                        var name = ui.item.attr('data-name');
                        var cellName = getCellName(name)

                        // Drop the widget preview into the canvas
                        var tempCell = $("<div id='"+cellName+"_wrapper' class='wsm-inline-editable' data-cell-name='"+cellName+"'></div>")
                                        .append(ui.item.find('img').addClass('thumbnail-preview'));
                        ui.item.replaceWith(tempCell);
                        tempCell.wrap("<div id='"+cellName+"' class='cell masonry-brick'></div>)");
                        tempCell.prepend(getWidgetLoadingGuard());

                        $(window).trigger("flexSaveStart");

                        // Fire off our AJAX call to let WSM know a new cell has been created...
                        $.post("/wsm/saveContent.do", {
                            "pageName": ContextManager.pageName,
                            "contentType": "content", // always
                            "cell_content_text": "${widget.name="+name+"}",
                            "cellName": cellName,
                            "pageSpecific": "true",
                            "cell_className_value": "",
                            "cell_styleAttr_value": ""
                        }).always(function(d) {
                            $("#treasure-chest .widget-loading").fadeOut();
                            // ...and, replace the preview with Real Cell Contents™!
                            $.get("cellContent.do",{
                                "cellName": cellName,
                                "pageName": ContextManager.pageName
                            }).always(function(d) {
                                if(d != "") {
                                    tempCell.empty().html(d).sneezeguard();
                                    container.trigger('masonryLayoutChanged');
                                }
                                // Now the cell is part of the canvas. We can save the new ordering.
                                container.trigger('masonryLayoutChangeCompleted');
                            }).fail(function(d) {
                                // If the cellContent call fails, we should request a page reload
                                pageReloadRequired = true;
                            });
                        });
                    }

                    ui.item.addClass(options.itemClass);
                    $(window).trigger('containerFluxEnded');
                },
                // Change: when sorting is in progress AND the DOM order has changed
                change: $.throttle(200, function(e,ui) {
                    container.trigger('masonryLayoutChanged');
                    ui.placeholder.toggleClass('invalid', container[0].scrollHeight > maxHeight);
                }),
                // Update: when sorting has ended (user has dropped the widget)
                //         AND the DOM order has changed.
                update: function(e,ui) {
                    // Only fire the event here if this widget was already in
                    // the canvas. For a new widget, we need to generate it first.
                    if(ui.item.hasClass('masonry-brick')) {
                        container.trigger('masonryLayoutChangeCompleted');
                    }
                },
                // Deactivate: when the container has an item moved away from it that
                //      does not yet belong to the list.
                deactivate: function(e,ui) {
                    container.trigger('masonryLayoutChanged');
                },
                remove: function(e,ui) {
                    container.trigger('masonryLayoutChanged');
                }
            }).disableSelection();
        };

        return this.each(function(){
            var container = $(this);
            packCells(container);

            if(isWSM) {
                makeWidgetCellsSortable(container);
            }

            // Event fired every time the canvas should repack
            // its cells, while the user is in the middle of a
            // move or resize operation
            container.bind('masonryLayoutChanged', function(e) {
                e.stopPropagation();
                container.masonry('reload');
            });

            // Flux events are used to turn off handlers on other
            // sneezeguards while the container is in the middle of
            // a move or sort operation.
            // Also, flux events may cause space/time discontinuities.
            $(window).bind('containerFluxStarted', function(e) {
                container.trigger('masonryLayoutChanged');
                container.addClass('in-flux');
            });
            $(window).bind('containerFluxEnded', function(e) {
                container.trigger('masonryLayoutChanged');
                container.removeClass('in-flux');
            });

            // Event fired when the user has completed a reordering
            // or resizing event and we are ready to persist the new
            // state of the canvas to the backend
            container.bind('masonryLayoutChangeCompleted', function(e) {
                if(container.data('saveFlexLayoutInProgress') == true) {
                    container.data('queueFlexLayoutSave', true);
                    return;
                } else {
                    container.data('saveFlexLayoutInProgress', true);
                    $(window).trigger("flexSaveStart");
                }
                var cells = container.sortable('toArray');
                var configs = [];
                for(x=0;x<cells.length;x++) {
                    var cell = $("#"+cells[x]);
                    configs.push({
                        configId: cells[x],
                        order: x,
                        width: cell.data('width'),
                        height: cell.data('height')
                    });
                }
                // requires json2.js from Douglas Crockford. Luckily built-in to WSM.
                var data = JSON.stringify({
                        "pageName": ContextManager.pageName,
                        "containerName": container.attr('id').replace('flexible-',''),
                        "configs": configs
                });
                // TODO: backend should really be allowing POST
                $.get("/wsm/saveFlexLayout.do?data="+data, function() {
                    //console.log("call complete");
                    container.data('saveFlexLayoutInProgress', false);
                    $(window).trigger("flexSaveEnd");
                    if(container.data('queueFlexLayoutSave') == true) {
                        //console.log("call was queued, calling more!");
                        container.data('queueFlexLayoutSave', false);
                        container.trigger($.Event('masonryLayoutChangeCompleted'))
                    } else if(pageReloadRequired) {
                        window.location.reload();
                    }
                });
            });

            // When an item is removed or added without using sortable
            // directly, we need to update Sortable's state so it stays kawaiiii
            container.bind('sortableItemsChanged', function(e) {
                container.sortable('refresh');
                container.trigger('masonryLayoutChangeCompleted');
            });
        });
    };
})(jQuery);

