define ["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js/models/section",
    "js/collections/textbook", "js/views/show_textbook", "js/views/edit_textbook", "js/views/list_textbooks",
    "js/views/edit_chapter", "js/views/feedback_prompt", "js/views/feedback_notification",
    "sinon", "jasmine-stealth"],
(Textbook, Chapter, ChapterSet, Section, TextbookSet, ShowTextbook, EditTextbook, ListTexbook, EditChapter, Prompt, Notification, sinon) ->
    feedbackTpl = readFixtures('system-feedback.underscore')

    beforeEach ->
        # remove this when we upgrade jasmine-jquery
        @addMatchers
            toContainText: (text) ->
                trimmedText = $.trim(@actual.text())
                if text and $.isFunction(text.test)
                    return text.test(trimmedText)
                else
                    return trimmedText.indexOf(text) != -1;

    describe "ShowTextbook", ->
        tpl = readFixtures('show-textbook.underscore')

        beforeEach ->
            setFixtures($("<script>", {id: "show-textbook-tpl", type: "text/template"}).text(tpl))
            appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(feedbackTpl))
            appendSetFixtures(sandbox({id: "page-notification"}))
            appendSetFixtures(sandbox({id: "page-prompt"}))
            @model = new Textbook({name: "Life Sciences", id: "0life-sciences"})
            spyOn(@model, "destroy").andCallThrough()
            @collection = new TextbookSet([@model])
            @view = new ShowTextbook({model: @model})

            @promptSpies = spyOnConstructor(Prompt, "Warning", ["show", "hide"])
            @promptSpies.show.andReturn(@promptSpies)
            window.section = new Section({
                id: "5",
                name: "Course Name",
                url_name: "course_name",
                org: "course_org",
                num: "course_num",
                revision: "course_rev"
            });

        afterEach ->
            delete window.section

        describe "Basic", ->
            it "should render properly", ->
                @view.render()
                expect(@view.$el).toContainText("Life Sciences")

            it "should set the 'editing' property on the model when the edit button is clicked", ->
                @view.render().$(".edit").click()
                expect(@model.get("editing")).toBeTruthy()

            it "should pop a delete confirmation when the delete button is clicked", ->
                @view.render().$(".delete").click()
                expect(@promptSpies.constructor).toHaveBeenCalled()
                ctorOptions = @promptSpies.constructor.mostRecentCall.args[0]
                expect(ctorOptions.title).toMatch(/Life Sciences/)
                # hasn't actually been removed
                expect(@model.destroy).not.toHaveBeenCalled()
                expect(@collection).toContain(@model)

            it "should show chapters appropriately", ->
                @model.get("chapters").add([{}, {}, {}])
                @model.set('showChapters', false)
                @view.render().$(".show-chapters").click()
                expect(@model.get('showChapters')).toBeTruthy()

            it "should hide chapters appropriately", ->
                @model.get("chapters").add([{}, {}, {}])
                @model.set('showChapters', true)
                @view.render().$(".hide-chapters").click()
                expect(@model.get('showChapters')).toBeFalsy()

        describe "AJAX", ->
            beforeEach ->
                @requests = requests = []
                @xhr = sinon.useFakeXMLHttpRequest()
                @xhr.onCreate = (xhr) -> requests.push(xhr)

                @savingSpies = spyOnConstructor(Notification, "Mini",
                    ["show", "hide"])
                @savingSpies.show.andReturn(@savingSpies)

            afterEach ->
                @xhr.restore()

            it "should destroy itself on confirmation", ->
                @view.render().$(".delete").click()
                ctorOptions = @promptSpies.constructor.mostRecentCall.args[0]
                # run the primary function to indicate confirmation
                ctorOptions.actions.primary.click(@promptSpies)
                # AJAX request has been sent, but not yet returned
                expect(@model.destroy).toHaveBeenCalled()
                expect(@requests.length).toEqual(1)
                expect(@savingSpies.constructor).toHaveBeenCalled()
                expect(@savingSpies.show).toHaveBeenCalled()
                expect(@savingSpies.hide).not.toHaveBeenCalled()
                savingOptions = @savingSpies.constructor.mostRecentCall.args[0]
                expect(savingOptions.title).toMatch(/Deleting/)
                # return a success response
                @requests[0].respond(200)
                expect(@savingSpies.hide).toHaveBeenCalled()
                expect(@collection.contains(@model)).toBeFalsy()

    describe "EditTextbook", ->
        describe "Basic", ->
            tpl = readFixtures('edit-textbook.underscore')
            chapterTpl = readFixtures('edit-chapter.underscore')

            beforeEach ->
                setFixtures($("<script>", {id: "edit-textbook-tpl", type: "text/template"}).text(tpl))
                appendSetFixtures($("<script>", {id: "edit-chapter-tpl", type: "text/template"}).text(chapterTpl))
                appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(feedbackTpl))
                appendSetFixtures(sandbox({id: "page-notification"}))
                appendSetFixtures(sandbox({id: "page-prompt"}))
                @model = new Textbook({name: "Life Sciences", editing: true})
                spyOn(@model, 'save')
                @collection = new TextbookSet()
                @collection.add(@model)
                @view = new EditTextbook({model: @model})
                spyOn(@view, 'render').andCallThrough()

            it "should render properly", ->
                @view.render()
                expect(@view.$("input[name=textbook-name]").val()).toEqual("Life Sciences")

            it "should allow you to create new empty chapters", ->
                @view.render()
                numChapters = @model.get("chapters").length
                @view.$(".action-add-chapter").click()
                expect(@model.get("chapters").length).toEqual(numChapters+1)
                expect(@model.get("chapters").last().isEmpty()).toBeTruthy()

            it "should save properly", ->
                @view.render()
                @view.$("input[name=textbook-name]").val("starfish")
                @view.$("input[name=chapter1-name]").val("wallflower")
                @view.$("input[name=chapter1-asset-path]").val("foobar")
                @view.$("form").submit()
                expect(@model.get("name")).toEqual("starfish")
                chapter = @model.get("chapters").first()
                expect(chapter.get("name")).toEqual("wallflower")
                expect(chapter.get("asset_path")).toEqual("foobar")
                expect(@model.save).toHaveBeenCalled()

            it "should not save on invalid", ->
                @view.render()
                @view.$("input[name=textbook-name]").val("")
                @view.$("input[name=chapter1-asset-path]").val("foobar.pdf")
                @view.$("form").submit()
                expect(@model.validationError).toBeTruthy()
                expect(@model.save).not.toHaveBeenCalled()

            it "does not save on cancel", ->
                @model.get("chapters").add([{name: "a", asset_path: "b"}])
                @view.render()
                @view.$("input[name=textbook-name]").val("starfish")
                @view.$("input[name=chapter1-asset-path]").val("foobar.pdf")
                @view.$(".action-cancel").click()
                expect(@model.get("name")).not.toEqual("starfish")
                chapter = @model.get("chapters").first()
                expect(chapter.get("asset_path")).not.toEqual("foobar")
                expect(@model.save).not.toHaveBeenCalled()

            it "should be possible to correct validation errors", ->
                @view.render()
                @view.$("input[name=textbook-name]").val("")
                @view.$("input[name=chapter1-asset-path]").val("foobar.pdf")
                @view.$("form").submit()
                expect(@model.validationError).toBeTruthy()
                expect(@model.save).not.toHaveBeenCalled()
                @view.$("input[name=textbook-name]").val("starfish")
                @view.$("input[name=chapter1-name]").val("foobar")
                @view.$("form").submit()
                expect(@model.validationError).toBeFalsy()
                expect(@model.save).toHaveBeenCalled()

            it "removes all empty chapters on cancel if the model has a non-empty chapter", ->
                chapters = @model.get("chapters")
                chapters.at(0).set("name", "non-empty")
                @model.setOriginalAttributes()
                @view.render()
                chapters.add([{}, {}, {}]) # add three empty chapters
                expect(chapters.length).toEqual(4)
                @view.$(".action-cancel").click()
                expect(chapters.length).toEqual(1)
                expect(chapters.first().get('name')).toEqual("non-empty")

            it "removes all empty chapters on cancel except one if the model has no non-empty chapters", ->
                chapters = @model.get("chapters")
                @view.render()
                chapters.add([{}, {}, {}]) # add three empty chapters
                expect(chapters.length).toEqual(4)
                @view.$(".action-cancel").click()
                expect(chapters.length).toEqual(1)


