define(["js/utils/drag_and_drop", "js/views/feedback_notification", "js/common_helpers/ajax_helpers", "jquery", "underscore"],
    function (ContentDragger, Notification, AjaxHelpers, $, _) {
        describe("Overview drag and drop functionality", function () {
            beforeEach(function () {
                setFixtures(readFixtures('mock/mock-outline.underscore'));
                _.each(
                    $('.unit'),
                    function (element) {
                        ContentDragger.makeDraggable(element, {
                            type: '.unit',
                            handleClass: '.unit-drag-handle',
                            droppableClass: 'ol.sortable-unit-list',
                            parentLocationSelector: 'li.courseware-subsection',
                            refresh: jasmine.createSpy('Spy on Unit'),
                            ensureChildrenRendered: jasmine.createSpy('Spy on Unit')
                        });
                    }
                );
                _.each(
                    $('.courseware-subsection'),
                    function (element) {
                        ContentDragger.makeDraggable(element, {
                            type: '.courseware-subsection',
                            handleClass: '.subsection-drag-handle',
                            droppableClass: '.sortable-subsection-list',
                            parentLocationSelector: 'section',
                            refresh: jasmine.createSpy('Spy on Subsection'),
                            ensureChildrenRendered: jasmine.createSpy('Spy on Subsection')
                        });
                    }
                );
            });

            describe("findDestination", function () {
                it("correctly finds the drop target of a drag", function () {
                    var $ele, destination;
                    $ele = $('#unit-1');
                    $ele.offset({
                        top: $ele.offset().top + 10,
                        left: $ele.offset().left
                    });
                    destination = ContentDragger.findDestination($ele, 1);
                    expect(destination.ele).toBe($('#unit-2'));
                    expect(destination.attachMethod).toBe('before');
                });
                it("can drag and drop across section boundaries, with special handling for single sibling", function () {
                    var $ele, $unit0, $unit4, destination;
                    $ele = $('#unit-1');
                    $unit4 = $('#unit-4');
                    $ele.offset({
                        top: $unit4.offset().top + 8,
                        left: $ele.offset().left
                    });
                    destination = ContentDragger.findDestination($ele, 1);
                    expect(destination.ele).toBe($unit4);
                    expect(destination.attachMethod).toBe('after');
                    destination = ContentDragger.findDestination($ele, -1);
                    expect(destination.ele).toBe($unit4);
                    expect(destination.attachMethod).toBe('before');
                    $ele.offset({
                        top: $unit4.offset().top + $unit4.height() + 1,
                        left: $ele.offset().left
                    });
                    destination = ContentDragger.findDestination($ele, 0);
                    expect(destination.ele).toBe($unit4);
                    expect(destination.attachMethod).toBe('after');
                    $unit0 = $('#unit-0');
                    $ele.offset({
                        top: $unit0.offset().top - 16,
                        left: $ele.offset().left
                    });
                    destination = ContentDragger.findDestination($ele, 0);
                    expect(destination.ele).toBe($unit0);
                    expect(destination.attachMethod).toBe('before');
                });
                it("can drop before the first element, even if element being dragged is\nslightly before the first element", function () {
                    var $ele, destination;
                    $ele = $('#subsection-2');
                    $ele.offset({
                        top: $('#subsection-0').offset().top - 5,
                        left: $ele.offset().left
                    });
                    destination = ContentDragger.findDestination($ele, -1);
                    expect(destination.ele).toBe($('#subsection-0'));
                    expect(destination.attachMethod).toBe('before');
                });
                it("can drag and drop across section boundaries, with special handling for last element", function () {
                    var $ele, destination;
                    $ele = $('#unit-4');
                    $ele.offset({
                        top: $('#unit-3').offset().bottom + 4,
                        left: $ele.offset().left
                    });
                    destination = ContentDragger.findDestination($ele, -1);
                    expect(destination.ele).toBe($('#unit-3'));
                    expect(destination.attachMethod).toBe('after');
                    $ele.offset({
                        top: $('#unit-3').offset().top + 4,
                        left: $ele.offset().left
                    });
                    destination = ContentDragger.findDestination($ele, -1);
                    expect(destination.ele).toBe($('#unit-3'));
                    expect(destination.attachMethod).toBe('before');
                });
                it("can drop past the last element, even if element being dragged is\nslightly before/taller then the last element", function () {
                    var $ele, destination;
                    $ele = $('#subsection-2');
                    $ele.offset({
                        top: $('#subsection-4').offset().top - 1,
                        left: $ele.offset().left
                    });
                    destination = ContentDragger.findDestination($ele, 1);
                    expect(destination.ele).toBe($('#subsection-4'));
                    expect(destination.attachMethod).toBe('after');
                });
                it("can drag into an empty list", function () {
                    var $ele, destination;
                    $ele = $('#unit-1');
                    $ele.offset({
                        top: $('#subsection-3').offset().top + 10,
                        left: $ele.offset().left
                    });
                    destination = ContentDragger.findDestination($ele, 1);
                    expect(destination.ele).toBe($('#subsection-list-3'));
                    expect(destination.attachMethod).toBe('prepend');
                });
                it("reports a null destination on a failed drag", function () {
                    var $ele, destination;
                    $ele = $('#unit-1');
                    $ele.offset({
                        top: $ele.offset().top + 200,
                        left: $ele.offset().left
                    });
                    destination = ContentDragger.findDestination($ele, 1);
                    expect(destination).toEqual({
                        ele: null,
                        attachMethod: ""
                    });
                });
                it("can drag into a collapsed list", function () {
                    var $ele, destination;
                    $('#subsection-2').addClass('is-collapsed');
                    $ele = $('#unit-2');
                    $ele.offset({
                        top: $('#subsection-2').offset().top + 3,
                        left: $ele.offset().left
                    });
                    destination = ContentDragger.findDestination($ele, 1);
                    expect(destination.ele).toBe($('#subsection-list-2'));
                    expect(destination.parentList).toBe($('#subsection-2'));
                    expect(destination.attachMethod).toBe('prepend');
                });
            });
            describe("onDragStart", function () {
                it("sets the dragState to its default values", function () {
                    expect(ContentDragger.dragState).toEqual({});
                    ContentDragger.onDragStart({
                        element: $('#unit-1')
                    }, null, null);
                    expect(ContentDragger.dragState).toEqual({
                        dropDestination: null,
                        attachMethod: '',
                        parentList: null,
                        lastY: 0,
                        dragDirection: 0
                    });
                });
                it("collapses expanded elements", function () {
                    expect($('#subsection-1')).not.toHaveClass('is-collapsed');
                    ContentDragger.onDragStart({
                        element: $('#subsection-1')
                    }, null, null);
                    expect($('#subsection-1')).toHaveClass('is-collapsed');
                    expect($('#subsection-1')).toHaveClass('expand-on-drop');
                });
            });
            describe("onDragMove", function () {
                beforeEach(function () {
                    this.redirectSpy = spyOn(window, 'scrollBy').andCallThrough();
                });
                it("adds the correct CSS class to the drop destination", function () {
                    var $ele, dragX, dragY;
                    $ele = $('#unit-1');
                    dragY = $ele.offset().top + 10;
                    dragX = $ele.offset().left;
                    $ele.offset({
                        top: dragY,
                        left: dragX
                    });
                    ContentDragger.onDragMove({
                        element: $ele,
                        dragPoint: {
                            y: dragY
                        }
                    }, '', {
                        clientX: dragX
                    });
                    expect($('#unit-2')).toHaveClass('drop-target drop-target-before');
                    expect($ele).toHaveClass('valid-drop');
                });
                it("does not add CSS class to the drop destination if out of bounds", function () {
                    var $ele, dragY;
                    $ele = $('#unit-1');
                    dragY = $ele.offset().top + 10;
                    $ele.offset({
                        top: dragY,
                        left: $ele.offset().left
                    });
                    ContentDragger.onDragMove({
                        element: $ele,
                        dragPoint: {
                            y: dragY
                        }
                    }, '', {
                        clientX: $ele.offset().left - 3
                    });
                    expect($('#unit-2')).not.toHaveClass('drop-target drop-target-before');
                    expect($ele).not.toHaveClass('valid-drop');
                });
                it("scrolls up if necessary", function () {
                    ContentDragger.onDragMove({
                        element: $('#unit-1')
                    }, '', {
                        clientY: 2
                    });
                    expect(this.redirectSpy).toHaveBeenCalledWith(0, -10);
                });
                it("scrolls down if necessary", function () {
                    ContentDragger.onDragMove({
                        element: $('#unit-1')
                    }, '', {
                        clientY: window.innerHeight - 5
                    });
                    expect(this.redirectSpy).toHaveBeenCalledWith(0, 10);
                });
            });
            describe("onDragEnd", function () {
                beforeEach(function () {
                    this.reorderSpy = spyOn(ContentDragger, 'handleReorder');
                });
                afterEach(function () {
                    this.reorderSpy.reset();
                });
                it("calls handleReorder on a successful drag", function () {
                    ContentDragger.dragState.dropDestination = $('#unit-2');
                    ContentDragger.dragState.attachMethod = "after";
                    ContentDragger.dragState.parentList = $('#subsection-1');
                    $('#unit-1').offset({
                        top: $('#unit-1').offset().top + 10,
                        left: $('#unit-1').offset().left
                    });
                    ContentDragger.onDragEnd({
                        element: $('#unit-1')
                    }, null, {
                        clientX: $('#unit-1').offset().left
                    });
                    expect(this.reorderSpy).toHaveBeenCalled();
                });
                it("clears out the drag state", function () {
                    ContentDragger.onDragEnd({
                        element: $('#unit-1')
                    }, null, null);
                    expect(ContentDragger.dragState).toEqual({});
                });
                it("sets the element to the correct position", function () {
                    ContentDragger.onDragEnd({
                        element: $('#unit-1')
                    }, null, null);
                    expect(['0px', 'auto']).toContain($('#unit-1').css('top'));
                    expect(['0px', 'auto']).toContain($('#unit-1').css('left'));
                });
                it("expands an element if it was collapsed on drag start", function () {
                    $('#subsection-1').addClass('is-collapsed');
                    $('#subsection-1').addClass('expand-on-drop');
                    ContentDragger.onDragEnd({
                        element: $('#subsection-1')
                    }, null, null);
                    expect($('#subsection-1')).not.toHaveClass('is-collapsed');
                    expect($('#subsection-1')).not.toHaveClass('expand-on-drop');
                });
                it("expands a collapsed element when something is dropped in it", function () {
                    expandElementSpy = spyOn(ContentDragger, 'expandElement').andCallThrough();
                    expect(expandElementSpy).not.toHaveBeenCalled();
                    expect($('#subsection-2').data('ensureChildrenRendered')).not.toHaveBeenCalled();

                    $('#subsection-2').addClass('is-collapsed');
                    ContentDragger.dragState.dropDestination = $('#list-2');
                    ContentDragger.dragState.attachMethod = "prepend";
                    ContentDragger.dragState.parentList = $('#subsection-2');
                    ContentDragger.onDragEnd({
                        element: $('#unit-1')
                    }, null, {
                        clientX: $('#unit-1').offset().left
                    });

                    // verify collapsed element expands while ensuring its children are properly rendered
                    expect(expandElementSpy).toHaveBeenCalled();
                    expect($('#subsection-2').data('ensureChildrenRendered')).toHaveBeenCalled();
                    expect($('#subsection-2')).not.toHaveClass('is-collapsed');
                });
            });
            describe("AJAX", function () {
                beforeEach(function () {
                    this.savingSpies = spyOnConstructor(Notification, "Mini", ["show", "hide"]);
                    this.savingSpies.show.andReturn(this.savingSpies);
                    this.clock = sinon.useFakeTimers();
                });
                afterEach(function () {
                    this.clock.restore();
                });
                it("should send an update on reorder from one parent to another", function () {
                    var requests, savingOptions;
                    requests = AjaxHelpers["requests"](this);
                    ContentDragger.dragState.dropDestination = $('#unit-4');
                    ContentDragger.dragState.attachMethod = "after";
                    ContentDragger.dragState.parentList = $('#subsection-2');
                    $('#unit-1').offset({
                        top: $('#unit-4').offset().top + 10,
                        left: $('#unit-4').offset().left
                    });
                    ContentDragger.onDragEnd({
                        element: $('#unit-1')
                    }, null, {
                        clientX: $('#unit-1').offset().left
                    });
                    expect(requests.length).toEqual(1);
                    expect(this.savingSpies.constructor).toHaveBeenCalled();
                    expect(this.savingSpies.show).toHaveBeenCalled();
                    expect(this.savingSpies.hide).not.toHaveBeenCalled();
                    savingOptions = this.savingSpies.constructor.mostRecentCall.args[0];
                    expect(savingOptions.title).toMatch(/Saving/);
                    expect($('#unit-1')).toHaveClass('was-dropped');
                    expect(requests[0].requestBody).toEqual('{"children":["fourth-unit-id","first-unit-id"]}');
                    requests[0].respond(200);
                    expect(this.savingSpies.hide).toHaveBeenCalled();
                    this.clock.tick(1001);
                    expect($('#unit-1')).not.toHaveClass('was-dropped');
                    // source
                    expect($('#subsection-1').data('refresh')).toHaveBeenCalled();
                    // target
                    expect($('#subsection-2').data('refresh')).toHaveBeenCalled();
                });
                it("should send an update on reorder within the same parent", function () {
                    var requests = AjaxHelpers["requests"](this);
                    ContentDragger.dragState.dropDestination = $('#unit-2');
                    ContentDragger.dragState.attachMethod = "after";
                    ContentDragger.dragState.parentList = $('#subsection-1');
                    $('#unit-1').offset({
                        top: $('#unit-1').offset().top + 10,
                        left: $('#unit-1').offset().left
                    });
                    ContentDragger.onDragEnd({
                        element: $('#unit-1')
                    }, null, {
                        clientX: $('#unit-1').offset().left
                    });
                    expect(requests.length).toEqual(1);
                    expect($('#unit-1')).toHaveClass('was-dropped');
                    expect(requests[0].requestBody).toEqual(
                        '{"children":["second-unit-id","first-unit-id","third-unit-id"]}'
                    );
                    requests[0].respond(200);
                    this.clock.tick(1001);
                    expect($('#unit-1')).not.toHaveClass('was-dropped');
                    // parent
                    expect($('#subsection-1').data('refresh')).toHaveBeenCalled();
                });
            });
        });
    });

