(function (window, $, _, Concrete) {
    'use strict';

    /**
     * Edit mode object for managing editing.
     */
    var EditMode = Concrete.EditMode = function EditMode(options) {
        this.init.call(this, options);
    };

    EditMode.prototype = {

        init: function editModeInit(options) {
            var my = this;
            options = options || {};
            options = $.extend({
                'notify': false
            }, options);

            Concrete.createGetterSetters.call(my, {
                dragging: false,
                active: true,
                nextBlockArea: null,
                areas: [],
                selectedCache: [],
                selectedThreshold: 5,
                dragAreaBlacklist: []
            });

            my.bindEvent('PanelLoad', function editModePanelOpenEventHandler(event, data) {
                my.panelOpened(data.panel, data.element);
            });
            my.bindEvent('PanelClose', function editModePanelCloseEventHandler(event, data) {
                if (data.panel.getIdentifier() == 'add-block') {
                    my.setNextBlockArea(null);
                }
                html.removeClass('ccm-panel-add-block');
            });

            my.bindEvent('EditModeAddBlockComplete EditModeUpdateBlockComplete', function(e) {
                _.defer(function() {
                    my.scanBlocks();
                });
            });

            my.bindEvent('EditModeBlockSaveInline', function(event, data) {
                $('#ccm-block-form').submit();
                ConcreteEvent.fire('EditModeExitInlineSaved');
                ConcreteEvent.fire('EditModeExitInline', {
                    action: 'save_inline'
                });
            });

            my.bindEvent('EditModeBlockEditInline', function (event, data) {
                var block = data.block,
                    area = block.getArea(),
                    action = (data.action) ? data.action : CCM_DISPATCHER_FILENAME + '/ccm/system/dialogs/block/edit',
                    arEnableGridContainer = area.getEnableGridContainer() ? 1 : 0,
                    templates = area.getCustomTemplates();
                    var postData = [
                        {name: 'cID', value: block.getCID()},
                        {name: 'arHandle', value: area.getHandle()},
                        {name: 'arGridMaximumColumns', value: data.arGridMaximumColumns},
                        {name: 'arEnableGridContainer', value: arEnableGridContainer},
                        {name: 'aID', value: area.getId()},
                        {name: 'bID', value: block.getId()}
                    ],
                    bID = block.getId(),
                    $container = block.getElem(),
                    prop;

                if (templates) {
                    for (var k in templates) {
                        postData[postData.length] = {
                            name: 'arCustomTemplates[' + k + ']',
                            value: templates[k]
                        };
                    }
                }

                if (block.getAttr('menu')) {
                    block.getAttr('menu').destroy();
                }
                if (data.postData) {
                    for (prop in data.postData) {
                        if (data.postData.hasOwnProperty(prop)) {
                            postData.push({name: prop, value: data.postData[prop]});
                        }
                    }
                }

                Concrete.event.unbind('EditModeExitInline.editmode');
                my.bindEvent('EditModeExitInline.editmode', function (e, event_data) {
                    Concrete.event.unbind(e);
                    e.stopPropagation();
                    var action = CCM_DISPATCHER_FILENAME + '/ccm/system/block/render',
                        data = {
                            cID: block.getCID(),
                            arEnableGridContainer: arEnableGridContainer,
                            bID: block.getId(),
                            arHandle: area.getHandle()
                        };

                    $.fn.dialog.showLoader();

                    if (!event_data || !event_data.action || event_data.action !== 'save_inline') {
                        $.get(action, data,
                            function (r) {
                                var realBlock = my.getBlockByID(block.getId());
                                if (!realBlock) {
                                    return;
                                }

                                var newBlock = realBlock.replace(r);
                                _.defer(function () {
                                    ConcreteEvent.fire('EditModeExitInlineComplete', {
                                        block: newBlock
                                    });
                                    my.destroyInlineEditModeToolbars();
                                    _.defer(function () {
                                        my.scanBlocks();
                                    });
                                });
                            }
                        );
                    }
                });

                // We can't just wholesale disable the menu manager even though that makes
                // it so that you can't click on blocks while they're disabled, because we
                // need the file manager menu when editing block design.
//              ConcreteMenuManager.disable();
                ConcreteToolbar.disable();
                $('div.ccm-area').addClass('ccm-area-inline-edit-disabled');
                block.getElem().addClass('ccm-block-edit-inline-active');


                $.ajax({
                    type: 'GET',
                    url: action,
                    data: postData,
                    success: function (r) {
                        var elem = $(r);
                        $container.empty()
                            .append(elem)
                            .find('.ccm-block-edit')
                            .addClass('ccm-block-edit-inline-active');
                        my.loadInlineEditModeToolbars($container);
                        $.fn.dialog.hideLoader();
                        Concrete.event.fire('EditModeInlineEditLoaded', {
                            block: block,
                            element: elem
                        });
                    }
                });
            });

            my.bindEvent('EditModeBlockAddInline', function (event, data) {
                var area = data.area,
                    selected = data.selected,
                    btID = data.btID,
                    cID = data.cID,
                    arEnableGridContainer = area.getEnableGridContainer() ? 1 : 0,
                    postData = [
                        {name: 'btask', value: 'edit'},
                        {name: 'cID', value: cID},
                        {name: 'arGridMaximumColumns', value: data.arGridMaximumColumns},
                        {name: 'arEnableGridContainer', value: arEnableGridContainer},
                        {name: 'arHandle', value: area.getHandle()},
                        {name: 'btID', value: btID}
                    ], dragAreaBlock, dragAreaBlockID, after;
                if (selected) {
                    after = selected.getElem();
                    dragAreaBlock = selected.getBlock();
                } else {
                    after = area.getBlockContainer().children().last();
                    dragAreaBlock = data.dragAreaBlock;
                }

                var templates = area.getCustomTemplates();
                if (templates) {
                    for (var k in templates) {
                        postData[postData.length] = {
                            name: 'arCustomTemplate[' + k + ']',
                            value: templates[k]
                        };
                    }
                }

                if (dragAreaBlock) {
                    dragAreaBlockID = dragAreaBlock.getId();
                }

                ConcreteMenuManager.disable();
                ConcreteToolbar.disable();
                jQuery.fn.dialog.closeAll();

                $('div.ccm-area').addClass('ccm-area-inline-edit-disabled');

                $.fn.dialog.showLoader();

                if (area.getAttr('menu')) {
                    area.getAttr('menu').destroy();
                }

                var saved = false;
                my.bindEvent('EditModeExitInlineSaved', function (e) {
                    Concrete.event.unbind(e);
                    saved = true;

                    var panel = ConcretePanelManager.getByIdentifier('add-block');
                    if ( panel && panel.pinned() ) panel.show();
                });
                my.bindEvent('EditModeExitInline', function (e) {
                    Concrete.event.unsubscribe(e);
                    if (saved) {
                        return;
                    }
                    $('#a' + area.getId() + '-bt' + btID).remove();
                    my.destroyInlineEditModeToolbars();
                    ConcreteEvent.fire('EditModeExitInlineComplete');

                    var panel = ConcretePanelManager.getByIdentifier('add-block');
                    if ( panel && panel.pinned() ) panel.show();
                });
                $.ajax({
                    type: 'GET',
                    url: CCM_DISPATCHER_FILENAME + '/ccm/system/dialogs/page/add_block',
                    data: postData,
                    success: function (r) {
                        var elem = $(r);
                        var $container = $('<div class="ccm-block-edit-inline-active"></div>');
                        $container.attr('id', 'a' + area.getId() + '-bt' + btID).append(elem);

                        area.getElem().addClass("ccm-area-edit-inline-active");
                        after.after($container);
                        $(function () {
                            $container.find('#ccm-block-form').concreteAjaxBlockForm({
                                'task': 'add',
                                'btSupportsInlineAdd': true,
                                'dragAreaBlockID': dragAreaBlockID,
                                dragArea: selected,
                                placeholder: '#a' + area.getId() + '-bt' + btID
                            });
                        });
                        my.loadInlineEditModeToolbars($container.find('div[data-container=inline-toolbar]'));
                    },
                    complete: function () {
                        $.fn.dialog.hideLoader();
                    }
                });
            });

            Concrete.event.bind('EditModeBlockAddToClipboard', function (event, data) {
                var block = data.block, area = block.getArea(), token = data.token;
                ConcreteToolbar.disableDirectExit();
                // got to grab the message too, eventually
                $.ajax({
                    type: 'POST',
                    url: CCM_TOOLS_PATH + '/pile_manager',
                    data: 'cID=' + block.getCID() + '&bID=' + block.getId() + '&arHandle=' + encodeURIComponent(area.getHandle()) + '&btask=add&scrapbookName=userScrapbook&ccm_token=' + encodeURIComponent(token),
                    success: function (resp) {
                        ConcreteAlert.notify({
                            'message': ccmi18n.copyBlockToScrapbookMsg,
                            'title': ccmi18n.copyBlockToScrapbook
                        });
                    }
                });
            });

            my.bindEvent('EditModeBlockDelete', function (event, data) {
                var block = data.block;
                block.delete(data.message);
                ConcreteEvent.fire('EditModeBlockDeleteComplete', {
                    block: block
                });
            });


            var $body = $(window.document.body),
                scrolling = false,
                scroll_buffer = 100;

            function scrollLoop(block, element, amount, step, test, scroll_method, axis) {
                if (!my.getDragging()) {
                    return;
                }
                if (test.call()) {
                    scrolling = true;
                    var pos_start = scroll_method.call(element),
                        pos_new = pos_start + amount,
                        args = _.toArray(arguments),
                        pos = block.getDraggerPosition();

                    scroll_method.call(element, pos_new);

                    pos[axis] -= pos_start - scroll_method.call(element);
                    block.renderPosition();

                    _.defer(function () {
                        scrollLoop.apply(this, args);
                    }, step);
                } else {
                    scrolling = false;
                }
            }

            my.bindEvent('EditModeBlockDrag', _.throttle(function editModeEditModeBlockDragEventHandler(event, data) {
                if (!my.getDragging()) {
                    return;
                }
                var block = data.block, pep = data.pep,
                    areas = my.getAreas(),
                    contenders;

                if (block instanceof Concrete.Layout) {
                    areas = [_(areas).find(function (a) {
                        return block.getArea() === a;
                    })];
                }

                contenders = _.flatten(_(areas).map(function (area) {
                    var drag_areas = area.contendingDragAreas(pep, block);
                    return drag_areas;
                }), true);

                _.defer(function () {
                    Concrete.event.fire('EditModeContenders', contenders);
                    my.selectContender(pep, block, contenders, data.event);
                });

                if (!scrolling) {
                    // Vertical
                    scrollLoop(block, $body, 2, 10, function () {
                        var pos = block.getDraggerPosition().y - $body.scrollTop();
                        return block.getDragging() && $(window).height() - pos <= scroll_buffer;
                    }, $.fn.scrollTop, 'y');
                    scrollLoop(block, $body, -2, 10, function () {
                        var pos = block.getDraggerPosition().y - $body.scrollTop();
                        return block.getDragging() && pos <= scroll_buffer;
                    }, $.fn.scrollTop, 'y');

                    // Horizontal
                    scrollLoop(block, $body, 2, 10, function () {
                        var pos = block.getDraggerPosition().x - $body.scrollLeft();
                        return block.getDragging() && $(window).width() - pos <= scroll_buffer;
                    }, $.fn.scrollLeft, 'x');
                    scrollLoop(block, $body, -2, 10, function () {
                        var pos = block.getDraggerPosition().x - $body.scrollLeft();
                        return block.getDragging() && pos <= scroll_buffer;
                    }, $.fn.scrollLeft, 'x');
                }

            }, 250, {trailing: false}));

            my.bindEvent('EditModeBlockDragStop', function editModeEditModeBlockDragStopEventHandler(e, data) {
                Concrete.event.fire('EditModeContenders', []);
                Concrete.event.fire('EditModeSelectableContender');
                $('html').removeClass('ccm-block-dragging');

                if (data.block instanceof Concrete.BlockType) return;
                my.scanBlocks();
            });

            my.bindEvent('EditModeBlockMove', function editModeEditModeBlockMoveEventHandler(e, data) {
                var block = data.block,
                    targetArea = data.targetArea,
                    sourceArea = data.sourceArea,
                    send = {
                        ccm_token: window.CCM_SECURITY_TOKEN,
                        btask: 'ajax_do_arrange',
                        area: targetArea.getId(),
                        sourceArea: sourceArea.getId(),
                        block: block.getId(),
                        blocks: []
                    };

                targetArea = targetArea.inEditMode(targetArea.getEditMode());

                _(targetArea.getBlocks()).each(function (block, key) {
                    send.blocks.push(block.getId());
                });
                block.bindMenu();
                var loading = false, timeout = setTimeout(function () {
                    loading = true;
                    $.fn.dialog.showLoader();
                }, 150);

                $.concreteAjax({
                    url: CCM_DISPATCHER_FILENAME + '/ccm/system/page/arrange_blocks?cID=' + block.getCID(),
                    data: send,
                    success: function (r) {
                        ConcreteToolbar.disableDirectExit();
                        $.fn.dialog.hideLoader();
                        clearTimeout(timeout);
                    }
                });

            });

            my.bindEvent('EditModeBlockDragStart', function editModeEditModeBlockDragStartEventHandler() {
                $('html').addClass('ccm-block-dragging');
                my.setDragging(true);
            });

            my.scanBlocks();

            Concrete.getEditMode = function () {
                return my;
            };

            if (options.notify) {
                ConcreteAlert.notify({
                    'message': ccmi18n.editModeMsg,
                    'title': ccmi18n.editMode
                });
            }
        },

        bindEvent: function editModeBindEvent(event, handler) {
            var my = this;

            return Concrete.event.bind(event, handler);
        },

        reset: function () {
            var my = this;

            _(my.getAreas()).each(function (area) {
                area.destroy();
            });

            my.setAttr('areas', []);
        },

        scanBlocks: function editModeScanBlocks() {
            var my = this, area, block;
            my.reset();

            $('div.ccm-area').each(function () {
                var me = $(this);
                if (me.parent().hasClass('ccm-block-stack')) return;
                area = new Concrete.Area(me, my);
                area.scanBlocks();
                my.addArea(area);
            });

            _.invoke(my.getAreas(), 'bindMenu');
        },

        panelOpened: function editModePanelOpened(panel, element) {
            var my = this, next_area = my.getNextBlockArea();

            if (panel.getIdentifier() !== 'add-block') {
                return null;
            }
            html.addClass('ccm-panel-add-block');

            $(element).find('input[data-input=search-blocks]').liveUpdate('ccm-panel-add-blocktypes-list', 'blocktypes');
            $(element).find('input[data-input=search-blocks]').focus();



            $(element).find('a.ccm-panel-add-block-draggable-block-type').each(function () {
                var block, me = $(this), dragger = $('<a/>').addClass('ccm-panel-add-block-draggable-block-type-dragger').appendTo(me);
                block = new Concrete.BlockType($(this), my, dragger, next_area);

                block.setPeper(dragger);
            });


            $(element).find('div.ccm-panel-add-block-stack-item').each(function () {
                var stack, block, me = $(this), dragger = me.find('div.stack-name');
                stack = new Concrete.Stack($(this), my, dragger, next_area);

                stack.setPeper(dragger);

                $(this).find('div.block').each(function () {
                    var block, me = $(this), dragger = me.find('div.block-name');
                    block = new Concrete.StackBlock($(this), stack, my, dragger, next_area);

                    block.setPeper(dragger);
                });
            });

            $(element).find('div.ccm-panel-add-clipboard-block-item').each(function () {
                var block, me = $(this);
                new Concrete.DuplicateBlock(me, my, next_area);
            });

            $(element).find('.ccm-panel-content').mousewheel(function (e) {

                if (!e.deltaY || !e.deltaFactor) {
                    return;
                }

                var change = -1 * e.deltaY * e.deltaFactor;

                var me = $(this),
                    deltaY = change || 0,
                    distance_from_top = me.scrollTop(),
                    distance_from_bottom = (me.get(0).scrollHeight - (me.scrollTop() + me.height()) - me.css('paddingTop').replace('px', ''));

                // If we don't have deltaY, just use default behavior.
                if (!deltaY) {
                    return;
                }

                if ((deltaY < 0 && !distance_from_top) ||
                    (deltaY > 0 && !distance_from_bottom)
                ) {
                    return false;
                }

                me.scrollTop(me.scrollTop() + deltaY);
                return false;
            });

            return panel;
        },

        getAreaByID: function editModeGetAreaByID(arID) {
            var areas = this.getAreas();
            return _.findWhere(areas, {id: parseInt(arID)});
        },

        getBlockByID: function editModeGetBockByID(blockID) {
            var areas = this.getAreas(), match = null;

            _(areas).every(function(area) {
                if (match) {
                    return false;
                }
                _(area.getBlocks()).every(function(block) {
                    if (block.getId() == blockID) {
                        match = block;
                        return false;
                    }

                    return true;
                });

                return true;
            });

            return match;
        },

        /**
         * Select the correct contender
         * @param  {Pep}      pep        The relevant pep object
         * @param  {Block}    block      The Block
         * @param  {Array}    contenders The possible contenders
         * @param  {Event}    event      The triggering event
         * @return {DragArea}            The selected contender
         */
        selectContender: function editModeSelectContender(pep, block, contenders, event) {
            var my = this;

            // First, remove those that aren't selectable
            contenders = _(contenders).filter(function (drag_area) {
                return drag_area.isSelectable(pep, block, event);
            });
            if (contenders.length < 2) {
                return Concrete.event.fire('EditModeSelectableContender', _(contenders).first());
            }

            var selectedCache = my.getSelectedCache(), blacklist = my.getDragAreaBlacklist();
            if (my.getSelectedThreshold() === selectedCache.length && !_(selectedCache).without(_(selectedCache).last()).length) {
                blacklist.push(_(selectedCache).last());
                my.setDragAreaBlacklist(blacklist);

                _.delay(function (drag_area) {
                    var blacklist = my.getDragAreaBlacklist();
                    my.setDragAreaBlacklist(_(blacklist).without(drag_area));
                }, 5000, _(selectedCache).last());

            }
            contenders = _(contenders).difference(blacklist);

            // Determine the closest area to center because why not
            var selected = _(contenders).min(function (drag_area) {
                var res = drag_area.centerDistanceToBlock(this);
                return res;
            }, block);

            selectedCache.push(selected);
            my.setSelectedCache(_(selectedCache).last(my.getSelectedThreshold()));

            Concrete.event.fire('EditModeSelectableContender', selected);
            return selected;
        },

        /**
         * Add an area to the areas
         * @param {Area} area Area to add
         */
        addArea: function editModeAddArea(area) {
            var my = this;

            my.getAreas().push(area);
        },

        getBlocks: function editModeGetBlocks() {
            var blocks = [];

            _(this.getAreas()).each(function (area) {
                blocks.push(area.getBlocks());
            });

            return _(blocks).flatten();
        },

        destroyInlineEditModeToolbars: function () {
            ConcreteMenuManager.enable();
            $('div.ccm-area-edit-inline-active').removeClass('ccm-area-edit-inline-active');
            $('div.ccm-block-edit-inline-active').remove();
            $('div.ccm-area').removeClass('ccm-area-inline-edit-disabled');
            $('#ccm-toolbar').css('opacity', 1);
            $('#ccm-inline-toolbar-container').remove();

            $(window).unbind('scroll.inline-toolbar');
            ConcreteToolbar.enable();
            $.fn.dialog.hideLoader();
        },

        loadInlineEditModeToolbars: function ($container, toolbarHTML) {

            $('#ccm-inline-toolbar-container').remove();
            var $holder = $('<div />', {id: 'ccm-inline-toolbar-container'}).appendTo(document.body);

            if (toolbarHTML) {
                $holder.append(toolbarHTML);
                var $toolbar = $holder.find('.ccm-inline-toolbar');
            } else {
                var $toolbar = $container.find('.ccm-inline-toolbar');
                $toolbar.appendTo($holder);
            }

            var $window = $(window),
                pos = $container.offset(),
                l = pos.left;

            var tw = l + parseInt($toolbar.width());
            if ($window.width() > $toolbar.width()) {
                if (tw > $window.width()) {
                    var overage = tw - (l + $container.width());
                    $toolbar.css('left', l - overage);
                } else {
                    $toolbar.css('left', l);
                }
            }
            $toolbar.css('opacity', 1);
            $toolbar.find('.dialog-launch').dialog();
            var t = pos.top - $holder.outerHeight() - 5;
            $holder.css('top', t).css('opacity', 1);

            if ($window.scrollTop() > t) {
                $('#ccm-toolbar-disabled,#ccm-toolbar').css('opacity', 0);
                $holder.addClass('ccm-inline-toolbar-affixed');
            }

            $window.on('scroll.inline-toolbar', function () {
                $holder.toggleClass('ccm-inline-toolbar-affixed', $window.scrollTop() > t);
                if ($window.scrollTop() > t) {
                    $('#ccm-toolbar-disabled,#ccm-toolbar').css('opacity', 0);
                } else {
                    $('#ccm-toolbar-disabled,#ccm-toolbar').css('opacity', 1);
                }
            });

        }
    };

}(window, jQuery, _, Concrete));