#    describe "ListTextbooks", ->
#        noTextbooksTpl = readFixtures("no-textbooks.underscore")
#
#        beforeEach ->
#            setFixtures($("<script>", {id: "no-textbooks-tpl", type: "text/template"}).text(noTextbooksTpl))
#            appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(feedbackTpl))
#            @showSpies = spyOnConstructor("ShowTextbook", ["render"])
#            @showSpies.render.andReturn(@showSpies) # equivalent of `return this`
#            showEl = $("<li>")
#            @showSpies.$el = showEl
#            @showSpies.el = showEl.get(0)
#            @editSpies = spyOnConstructor("EditTextbook", ["render"])
#            editEl = $("<li>")
#            @editSpies.render.andReturn(@editSpies)
#            @editSpies.$el = editEl
#            @editSpies.el= editEl.get(0)
#
#            @collection = new TextbookSet
#            @view = new ListTextbooks({collection: @collection})
#            @view.render()
#
#        it "should render the empty template if there are no textbooks", ->
#            expect(@view.$el).toContainText("You haven't added any textbooks to this course yet")
#            expect(@view.$el).toContain(".new-button")
#            expect(@showSpies.constructor).not.toHaveBeenCalled()
#            expect(@editSpies.constructor).not.toHaveBeenCalled()
#
#        it "should render ShowTextbook views by default if no textbook is being edited", ->
#            # add three empty textbooks to the collection
#            @collection.add([{}, {}, {}])
#            # reset spies due to re-rendering on collection modification
#            @showSpies.constructor.reset()
#            @editSpies.constructor.reset()
#            # render once and test
#            @view.render()
#
#            expect(@view.$el).not.toContainText(
#                "You haven't added any textbooks to this course yet")
#            expect(@showSpies.constructor).toHaveBeenCalled()
#            expect(@showSpies.constructor.calls.length).toEqual(3);
#            expect(@editSpies.constructor).not.toHaveBeenCalled()
#
#        it "should render an EditTextbook view for a textbook being edited", ->
#            # add three empty textbooks to the collection: the first and third
#            # should be shown, and the second should be edited
#            @collection.add([{editing: false}, {editing: true}, {editing: false}])
#            editing = @collection.at(1)
#            expect(editing.get("editing")).toBeTruthy()
#            # reset spies
#            @showSpies.constructor.reset()
#            @editSpies.constructor.reset()
#            # render once and test
#            @view.render()
#
#            expect(@showSpies.constructor).toHaveBeenCalled()
#            expect(@showSpies.constructor.calls.length).toEqual(2)
#            expect(@showSpies.constructor).not.toHaveBeenCalledWith({model: editing})
#            expect(@editSpies.constructor).toHaveBeenCalled()
#            expect(@editSpies.constructor.calls.length).toEqual(1)
#            expect(@editSpies.constructor).toHaveBeenCalledWith({model: editing})
#
#        it "should add a new textbook when the new-button is clicked", ->
#            # reset spies
#            @showSpies.constructor.reset()
#            @editSpies.constructor.reset()
#            # test
#            @view.$(".new-button").click()
#
#            expect(@collection.length).toEqual(1)
#            expect(@view.$el).toContain(@editSpies.$el)
#            expect(@view.$el).not.toContain(@showSpies.$el)


    describe "EditChapter", ->
        tpl = readFixtures("edit-chapter.underscore")

        beforeEach ->
            setFixtures($("<script>", {id: "edit-chapter-tpl", type: "text/template"}).text(tpl))
            appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(feedbackTpl))
            @model = new Chapter
                name: "Chapter 1"
                asset_path: "/ch1.pdf"
            @collection = new ChapterSet()
            @collection.add(@model)
            @view = new EditChapter({model: @model})
            spyOn(@view, "remove").andCallThrough()
            CMS.URL.UPLOAD_ASSET = "/upload"
            window.section = new Section({name: "abcde"})

        afterEach ->
            delete CMS.URL.UPLOAD_ASSET
            delete window.section

        it "can render", ->
            @view.render()
            expect(@view.$("input.chapter-name").val()).toEqual("Chapter 1")
            expect(@view.$("input.chapter-asset-path").val()).toEqual("/ch1.pdf")

        it "can delete itself", ->
            @view.render().$(".action-close").click()
            expect(@collection.length).toEqual(0)
            expect(@view.remove).toHaveBeenCalled()

#        it "can open an upload dialog", ->
#            uploadSpies = spyOnConstructor("UploadDialog", ["show", "el"])
#            uploadSpies.show.andReturn(uploadSpies)
#
#            @view.render().$(".action-upload").click()
#            ctorOptions = uploadSpies.constructor.mostRecentCall.args[0]
#            expect(ctorOptions.model.get('title')).toMatch(/abcde/)
#            expect(typeof ctorOptions.onSuccess).toBe('function')
#            expect(uploadSpies.show).toHaveBeenCalled()

        it "saves content when opening upload dialog", ->
            @view.render()
            @view.$("input.chapter-name").val("rainbows")
            @view.$("input.chapter-asset-path").val("unicorns")
            @view.$(".action-upload").click()
            expect(@model.get("name")).toEqual("rainbows")
            expect(@model.get("asset_path")).toEqual("unicorns")
