describe('textAngularManager', function(){
    'use strict';
    beforeEach(module('textAngular'));

    describe('getVersion', function(){
        it('should return a valid version in the correct format!', inject(function(textAngularManager){
            expect(/v\d+.\d+.\d+/i.test(textAngularManager.getVersion())).toBe(true);
        }));
    });

    describe('toolbar', function(){
        describe('registration', function(){
            it('should require a scope object', inject(function(textAngularManager){
                expect(textAngularManager.registerToolbar).toThrow("textAngular Error: A toolbar requires a scope");
            }));

            it('should require a name', inject(function(textAngularManager){
                expect(function(){textAngularManager.registerToolbar({});}).toThrow("textAngular Error: A toolbar requires a name");
                expect(function(){textAngularManager.registerToolbar({name: ''});}).toThrow("textAngular Error: A toolbar requires a name");
            }));

            it('should require a unique name', inject(function(textAngularManager){
                textAngularManager.registerToolbar({name: 'test'});
                expect(function(){textAngularManager.registerToolbar({name: 'test'});}).toThrow('textAngular Error: A toolbar with name "test" already exists');
            }));
        });

        describe('retrieval', function(){
            it('should be undefined for no registered toolbar', inject(function(textAngularManager){
                expect(textAngularManager.retrieveToolbar('test')).toBeUndefined();
            }));

            it('should get the correct toolbar', inject(function(textAngularManager){
                var scope = {name: 'test'};
                textAngularManager.registerToolbar(scope);
                expect(textAngularManager.retrieveToolbar('test')).toBe(scope);
            }));

            it('should get the correct toolbar via editor', inject(function(textAngularManager){
                var scope = {name: 'test'};
                textAngularManager.registerToolbar(scope);
                textAngularManager.registerEditor('testeditor', {}, ['test']);
                expect(textAngularManager.retrieveToolbarsViaEditor('testeditor')[0]).toBe(scope);
            }));
        });

        describe('unregister', function(){
            it('should get the correct toolbar', inject(function(textAngularManager){
                textAngularManager.registerToolbar({name: 'test'});
                textAngularManager.unregisterToolbar('test');
                expect(textAngularManager.retrieveToolbar('test')).toBeUndefined();
            }));
        });

        describe('unregister-cleanup', function(){
            it('should not have any toolbarScopes left', inject(function(textAngularManager){
                textAngularManager.registerEditor('testeditor', {}, ['test', 'test1']);
                textAngularManager.registerToolbar({name: 'test'});
                textAngularManager.registerToolbar({name: 'test1'});
                expect(textAngularManager.getToolbarScopes().length).toBe(2);
                // note: unregisterToolbar does nothing to the toolbarScopes
                textAngularManager.unregisterToolbar('test');
                expect(textAngularManager.getToolbarScopes().length).toBe(2);
                textAngularManager.unregisterToolbar('test1');
                expect(textAngularManager.getToolbarScopes().length).toBe(2);
                textAngularManager.unregisterEditor('testeditor');
                // this will have deleted the toolbarScopes held in the editor
                expect(textAngularManager.getToolbarScopes().length).toBe(0);
            }));
        });

        describe('modification', function(){
            var $rootScope, toolbar1, toolbar2, textAngularManager;
            beforeEach(inject(function(_textAngularManager_){
                textAngularManager = _textAngularManager_;
            }));
            beforeEach(inject(function (_$compile_, _$rootScope_) {
                $rootScope = _$rootScope_;
                toolbar1 = jQuery(_$compile_('<text-angular-toolbar name="test1"></text-angular-toolbar>')($rootScope)[0]);
                toolbar2 = jQuery(_$compile_('<text-angular-toolbar name="test2"></text-angular-toolbar>')($rootScope)[0]);
                $rootScope.$digest();
            }));

            describe('throws error on no toolbar', function(){
                it('when update tool', function(){
                    expect(function(){
                        textAngularManager.updateToolbarToolDisplay('test', 'h1', {iconclass: 'test-icon-class'});
                    }).toThrow('textAngular Error: No Toolbar with name "test" exists');
                });
                it('when reset tool', function(){
                    expect(function(){
                        textAngularManager.resetToolbarToolDisplay('test', 'h1');
                    }).toThrow('textAngular Error: No Toolbar with name "test" exists');
                });
            });

            describe('single toolbar', function(){
                // we test these by adding an icon with a specific class and then testing for it's existance
                it('should update only one button on one toolbar', function(){
                    textAngularManager.updateToolbarToolDisplay('test1', 'h1', {iconclass: 'test-icon-class'});
                    expect(jQuery('i.test-icon-class', toolbar1).length).toBe(1);
                    expect(jQuery('i.test-icon-class', toolbar2).length).toBe(0);
                });
                it('should reset one toolbar button on one toolbar', function(){
                    textAngularManager.updateToolbarToolDisplay('test1', 'h1', {iconclass: 'test-icon-class'});
                    textAngularManager.updateToolbarToolDisplay('test1', 'h2', {iconclass: 'test-icon-class2'});
                    textAngularManager.resetToolbarToolDisplay('test1', 'h1');
                    expect(jQuery('[name="h1"] i.test-icon-class', toolbar1).length).toBe(0);
                    expect(jQuery('[name="h1"] i.test-icon-class', toolbar2).length).toBe(0);
                    expect(jQuery('[name="h2"] i.test-icon-class2', toolbar1).length).toBe(1);
                });
            });
            describe('multi toolbar', function(){
                it('should update only one button on multiple toolbars', function(){
                    textAngularManager.updateToolDisplay('h1', {iconclass: 'test-icon-class'});
                    expect(jQuery('[name="h1"] i.test-icon-class', toolbar1).length).toBe(1);
                    expect(jQuery('[name="h1"] i.test-icon-class', toolbar2).length).toBe(1);
                });
                it('should reset one toolbar button', function(){
                    textAngularManager.updateToolDisplay('h1', {iconclass: 'test-icon-class'});
                    textAngularManager.updateToolDisplay('h2', {iconclass: 'test-icon-class2'});
                    textAngularManager.resetToolDisplay('h1');
                    expect(jQuery('[name="h1"] i.test-icon-class', toolbar1).length).toBe(0);
                    expect(jQuery('[name="h1"] i.test-icon-class', toolbar2).length).toBe(0);
                    expect(jQuery('[name="h2"] i.test-icon-class2', toolbar1).length).toBe(1);
                });
                it('should update multiple buttons on multiple toolbars', function(){
                    textAngularManager.updateToolsDisplay({'h1': {iconclass: 'test-icon-class'},'h2': {iconclass: 'test-icon-class2'}});
                    expect(jQuery('[name="h1"] i.test-icon-class, [name="h2"] i.test-icon-class2', toolbar1).length).toBe(2);
                    expect(jQuery('[name="h1"] i.test-icon-class, [name="h2"] i.test-icon-class2', toolbar2).length).toBe(2);
                });
                it('should reset all toolbar buttons', function(){
                    textAngularManager.updateToolsDisplay({'h1': {iconclass: 'test-icon-class'},'h2': {iconclass: 'test-icon-class2'}});
                    textAngularManager.resetToolsDisplay();
                    expect(jQuery('[name="h1"] i.test-icon-class, [name="h2"] i.test-icon-class2', toolbar1).length).toBe(0);
                    expect(jQuery('[name="h1"] i.test-icon-class, [name="h2"] i.test-icon-class2', toolbar2).length).toBe(0);
                });
            });

            describe('dynamically add tool', function(){
                it('to all toolbars', function(){
                    textAngularManager.addTool('newtool', {buttontext: 'Test Add Tool'}, 0, 0);
                    expect(toolbar1.find('[name="newtool"]').length).toBe(1);
                    expect(toolbar2.find('[name="newtool"]').length).toBe(1);
                });
                it('specifically to one toolbar', function(){
                    textAngularManager.addToolToToolbar('newtool', {buttontext: 'Test Add Tool'}, 'test1', 0, 0);
                    expect(toolbar1.find('[name="newtool"]').length).toBe(1);
                    expect(toolbar2.find('[name="newtool"]').length).toBe(0);
                });
            });

            describe('dynamically remove tool', function(){
                it('from all toolbars', function(){
                    textAngularManager.addTool('newtool', {buttontext: 'Test Add Tool'}, 0, 0);
                    textAngularManager.removeTool('newtool');
                    expect(toolbar1.find('[name="newtool"]').length).toBe(0);
                    expect(toolbar2.find('[name="newtool"]').length).toBe(0);
                });
                it('when only on one toolbar', function(){
                    textAngularManager.addToolToToolbar('newtool', {buttontext: 'Test Add Tool'}, 'test1', 0, 0);
                    textAngularManager.removeTool('newtool');
                    expect(toolbar1.find('[name="newtool"]').length).toBe(0);
                    expect(toolbar2.find('[name="newtool"]').length).toBe(0);
                });
            });
        });
    });

    describe('editor', function(){
        describe('registration', function(){
            it('should require a name', inject(function(textAngularManager){
                expect(textAngularManager.registerEditor).toThrow("textAngular Error: An editor requires a name");
                expect(function(){textAngularManager.registerEditor('');}).toThrow("textAngular Error: An editor requires a name");
            }));

            it('should require a scope object', inject(function(textAngularManager){
                expect(function(){textAngularManager.registerEditor('test');}).toThrow("textAngular Error: An editor requires a scope");
            }));

            it('should require a unique name', inject(function(textAngularManager){
                textAngularManager.registerEditor('test', {});
                expect(function(){textAngularManager.registerEditor('test', {});}).toThrow('textAngular Error: An Editor with name "test" already exists');
            }));

            it('should return a disable function', inject(function(textAngularManager){
                expect(textAngularManager.registerEditor('test', {}).disable).toBeDefined();
            }));

            it('should return a enable function', inject(function(textAngularManager){
                expect(textAngularManager.registerEditor('test', {}).enable).toBeDefined();
            }));

            it('should return a focus function', inject(function(textAngularManager){
                expect(textAngularManager.registerEditor('test', {}).focus).toBeDefined();
            }));

            it('should return a unfocus function', inject(function(textAngularManager){
                expect(textAngularManager.registerEditor('test', {}).unfocus).toBeDefined();
            }));

            it('should return a updateSelectedStyles function', inject(function(textAngularManager){
                expect(textAngularManager.registerEditor('test', {}).updateSelectedStyles).toBeDefined();
            }));
        });

        describe('retrieval', function(){
            it('should be undefined for no registered editor', inject(function(textAngularManager){
                expect(textAngularManager.retrieveEditor('test')).toBeUndefined();
            }));

            it('should get the correct editor', inject(function(textAngularManager){
                var scope = {};
                textAngularManager.registerEditor('test', scope);
                expect(textAngularManager.retrieveEditor('test').scope).toBe(scope);
            }));
        });

        describe('unregister', function(){
            it('should get the correct editor', inject(function(textAngularManager){
                textAngularManager.registerEditor('test', {});
                textAngularManager.unregisterEditor('test');
                expect(textAngularManager.retrieveEditor('test')).toBeUndefined();
            }));
        });

        describe('interacting', function(){
            var $rootScope, textAngularManager, editorFuncs, testbar1, testbar2, testbar3;
            var editorScope = {};
            var $log;
            beforeEach(inject(function(_textAngularManager_, _$log_){
                textAngularManager = _textAngularManager_;
                $log = _$log_;
            }));
            afterEach(inject(function(){
                // turn on to display log messages when needed
                //console.log($log.debug.logs);
            }));

            describe('active state', function(){
                beforeEach(inject(function (_$rootScope_) {
                    $rootScope = _$rootScope_;
                    textAngularManager.registerToolbar((testbar1 = {name: 'testbar1', disabled: true}));
                    textAngularManager.registerToolbar((testbar2 = {name: 'testbar2', disabled: true}));
                    textAngularManager.registerToolbar((testbar3 = {name: 'testbar3', disabled: true}));
                    editorFuncs = textAngularManager.registerEditor('test', editorScope, ['testbar1','testbar2']);
                    $rootScope.$digest();
                }));
                describe('focus', function(){
                    beforeEach(function(){
                        editorFuncs.focus();
                        $rootScope.$digest();
                    });
                    it('should set disabled to false on toolbars', function(){
                        expect(!testbar1.disabled);
                        expect(!testbar2.disabled);
                        expect(testbar3.disabled);
                    });
                    it('should set the active editor to the editor', function(){
                        expect(testbar1._parent).toBe(editorScope);
                        expect(testbar2._parent).toBe(editorScope);
                        // syntax change Jasmine 1.x -> 2.x ==> toNotBe -> not.toBe
                        expect(testbar3._parent).not.toBe(editorScope);
                    });
                });
                describe('unfocus', function(){
                    beforeEach(function(){
                        editorFuncs.unfocus();
                        $rootScope.$digest();
                    });
                    it('should set disabled to false on toolbars', function(){
                        expect(testbar1.disabled);
                        expect(testbar2.disabled);
                        expect(!testbar3.disabled);
                    });
                });
                describe('disable', function(){
                    beforeEach(function(){
                        editorFuncs.disable();
                        $rootScope.$digest();
                    });
                    it('should set disabled to false on toolbars', function(){
                        expect(testbar1.disabled).toBe(true);
                        expect(testbar2.disabled).toBe(true);
                        expect(testbar3.disabled).toBe(true);
                    });
                });
                describe('enable', function(){
                    beforeEach(function(){
                        editorFuncs.disable();
                        $rootScope.$digest();
                        editorFuncs.enable();
                        $rootScope.$digest();
                    });
                    it('should set disabled to false on toolbars', function(){
                        expect(testbar1.disabled).toBe(false);
                        expect(testbar2.disabled).toBe(false);
                        expect(testbar3.disabled).toBe(true);
                    });
                });
            });

            describe('actions passthrough', function(){
                var editorScope, element;
                beforeEach(inject(function(taRegisterTool, taOptions, _$rootScope_, _$compile_){
                    // add a tool that is ALLWAYS active
                    taRegisterTool('activeonrangyrange', {
                        buttontext: 'Active On Rangy Rangy',
                        action: function(){
                            return this.$element.attr('hit-this', 'true');
                        },
                        commandKeyCode: 21,
                        activeState: function(rangyrange){ return rangyrange !== undefined; }
                    });
                    taRegisterTool('inactiveonrangyrange', {
                        buttontext: 'Inactive On Rangy Rangy',
                        action: function(){
                            return this.$element.attr('hit-this', 'true');
                        },
                        commandKeyCode: 23,
                        activeState: function(rangyrange){ return rangyrange === undefined; }
                    });
                    taRegisterTool('noactivestate', {
                        buttontext: 'Shouldnt error, Shouldnt be active either',
                        action: function(){
                            return this.$element.attr('hit-this', 'true');
                        }
                    });
                    taRegisterTool('onselect', {
                        buttontext: 'Active on element select',
                        action: function(){
                            return this.$element.attr('hit-this', 'true');
                        },
                        onElementSelect: {
                            element: 'i',
                            action: function(event, element, editorScope){
                                return this.$element.attr('hit-this', 'true');
                            }
                        }
                    });
                    taRegisterTool('onselectattr', {
                        buttontext: 'Active on element with attr select',
                        action: function(){
                            return this.$element.attr('hit-this', 'true');
                        },
                        onElementSelect: {
                            element: 'i',
                            onlyWithAttrs: ['src'],
                            action: function(event, element, editorScope){
                                return this.$element.attr('hit-this', 'true');
                            }
                        }
                    });
                    taRegisterTool('onselectattr_specific', {
                        buttontext: 'Active on element select',
                        action: function(){
                            return this.$element.attr('hit-this', 'true');
                        },
                        onElementSelect: {
                            element: 'i',
                            onlyWithAttrs: ['src','href'],
                            action: function(event, element, editorScope){
                                return this.$element.attr('hit-this', 'true');
                            }
                        }
                    });
                    taRegisterTool('specialkey', {
                        buttontext: 'specialkey',
                        action: function(){
                            return this.$element.attr('hit-this', 'true');
                        },
                        commandKeyCode: 'TabKey',
                        onElementSelect: {
                            element: 'i',
                            onlyWithAttrs: ['src','href'],
                            action: function(event, element, editorScope){
                                return this.$element.attr('hit-this', 'true');
                            }
                        }
                    });
                    taRegisterTool('unused', {
                        buttontext: 'Button Is Not Used',
                        action: function(){
                            throw('Error Should Not Run');
                        },
                        commandKeyCode: 42,
                        onElementSelect: {
                            element: 'b',
                            action: function(event, element, editorScope){
                                throw('Error Should Not Run');
                            }
                        }
                    });
                    taOptions.toolbar = [['noactivestate','activeonrangyrange','inactiveonrangyrange','onselect','onselectattr','onselectattr_specific', 'specialkey']];
                    $rootScope = _$rootScope_;
                    element = jQuery(_$compile_('<text-angular name="test"><p>Test Content</p></text-angular>')($rootScope)[0]);
                    $rootScope.$digest();
                    editorScope = textAngularManager.retrieveEditor('test');
                }));

                describe('updateSelectedStyles', function(){
                    describe('should activate buttons correctly', function(){
                        it('without rangyrange passed through', function(){
                            editorScope.editorFunctions.updateSelectedStyles();
                            $rootScope.$digest();
                            expect(element.find('.ta-toolbar button.active').length).toBe(1);
                        });
                        it('with rangyrange passed through', function(){
                            editorScope.editorFunctions.updateSelectedStyles({});
                            $rootScope.$digest();
                            expect(element.find('.ta-toolbar button.active').length).toBe(1);
                        });
                    });
                });

                describe('updateSelectedStyles through textAngularMananger', function(){
                    describe('should activate buttons correctly', function(){
                        it('without rangyrange passed through', function(){
                            textAngularManager.updateStyles();
                            $rootScope.$digest();
                            expect(element.find('.ta-toolbar button.active').length).toBe(1);
                        });
                        it('with rangyrange passed through', function(){
                            textAngularManager.updateStyles({});
                            $rootScope.$digest();
                            expect(element.find('.ta-toolbar button.active').length).toBe(1);
                        });
                    });
                });

                describe('sendKeyCommand', function(){
                    it('should return true if there is a relevantCommandKeyCode on a tool', function(){
                        expect(editorScope.editorFunctions.sendKeyCommand({metaKey: true, which: 21})).toBe(true);
                    });

                    it('should call the action of the specified tool', function(){
                        editorScope.editorFunctions.sendKeyCommand({metaKey: true, which: 21});
                        $rootScope.$digest();
                        expect(element.find('.ta-toolbar button[name=activeonrangyrange]').attr('hit-this')).toBe('true');
                    });

                    it('should react only when modifiers present', function(){
                        editorScope.editorFunctions.sendKeyCommand({which: 21});
                        $rootScope.$digest();
                        expect(element.find('.ta-toolbar button[name=activeonrangyrange]').attr('hit-this')).toBeUndefined();
                    });

                    it('should react to metaKey', function(){
                        editorScope.editorFunctions.sendKeyCommand({metaKey: true, which: 21});
                        $rootScope.$digest();
                        expect(element.find('.ta-toolbar button[name=activeonrangyrange]').attr('hit-this')).toBe('true');
                    });

                    it('should react to ctrlKey', function(){
                        editorScope.editorFunctions.sendKeyCommand({ctrlKey: true, which: 21});
                        $rootScope.$digest();
                        expect(element.find('.ta-toolbar button[name=activeonrangyrange]').attr('hit-this')).toBe('true');
                    });

                    it('should react to tabKey', function(){
                        var event = {shiftKey: false, which: 9, specialKey: 'TabKey',
                            preventDefault: function () {}};
                        var fakeEditorScope =  { updateSelectedStyles: function() {}};
                        textAngularManager.sendKeyCommand(angular.extend(editorScope.scope, fakeEditorScope), event);
                        $rootScope.$digest();
                        expect(element.find('.ta-toolbar button[name=specialkey]').attr('hit-this')).toBe('true');
                    });
                });

                describe('onElementSelect', function(){
                    it('should do nothing if button not added', function(){
                        expect(function(){
                            editorScope.editorFunctions.triggerElementSelect({}, '<b>');
                        }).not.toThrow();
                    });

                    it('should return true if there is a relevant select element on a tool', function(){
                        expect(editorScope.editorFunctions.triggerElementSelect({}, '<i>')).toBe(true);
                    });

                    it('should call the onElementSelect.action handler of defined tools', function(){
                        editorScope.editorFunctions.triggerElementSelect({}, '<i>');
                        $rootScope.$digest();
                        expect(element.find('.ta-toolbar button[name=onselect]').attr('hit-this')).toBe('true');
                    });

                    it('should call the onElementSelect.action handler of defined tools with attr', function(){
                        editorScope.editorFunctions.triggerElementSelect({}, '<i src="test">');
                        $rootScope.$digest();
                        expect(element.find('.ta-toolbar button[name=onselect]').attr('hit-this')).toBeUndefined();
                        expect(element.find('.ta-toolbar button[name=onselectattr]').attr('hit-this')).toBe('true');
                    });

                    it('should call only the most specific handler when ambigious', function(){
                        editorScope.editorFunctions.triggerElementSelect({}, '<i src="test" href="test">');
                        $rootScope.$digest();
                        expect(element.find('.ta-toolbar button[name=onselectattr_specific]').attr('hit-this')).toBe('true');
                        expect(element.find('.ta-toolbar button[name=onselectattr]').attr('hit-this')).toBeUndefined();
                    });

                    it('should not call more specific', function(){
                        editorScope.editorFunctions.triggerElementSelect({}, '<i src="test">');
                        $rootScope.$digest();
                        expect(element.find('.ta-toolbar button[name=onselectattr_specific]').attr('hit-this')).toBeUndefined();
                        expect(element.find('.ta-toolbar button[name=onselectattr]').attr('hit-this')).toBe('true');
                    });
                });
            });
        });

        describe('linking toolbar to existing editor', function(){
            it('should link when referenced', inject(function(textAngularManager) {
                var scope = {name: 'test'};
                textAngularManager.registerEditor('testeditor', {}, ['test']);
                textAngularManager.registerToolbar(scope);
                expect(textAngularManager.retrieveToolbarsViaEditor('testeditor')[0]).toBe(scope);
            }));
            it('should not link when not referenced', inject(function(textAngularManager) {
                var scope = {name: 'test'};
                textAngularManager.registerEditor('testeditor', {}, []);
                textAngularManager.registerToolbar(scope);
                expect(textAngularManager.retrieveToolbarsViaEditor('testeditor').length).toBe(0);
            }));
        });

        describe('updating', function(){
            var $rootScope, element;
            beforeEach(inject(function (_$compile_, _$rootScope_) {
                $rootScope = _$rootScope_;
                $rootScope.htmlcontent = '<p>Test Content</p>';
                element = _$compile_('<text-angular name="test" ng-model="htmlcontent"></text-angular>')($rootScope);
                $rootScope.$digest();
            }));
            it('should throw error for named editor that doesn\'t exist', inject(function(textAngularManager){
                expect(function(){textAngularManager.refreshEditor('non-editor');}).toThrow('textAngular Error: No Editor with name "non-editor" exists');
            }));
            it('should update from text view to model', inject(function(textAngularManager){
                jQuery('.ta-text .ta-bind', element[0]).append('<div>Test 2 Content</div>');
                textAngularManager.refreshEditor('test');
                expect($rootScope.htmlcontent).toBe('<p>Test Content</p><div>Test 2 Content</div>');
            }));
        });
    });
});
