/*****************************************************************************
 * Open MCT Web, Copyright (c) 2014-2015, United States Government
 * as represented by the Administrator of the National Aeronautics and Space
 * Administration. All rights reserved.
 *
 * Open MCT Web is 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.
 *
 * Open MCT Web includes source code licensed under additional open source
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 * this source code distribution or the Licensing information page available
 * at runtime from the About dialog for additional information.
 *****************************************************************************/
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/

define(
    ["../../src/controllers/SelectorController"],
    function (SelectorController) {
        "use strict";

        describe("The controller for the 'selector' control", function () {
            var mockObjectService,
                mockScope,
                mockDomainObject,
                mockType,
                mockDomainObjects,
                controller;

            function promiseOf(v) {
                return (v || {}).then ? v : {
                    then: function (callback) {
                        return promiseOf(callback(v));
                    }
                };
            }

            function makeMockObject(id) {
                var mockObject = jasmine.createSpyObj(
                    'object-' + id,
                    [ 'getId' ]
                );
                mockObject.getId.andReturn(id);
                return mockObject;
            }

            beforeEach(function () {
                mockObjectService = jasmine.createSpyObj(
                    'objectService',
                    ['getObjects']
                );
                mockScope = jasmine.createSpyObj(
                    '$scope',
                    ['$watch', '$watchCollection']
                );
                mockDomainObject = jasmine.createSpyObj(
                    'domainObject',
                    [ 'getCapability', 'hasCapability' ]
                );
                mockType = jasmine.createSpyObj(
                    'type',
                    [ 'instanceOf' ]
                );
                mockDomainObjects = {};

                [ "ROOT", "abc", "def", "xyz" ].forEach(function (id) {
                    mockDomainObjects[id] = makeMockObject(id);
                });

                mockDomainObject.getCapability.andReturn(mockType);
                mockObjectService.getObjects.andReturn(promiseOf(mockDomainObjects));
                mockScope.field = "testField";
                mockScope.ngModel = {};

                controller = new SelectorController(
                    mockObjectService,
                    mockScope
                );
            });

            it("loads the root object", function () {
                expect(mockObjectService.getObjects)
                    .toHaveBeenCalledWith(["ROOT"]);
            });

            it("watches for changes in selection in left-hand tree", function () {
                var testObject = { a: 123, b: 456 };
                // This test is sensitive to ordering of watch calls
                expect(mockScope.$watch.calls.length).toEqual(1);
                // Make sure we're watching the correct object
                controller.treeModel.selectedObject = testObject;
                expect(mockScope.$watch.calls[0].args[0]()).toBe(testObject);
            });

            it("watches for changes in controlled property", function () {
                var testValue = [ "a", "b", 1, 2 ];
                // This test is sensitive to ordering of watch calls
                expect(mockScope.$watchCollection.calls.length).toEqual(1);
                // Make sure we're watching the correct object
                mockScope.ngModel = { testField: testValue };
                expect(mockScope.$watchCollection.calls[0].args[0]()).toBe(testValue);
            });

            it("rejects selection of incorrect types", function () {
                mockScope.structure = { type: "someType" };
                mockType.instanceOf.andReturn(false);
                controller.treeModel.selectedObject = mockDomainObject;
                // Fire the watch
                mockScope.$watch.calls[0].args[1](mockDomainObject);
                // Should have cleared the selection
                expect(controller.treeModel.selectedObject).toBeUndefined();
                // Verify interaction (that instanceOf got a useful argument)
                expect(mockType.instanceOf).toHaveBeenCalledWith("someType");
            });

            it("permits selection of matching types", function () {
                mockScope.structure = { type: "someType" };
                mockType.instanceOf.andReturn(true);
                controller.treeModel.selectedObject = mockDomainObject;
                // Fire the watch
                mockScope.$watch.calls[0].args[1](mockDomainObject);
                // Should have preserved the selection
                expect(controller.treeModel.selectedObject).toEqual(mockDomainObject);
                // Verify interaction (that instanceOf got a useful argument)
                expect(mockType.instanceOf).toHaveBeenCalledWith("someType");
            });

            it("loads objects when the underlying list changes", function () {
                var testIds = [ "abc", "def", "xyz" ];
                // This test is sensitive to ordering of watch calls
                expect(mockScope.$watchCollection.calls.length).toEqual(1);
                // Make sure we're watching the correct object
                mockScope.ngModel = { testField: testIds };
                // Fire the watch
                mockScope.$watchCollection.calls[0].args[1](testIds);
                // Should have loaded the corresponding objects
                expect(mockObjectService.getObjects).toHaveBeenCalledWith(testIds);
            });

            it("exposes the root object to populate the left-hand tree", function () {
                expect(controller.root()).toEqual(mockDomainObjects.ROOT);
            });

            it("adds objects to the underlying model", function () {
                expect(mockScope.ngModel.testField).toBeUndefined();
                controller.select(mockDomainObjects.def);
                expect(mockScope.ngModel.testField).toEqual(["def"]);
                controller.select(mockDomainObjects.abc);
                expect(mockScope.ngModel.testField).toEqual(["def", "abc"]);
            });

            it("removes objects to the underlying model", function () {
                controller.select(mockDomainObjects.def);
                controller.select(mockDomainObjects.abc);
                expect(mockScope.ngModel.testField).toEqual(["def", "abc"]);
                controller.deselect(mockDomainObjects.def);
                expect(mockScope.ngModel.testField).toEqual(["abc"]);
            });

            it("provides a list of currently-selected objects", function () {
                // Verify precondition
                expect(controller.selected()).toEqual([]);
                // Select some objects
                controller.select(mockDomainObjects.def);
                controller.select(mockDomainObjects.abc);
                // Fire the watch for the id changes...
                mockScope.$watchCollection.calls[0].args[1](
                    mockScope.$watchCollection.calls[0].args[0]()
                );
                // Should have loaded and exposed those objects
                expect(controller.selected()).toEqual(
                    [mockDomainObjects.def, mockDomainObjects.abc]
                );
            });
        });
    }
);