define([
    'jquery', 'underscore', 'annotator_1.2.9', 'logger', 'js/edxnotes/views/notes_factory', 'js/spec/edxnotes/custom_matchers'
], function($, _, Annotator, Logger, NotesFactory, customMatchers) {
    'use strict';
    describe('EdxNotes Accessibility Plugin', function() {
        function keyDownEvent (key) {
            return $.Event('keydown', {keyCode: key});
        }

        function tabBackwardEvent () {
            return $.Event('keydown', {keyCode: $.ui.keyCode.TAB, shiftKey: true});
        }

        function tabForwardEvent () {
            return $.Event('keydown', {keyCode: $.ui.keyCode.TAB, shiftKey: false});
        }

        function enterMetaKeyEvent () {
            return $.Event('keydown', {keyCode: $.ui.keyCode.ENTER, metaKey: true});
        }

        function enterControlKeyEvent () {
            return $.Event('keydown', {keyCode: $.ui.keyCode.ENTER, ctrlKey: true});
        }

        beforeEach(function() {
            this.KEY = $.ui.keyCode;
            customMatchers(this);
            loadFixtures('js/fixtures/edxnotes/edxnotes_wrapper.html');
            this.annotator =  NotesFactory.factory(
                $('div#edx-notes-wrapper-123').get(0), {
                    endpoint: 'http://example.com/'
                }
            );
            this.plugin = this.annotator.plugins.Accessibility;
            spyOn(Logger, 'log');
        });

        afterEach(function () {
            _.invoke(Annotator._instances, 'destroy');
        });

        describe('destroy', function () {
            it('should unbind all events', function () {
                spyOn($.fn, 'off');
                spyOn(this.annotator, 'unsubscribe').andCallThrough();
                this.plugin.destroy();
                expect(this.annotator.unsubscribe).toHaveBeenCalledWith(
                    'annotationViewerTextField', this.plugin.addAriaAttributes
                );
                expect(this.annotator.unsubscribe).toHaveBeenCalledWith(
                    'annotationsLoaded', this.plugin.addDescriptions
                );
                expect(this.annotator.unsubscribe).toHaveBeenCalledWith(
                    'annotationCreated', this.plugin.addDescriptions
                );
                expect(this.annotator.unsubscribe).toHaveBeenCalledWith(
                    'annotationCreated', this.plugin.focusOnHighlightedText
                );
                expect(this.annotator.unsubscribe).toHaveBeenCalledWith(
                    'annotationDeleted', this.plugin.removeDescription
                );
                expect($.fn.off).toHaveBeenCalledWith('.accessibility');
            });
        });

        describe('a11y attributes', function () {
            var highlight, annotation, note;

            beforeEach(function() {
                highlight = $('<span class="annotator-hl" tabindex="0"/>').appendTo(this.annotator.element);
                annotation = {
                    id: '01',
                    text: 'Test text',
                    highlights: [highlight.get(0)]
                };
            });

            it('should be added to highlighted text and associated note', function () {
                this.annotator.viewer.load([annotation]);
                note = $('.annotator-note');
                expect(note).toExist();
                expect(note).toHaveAttr('tabindex', -1);
                expect(note).toHaveAttr('role', 'note');
                expect(note).toHaveAttr('class', 'annotator-note');
            });

            it('should create aria-descriptions when annotations are loaded', function () {
                this.annotator.publish('annotationsLoaded', [[annotation]]);
                expect(highlight).toHaveAttr('aria-describedby', 'aria-note-description-01');
                expect($('#aria-note-description-01')).toContainText('Test text');
            });

            it('should create aria-description when new annotation is created', function () {
                this.annotator.publish('annotationCreated', [annotation]);
                expect(highlight).toHaveAttr('aria-describedby', 'aria-note-description-01');
                expect($('#aria-note-description-01')).toContainText('Test text');
            });

            it('should remove aria-description when the annotation is removed', function () {
                this.annotator.publish('annotationDeleted', [annotation]);
                expect($('#aria-note-description-01')).not.toExist();
            });
        });

        describe('keydown events on highlighted text', function () {
            var highlight, annotation, note;

            beforeEach(function() {
                highlight = $('<span class="annotator-hl" tabindex="0"/>').appendTo(this.annotator.element);
                annotation = {
                    id: '01',
                    text: 'Test text',
                    highlights: [highlight.get(0)]
                };
                highlight.data('annotation', annotation);
                spyOn(this.annotator, 'showViewer').andCallThrough();
                spyOn(this.annotator.viewer, 'hide').andCallThrough();
                spyOn(this.plugin, 'focusOnGrabber').andCallThrough();
            });

            it('should open the viewer on SPACE keydown and focus on note', function () {
                highlight.trigger(keyDownEvent(this.KEY.SPACE));
                expect(this.annotator.showViewer).toHaveBeenCalled();
            });

            it('should open the viewer on ENTER keydown and focus on note', function () {
                highlight.trigger(keyDownEvent(this.KEY.ENTER));
                expect(this.annotator.showViewer).toHaveBeenCalled();
            });

            // This happens only when coming from notes page
            it('should open focus on viewer on TAB keydown if viewer is opened', function () {
                this.annotator.viewer.load([annotation]);
                highlight.trigger(keyDownEvent(this.KEY.TAB));
                expect(this.annotator.element.find('.annotator-listing')).toBeFocused();
            });

            it('should focus highlighted text after closing', function () {
                var note;
                highlight.trigger(keyDownEvent(this.KEY.ENTER));
                note = this.annotator.element.find('.annotator-edit');
                note.trigger(keyDownEvent(this.KEY.ESCAPE));
                expect(highlight).toBeFocused();
            });

            it('should focus on grabber after being deleted', function () {
                highlight.trigger(keyDownEvent(this.KEY.ENTER));
                this.annotator.publish('annotationDeleted', {});
                expect(this.plugin.focusGrabber).toBeFocused();
            });

            it('should not focus on grabber when the viewer is hidden', function () {
                this.annotator.publish('annotationDeleted', {});
                expect(this.plugin.focusGrabber).not.toBeFocused();
            });
        });

        describe('keydown events on viewer', function () {
            var highlight, annotation, listing, note, edit, del, close;

            beforeEach(function() {
                highlight = $('<span class="annotator-hl" tabindex="0"/>').appendTo(this.annotator.element);
                annotation = {
                    id: '01',
                    text: "Test text",
                    highlights: [highlight.get(0)]
                };
                highlight.data('annotation', annotation);
                this.annotator.viewer.load([annotation]);
                listing = this.annotator.element.find('.annotator-listing').first();
                note = this.annotator.element.find('.annotator-note').first();
                edit= this.annotator.element.find('.annotator-edit').first();
                del = this.annotator.element.find('.annotator-delete').first();
                close = this.annotator.element.find('.annotator-close').first();
                spyOn(this.annotator.viewer, 'hide').andCallThrough();
            });

            it('should give focus to Note on Listing TAB keydown', function () {
                listing.focus();
                listing.trigger(tabForwardEvent());
                expect(note).toBeFocused();
            });

            it('should give focus to Close on Listing SHIFT + TAB keydown', function () {
                listing.focus();
                listing.trigger(tabBackwardEvent());
                expect(close).toBeFocused();
            });

            it('should cycle forward through Note, Edit, Delete, and Close on TAB keydown', function () {
                note.focus();
                note.trigger(tabForwardEvent());
                expect(edit).toBeFocused();
                edit.trigger(tabForwardEvent());
                expect(del).toBeFocused();
                del.trigger(tabForwardEvent());
                expect(close).toBeFocused();
                close.trigger(tabForwardEvent());
                expect(note).toBeFocused();
            });

            it('should cycle backward through Note, Edit, Delete, and Close on SHIFT + TAB keydown', function () {
                note.focus();
                note.trigger(tabBackwardEvent());
                expect(close).toBeFocused();
                close.trigger(tabBackwardEvent());
                expect(del).toBeFocused();
                del.trigger(tabBackwardEvent());
                expect(edit).toBeFocused();
                edit.trigger(tabBackwardEvent());
                expect(note).toBeFocused();
            });

            it('should hide on ESCAPE keydown', function () {
                var tabControls = [listing, note, edit, del, close];

                _.each(tabControls, function (control) {
                    control.focus();
                    control.trigger(keyDownEvent(this.KEY.ESCAPE));
                }, this);
                expect(this.annotator.viewer.hide.callCount).toBe(5);
            });
        });

        describe('keydown events on editor', function () {
            var highlight, annotation, form, annotatorItems, textArea, tags, save, cancel;

            beforeEach(function() {
                highlight = $('<span class="annotator-hl" tabindex="0"/>').appendTo(this.annotator.element);
                annotation = {
                    id: '01',
                    text: "Test text",
                    highlights: [highlight.get(0)]
                };
                highlight.data('annotation', annotation);
                this.annotator.editor.show(annotation, {'left': 0, 'top': 0});
                form = this.annotator.element.find('form.annotator-widget');
                annotatorItems = this.annotator.element.find('.annotator-item');
                textArea = annotatorItems.first().children('textarea');
                tags = annotatorItems.first().next().children('input');
                save  = this.annotator.element.find('.annotator-save');
                cancel = this.annotator.element.find('.annotator-cancel');
                spyOn(this.annotator.editor, 'submit').andCallThrough();
                spyOn(this.annotator.editor, 'hide').andCallThrough();
            });

            it('should give focus to TextArea on Form TAB keydown', function () {
                form.focus();
                form.trigger(tabForwardEvent());
                expect(textArea).toBeFocused();
            });

            it('should give focus to Cancel on Form SHIFT + TAB keydown', function () {
                form.focus();
                form.trigger(tabBackwardEvent());
                expect(cancel).toBeFocused();
            });

            it('should cycle forward through textarea, tags, save, and cancel on TAB keydown', function () {
                textArea.focus();
                textArea.trigger(tabForwardEvent());
                expect(tags).toBeFocused();
                tags.trigger(tabForwardEvent());
                expect(save).toBeFocused();
                save.trigger(tabForwardEvent());
                expect(cancel).toBeFocused();
                cancel.trigger(tabForwardEvent());
                expect(textArea).toBeFocused();
            });

            it('should cycle back through textarea, tags, save, and cancel on SHIFT + TAB keydown', function () {
                textArea.focus();
                textArea.trigger(tabBackwardEvent());
                expect(cancel).toBeFocused();
                cancel.trigger(tabBackwardEvent());
                expect(save).toBeFocused();
                save.trigger(tabBackwardEvent());
                expect(tags).toBeFocused();
                tags.trigger(tabBackwardEvent());
                expect(textArea).toBeFocused();
            });

            it('should submit if target is Save on ENTER or SPACE keydown', function () {
                save.focus();
                save.trigger(keyDownEvent(this.KEY.ENTER));
                expect(this.annotator.editor.submit).toHaveBeenCalled();
                this.annotator.editor.submit.reset();
                save.focus();
                save.trigger(keyDownEvent(this.KEY.SPACE));
                expect(this.annotator.editor.submit).toHaveBeenCalled();
            });

            it('should submit on META or CONTROL + ENTER keydown', function () {
                textArea.focus();
                textArea.trigger(enterMetaKeyEvent());
                expect(this.annotator.editor.submit).toHaveBeenCalled();
                this.annotator.editor.submit.reset();
                textArea.focus();
                textArea.trigger(enterControlKeyEvent());
                expect(this.annotator.editor.submit).toHaveBeenCalled();
            });

            it('should hide if target is Cancel on ENTER or SPACE keydown', function () {
                cancel.focus();
                cancel.trigger(keyDownEvent(this.KEY.ENTER));
                expect(this.annotator.editor.hide).toHaveBeenCalled();
                this.annotator.editor.hide.reset();
                cancel.focus();
                save.trigger(keyDownEvent(this.KEY.SPACE));
                expect(this.annotator.editor.hide).toHaveBeenCalled();
            });

            it('should hide on ESCAPE keydown', function () {
                var tabControls = [textArea, save, cancel];

                _.each(tabControls, function (control) {
                    control.focus();
                    control.trigger(keyDownEvent(this.KEY.ESCAPE));
                }, this);
                expect(this.annotator.editor.hide.callCount).toBe(3);
            });
        });
    });
});
