/*****************************************************************************
 * 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*/

/**
 * MCTRepresentationSpec. Created by vwoeltje on 11/6/14.
 */
define(
    ["../src/BrowseController"],
    function (BrowseController) {
        "use strict";

        describe("The browse controller", function () {
            var mockScope,
                mockRoute,
                mockLocation,
                mockObjectService,
                mockNavigationService,
                mockRootObject,
                mockUrlService,
                mockDomainObject,
                mockNextObject,
                controller;

            function mockPromise(value) {
                return {
                    then: function (callback) {
                        return mockPromise(callback(value));
                    }
                };
            }

            beforeEach(function () {
                mockScope = jasmine.createSpyObj(
                    "$scope",
                    [ "$on", "$watch" ]
                );
                mockRoute = { current: { params: {} } };
                mockLocation = jasmine.createSpyObj(
                    "$location",
                    [ "path" ]
                );
                mockUrlService = jasmine.createSpyObj(
                    "urlService",
                    ["urlForLocation"]
                );
                mockObjectService = jasmine.createSpyObj(
                    "objectService",
                    [ "getObjects" ]
                );
                mockNavigationService = jasmine.createSpyObj(
                    "navigationService",
                    [
                        "getNavigation",
                        "setNavigation",
                        "addListener",
                        "removeListener"
                    ]
                );
                mockRootObject = jasmine.createSpyObj(
                    "domainObject",
                    [ "getId", "getCapability", "getModel", "useCapability" ]
                );
                mockDomainObject = jasmine.createSpyObj(
                    "domainObject",
                    [ "getId", "getCapability", "getModel", "useCapability" ]
                );
                mockNextObject = jasmine.createSpyObj(
                    "nextObject",
                    [ "getId", "getCapability", "getModel", "useCapability" ]
                );

                mockObjectService.getObjects.andReturn(mockPromise({
                    ROOT: mockRootObject
                }));
                mockRootObject.useCapability.andReturn(mockPromise([
                    mockDomainObject
                ]));
                mockDomainObject.useCapability.andReturn(mockPromise([
                    mockNextObject
                ]));
                mockNextObject.useCapability.andReturn(undefined);
                mockNextObject.getId.andReturn("next");
                mockDomainObject.getId.andReturn("mine");

                controller = new BrowseController(
                    mockScope,
                    mockRoute,
                    mockLocation,
                    mockObjectService,
                    mockNavigationService,
                    mockUrlService
                );
            });

            it("uses composition to set the navigated object, if there is none", function () {
                controller = new BrowseController(
                    mockScope,
                    mockRoute,
                    mockLocation,
                    mockObjectService,
                    mockNavigationService,
                    mockUrlService
                );
                expect(mockNavigationService.setNavigation)
                    .toHaveBeenCalledWith(mockDomainObject);
            });

            it("does not try to override navigation", function () {
                mockNavigationService.getNavigation.andReturn(mockDomainObject);
                controller = new BrowseController(
                    mockScope,
                    mockRoute,
                    mockLocation,
                    mockObjectService,
                    mockNavigationService,
                    mockUrlService
                );
                expect(mockScope.navigatedObject).toBe(mockDomainObject);
            });

            it("updates scope when navigated object changes", function () {
                // Should have registered a listener - call it
                mockNavigationService.addListener.mostRecentCall.args[0](
                    mockDomainObject
                );
                expect(mockScope.navigatedObject).toEqual(mockDomainObject);
            });


            it("releases its navigation listener when its scope is destroyed", function () {
                expect(mockScope.$on).toHaveBeenCalledWith(
                    "$destroy",
                    jasmine.any(Function)
                );
                mockScope.$on.mostRecentCall.args[1]();

                // Should remove the listener it added earlier
                expect(mockNavigationService.removeListener).toHaveBeenCalledWith(
                    mockNavigationService.addListener.mostRecentCall.args[0]
                );
            });

            it("uses route parameters to choose initially-navigated object", function () {
                mockRoute.current.params.ids = "mine/next";
                controller = new BrowseController(
                    mockScope,
                    mockRoute,
                    mockLocation,
                    mockObjectService,
                    mockNavigationService
                );
                expect(mockScope.navigatedObject).toBe(mockNextObject);
                expect(mockNavigationService.setNavigation)
                    .toHaveBeenCalledWith(mockNextObject);
            });

            it("handles invalid IDs by going as far as possible", function () {
                // Idea here is that if we get a bad path of IDs,
                // browse controller should traverse down it until
                // it hits an invalid ID.
                mockRoute.current.params.ids = "mine/junk";
                controller = new BrowseController(
                    mockScope,
                    mockRoute,
                    mockLocation,
                    mockObjectService,
                    mockNavigationService
                );
                expect(mockScope.navigatedObject).toBe(mockDomainObject);
                expect(mockNavigationService.setNavigation)
                    .toHaveBeenCalledWith(mockDomainObject);
            });

            it("handles compositionless objects by going as far as possible", function () {
                // Idea here is that if we get a path which passes
                // through an object without a composition, browse controller
                // should stop at it since remaining IDs cannot be loaded.
                mockRoute.current.params.ids = "mine/next/junk";
                controller = new BrowseController(
                    mockScope,
                    mockRoute,
                    mockLocation,
                    mockObjectService,
                    mockNavigationService
                );
                expect(mockScope.navigatedObject).toBe(mockNextObject);
                expect(mockNavigationService.setNavigation)
                    .toHaveBeenCalledWith(mockNextObject);
            });

            it("updates the displayed route to reflect current navigation", function () {
                var mockContext = jasmine.createSpyObj('context', ['getPath']),
                    mockUnlisten = jasmine.createSpy('unlisten'),
                    mockMode = "browse";

                mockContext.getPath.andReturn(
                    [mockRootObject, mockDomainObject, mockNextObject]
                );
                mockNextObject.getCapability.andCallFake(function (c) {
                    return c === 'context' && mockContext;
                });
                mockScope.$on.andReturn(mockUnlisten);
                // Provide a navigation change
                mockNavigationService.addListener.mostRecentCall.args[0](
                    mockNextObject
                );

                // Allows the path index to be checked
                // prior to setting $route.current
                mockLocation.path.andReturn("/browse/");

                // Exercise the Angular workaround
                mockScope.$on.mostRecentCall.args[1]();
                expect(mockUnlisten).toHaveBeenCalled();

                // location.path to be called with the urlService's
                // urlFor function with the next domainObject and mode
                expect(mockLocation.path).toHaveBeenCalledWith(
                    mockUrlService.urlForLocation(mockMode, mockNextObject)
                );
            });

        });
    }
);
