/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
(function () {
  'use strict';

  describe('actions directive', function () {
    var $scope, $compile, $q, $templateCache, basePath;

    var rowItem = {id: 'row'};
    var customItem = {id: 'custom'};
    var callback = jasmine.createSpy('callback');

    beforeEach(module('templates'));
    beforeEach(module('horizon.framework'));

    beforeEach(inject(function ($injector) {
      $compile = $injector.get('$compile');
      basePath = $injector.get('horizon.framework.widgets.basePath');
      $scope = $injector.get('$rootScope').$new();
      $q = $injector.get('$q');
      $templateCache = $injector.get('$templateCache');
    }));

    it('should have no buttons if there are no actions', function () {
      var element = batchElementFor([]);
      expect(element.children().length).toBe(0);
    });

    it('should allow for specifying by url for batch', function () {
      $scope.customItem = customItem;
      var element = batchElementFor([permittedActionWithUrl('custom')]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toBeGreaterThan(-1);
      expect(actionList.find('button').attr('class')).toEqual('btn-custom');
      expect(actionList.find('button').attr('ng-click')).toEqual('disabled || callback(item)');
      expect(actionList.text().trim()).toEqual('Custom Button');

      actionList.find('button').click();
      expect(callback).toHaveBeenCalledWith(customItem);
    });

    it('should allow for specifying by url for row', function () {
      $scope.customItem = customItem;
      var element = rowElementFor([
        permittedActionWithUrl('custom2'),
        permittedActionWithUrl('custom')
      ]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toEqual(-1);
      expect(actionList.find('button.btn-custom-2.split-button').text().trim())
        .toEqual('Custom Button 2');
      expect(actionList.find('li .btn-custom').text().trim()).toEqual('Custom Button');

      actionList.find('button.btn-custom-2.split-button').click();
      expect(callback).toHaveBeenCalledWith(undefined);

      actionList.find('li .btn-custom').click();
      expect(callback).toHaveBeenCalledWith(customItem);
    });

    it('should allow for specifying action text', function () {
      var element = batchElementFor([permittedActionWithText('Create Image')]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toBeGreaterThan(-1);
      expect(actionList.find('button').attr('ng-click')).toEqual('disabled || callback(item)');
      expect(actionList.text().trim()).toEqual('Create Image');

      actionList.find('button').click();
      expect(callback).toHaveBeenCalled();
    });

    it('should allow for specifying by template for create', function () {
      var element = batchElementFor([permittedActionWithType('create', 'Create Image')]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toBeGreaterThan(-1);
      expect(actionList.find('button').attr('ng-click')).toEqual('disabled || callback(item)');
      expect(actionList.text().trim()).toEqual('Create Image');
    });

    it('should allow for specifying by template for delete-selected', function () {
      var element = batchElementFor([permittedActionWithType('delete-selected', 'Delete Images')]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toBeGreaterThan(-1);
      expect(actionList.find('button').attr('ng-click')).toEqual('disabled || callback(item)');
      expect(actionList.text().trim()).toEqual('Delete Images');
    });

    it('should allow for specifying by template for delete', function () {
      var element = rowElementFor([permittedActionWithType('delete', 'Delete Image')]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toBeGreaterThan(-1);
      expect(actionList.find('button').attr('ng-click')).toEqual('disabled || callback(item)');
      expect(actionList.text().trim()).toEqual('Delete Image');

      actionList.find('button').click();
      expect(callback).toHaveBeenCalledWith(rowItem);
    });

    it('should allow for specifying by template for danger', function () {
      var element = rowElementFor([permittedActionWithType('danger', 'Shutdown Instance')]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toBeGreaterThan(-1);
      expect(actionList.find('button').attr('ng-click')).toEqual('disabled || callback(item)');
      expect(actionList.text().trim()).toEqual('Shutdown Instance');

      actionList.find('button').click();
      expect(callback).toHaveBeenCalledWith(rowItem);
    });

    it('should have one button if there is one action', function () {
      var element = batchElementFor([
        permittedActionWithType('create', 'Create Image')
      ]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toBeGreaterThan(-1);
      expect(actionList.find('button').attr('ng-click')).toEqual('disabled || callback(item)');
      expect(actionList.text().trim()).toEqual('Create Image');
    });

    it('should have no buttons if not permitted', function () {
      var element = batchElementFor([notPermittedAction()]);

      expect(element.children().length).toBe(0);
    });

    it('should have multiple buttons for multiple actions as a list', function () {
      var element = batchElementFor([
        permittedActionWithType('create', 'Create Image'),
        permittedActionWithType('delete-selected', 'Delete Images')
      ]);

      expect(element.children().length).toBe(2);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(2);
      expect(actionList.attr('class').indexOf('btn-addon')).toBeGreaterThan(-1);
      expect(actionList.find('button.btn-default').text().trim()).toEqual('Create Image');
      expect(actionList.find('button.btn-danger').text().trim()).toEqual('Delete Images');
    });

    it('should bind multiple callbacks for multiple buttons in a batch', function () {
      var callback1 = jasmine.createSpy('callback1');
      var callback2 = jasmine.createSpy('callback2');
      var element = batchElementFor([
        permittedActionWithText('Action 1', 'btn-1', callback1),
        permittedActionWithText('Action 2', 'btn-2', callback2)
      ]);

      expect(element.children().length).toBe(2);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(2);

      actionList.find('button.btn-1').click();
      expect(callback1).toHaveBeenCalled();

      actionList.find('button.btn-2').click();
      expect(callback2).toHaveBeenCalled();
    });

    it('should have as many buttons as permitted', function () {
      var element = batchElementFor([
        permittedActionWithType('create', 'Create Image'),
        notPermittedAction()
      ]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toBeGreaterThan(-1);
      expect(actionList.find('button.btn-default').text().trim()).toEqual('Create Image');
    });

    it('should have multiple buttons as a dropdown with correct styling', function () {
      var element = rowElementFor([
        permittedActionWithText('Edit Instance', 'btn-custom'),
        permittedActionWithType('danger', 'Shutdown Instance')
      ]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toEqual(-1);
      expect(actionList.find('button.split-button.btn-custom').text().trim())
        .toEqual('Edit Instance');
      expect(actionList.find('li a.text-danger').text().trim()).toEqual('Shutdown Instance');
    });

    it('should style danger type button as button in a dropdown', function () {
      var element = rowElementFor([
        permittedActionWithType('danger', 'Shutdown Instance'),
        permittedActionWithText('Edit Instance', 'btn-custom')
      ]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toEqual(-1);
      expect(actionList.find('button.split-button.btn-danger').text().trim())
        .toEqual('Shutdown Instance');
      expect(actionList.find('li a.btn-custom').text().trim()).toEqual('Edit Instance');
    });

    it('should have multiple buttons as a dropdown for actions text', function () {
      var element = rowElementFor([
        permittedActionWithText('Create Image'),
        permittedActionWithText('Delete Image', 'text-danger')
      ]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toEqual(-1);
      expect(actionList.find('button').first().text().trim()).toEqual('Create Image');
      expect(actionList.find('li a.text-danger').text().trim()).toEqual('Delete Image');
    });

    it('should bind callbacks per button for dropdowns', function () {
      var callback1 = jasmine.createSpy('callback1');
      var callback2 = jasmine.createSpy('callback2');
      var element = rowElementFor([
        permittedActionWithText('Action 1', 'btn-1', callback1),
        permittedActionWithText('Action 2', 'btn-2', callback2)
      ]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);

      actionList.find('.btn-1').click();
      expect(callback1).toHaveBeenCalledWith(rowItem);

      actionList.find('li .btn-2').click();
      expect(callback2).toHaveBeenCalledWith(rowItem);
    });

    it('should have one button if only one permitted for dropdown', function () {
      var element = rowElementFor([
        permittedActionWithText('Single Action', 'btn-custom'),
        notPermittedAction()
      ]);

      expect(element.children().length).toBe(1);
      var actionList = element.find('action-list');
      expect(actionList.length).toBe(1);
      expect(actionList.attr('class').indexOf('btn-addon')).toBeGreaterThan(-1);
      expect(actionList.find('button.btn-custom').text().trim()).toEqual('Single Action');
    });

    it('should disable actions while an action is performed', function() {
      var def = $q.defer();
      var callbacks = {
        first: function() {
          return def.promise;
        }
      };
      spyOn(callbacks, 'first').and.callThrough();
      var element = rowElementFor([
        permittedActionWithText('Action 1', 'btn-1', callbacks.first)
      ]);
      var ctrl = element.scope().actionsCtrl;

      // Initially actions should not be disabled
      expect(ctrl.disabled).toBe(false);

      // Perform the action
      element.find('.btn-1').click();
      expect(callbacks.first).toHaveBeenCalled();

      // Actions should be disabled
      expect(ctrl.disabled).toBe(true);
      callbacks.first.calls.reset();
      element.find('.btn-1').click();
      expect(callbacks.first).not.toHaveBeenCalled();

      // Resolve the action
      def.resolve();
      $scope.$apply();

      // Actions should be enabled again
      expect(ctrl.disabled).toBe(false);
      callbacks.first.calls.reset();
      element.find('.btn-1').click();
      expect(callbacks.first).toHaveBeenCalled();
    });

    it('should render detail actions', function () {
      var actions = [{
        template: {
          text: 'Action 1',
          title: 'Do something cool',
          description: 'This describes what that cool thing is you can do.'
        },
        service: getService(getPermission(true), callback)
      },{
        template: {
          text: 'Action 2',
          title: 'Do something dangerous',
          type: 'danger',
          description: 'This describes what that dangerous thing is you can do.'
        },
        service: getService(getPermission(true), callback)
      }];
      var element = rowElementFor(actions, true);

      expect(element.find('.panel').length).toBe(2);
      expect(element.find('.panel-title').first().text().trim()).toBe('Do something cool');
      expect(element.find('.panel-title').last().text().trim()).toBe('Do something dangerous');
      expect(element.find('.panel-body button').first().text().trim()).toBe('Action 1');
      expect(element.find('.panel-body button').last().text().trim()).toBe('Action 2');
      expect(element.find('.panel').first().hasClass('panel-info')).toBe(true);
      expect(element.find('.panel').last().hasClass('panel-danger')).toBe(true);
      expect(element.find('.panel-body button').first().hasClass('btn-primary')).toBe(true);
      expect(element.find('.panel-body button').last().hasClass('btn-danger')).toBe(true);
    });

    function permittedActionWithUrl(templateName) {
      return {
        template: {
          url: getTemplatePath("actions." + templateName)
        },
        service: getService(getPermission(true), callback)
      };
    }

    function permittedActionWithText(text, actionClasses, actionCallback) {
      return {
        template: {
          text: text,
          actionClasses: actionClasses
        },
        service: getService(getPermission(true), actionCallback || callback)
      };
    }

    function permittedActionWithType(templateType, text, actionCallback) {
      return {
        template: {
          type: templateType,
          text: text
        },
        service: getService(getPermission(true), actionCallback || callback)
      };
    }

    function notPermittedAction() {
      return {
        template: 'dummy',
        service: getService(getPermission(false), callback)
      };
    }

    function getService(permissions, callback) {
      return {
        allowed: function(args) {
          if (args) {
            expect(args).toEqual(rowItem);
          }
          return permissions;
        },
        perform: function(args) {
          return callback(args);
        }
      };
    }

    function getPermission(allowed) {
      var deferred = $q.defer();

      if (allowed) {
        deferred.resolve();
      } else {
        deferred.reject();
      }

      return deferred.promise;
    }

    function batchElementFor(actions) {
      $scope.actions = function() {
        return actions;
      };

      var element = angular.element(getTemplate('actions.batch'));

      $compile(element)($scope);
      $scope.$apply();

      return element;
    }

    function rowElementFor(actions, detail) {
      $scope.rowItem = rowItem;
      $scope.actions = function() {
        return actions;
      };

      var element = angular.element(getTemplate(detail ? 'actions.detail' : 'actions.row'));

      $compile(element)($scope);
      $scope.$apply();

      return element;
    }

    function getTemplate(templateName) {
      return $templateCache.get(getTemplatePath(templateName));
    }

    function getTemplatePath(templateName) {
      return basePath + 'action-list/' + templateName + '.mock.html';
    }

  });
})();
