/*global define,Promise*/

/**
 * Module defining TreeNodeController. Created by vwoeltje on 11/10/14.
 */
define(
    [],
    function () {
        "use strict";

        /**
         * The TreeNodeController supports the tree node representation;
         * a tree node has a label for the current object as well as a
         * subtree which shows (and is not loaded until) the node is
         * expanded.
         *
         * This controller tracks the following, so that the tree node
         * template may update its state accordingly:
         *
         * * Whether or not the tree node has ever been expanded (this
         *   is used to lazily load, exactly once, the subtree)
         * * Whether or not the node is currently the domain object
         *   of navigation (this gets highlighted differently to
         *   provide the user with visual feedback.)
         *
         * Additionally, this controller will automatically trigger
         * node expansion when this tree node's _subtree_ will contain
         * the navigated object (recursively, this becomes an
         * expand-to-show-navigated-object behavior.)
         * @constructor
         */
        function TreeNodeController($scope, $timeout) {
            var selectedObject = ($scope.ngModel || {}).selectedObject,
                isSelected = false,
                hasBeenExpanded = false;

            // Look up the id for a domain object. A convenience
            // for mapping; additionally does some undefined-checking.
            function getId(obj) {
                return obj && obj.getId && obj.getId();
            }

            // Verify that id paths are equivalent, staring at
            // index, ending at the end of the node path.
            function checkPath(nodePath, navPath, index) {
                index = index || 0;

                // The paths overlap if we have made it past the
                // end of the node's path; otherwise, check the
                // id at the current index for equality and perform
                // a recursive step for subsequent ids in the paths,
                // until we exceed path length or hit a mismatch.
                return (index >= nodePath.length) ||
                        ((navPath[index] === nodePath[index]) &&
                                checkPath(nodePath, navPath, index + 1));
            }

            // Track that a node has been expanded, either by the
            // user or automatically to show a selection.
            function trackExpansion() {
                if (!hasBeenExpanded) {
                    // Run on a timeout; if a lot of expansion needs to
                    // occur (e.g. if the selection is several nodes deep) we
                    // want this to be spread across multiple digest cycles.
                    $timeout(function () { hasBeenExpanded = true; }, 0);
                }
            }

            // Consider the currently-navigated object and update
            // parameters which support display.
            function checkSelection() {
                var nodeObject = $scope.domainObject,
                    navObject = selectedObject,
                    nodeContext = nodeObject &&
                            nodeObject.getCapability('context'),
                    navContext = navObject &&
                            navObject.getCapability('context'),
                    nodePath,
                    navPath;

                // Deselect; we will reselect below, iff we are
                // exactly at the end of the path.
                isSelected = false;

                // Expand if necessary (if the navigated object will
                // be in this node's subtree)
                if (nodeContext && navContext) {
                    // Get the paths as arrays of identifiers
                    nodePath = nodeContext.getPath().map(getId);
                    navPath = navContext.getPath().map(getId);

                    // Check to see if the node's path lies entirely
                    // within the navigation path; otherwise, navigation
                    // has happened in some other subtree.
                    if (navPath.length >= nodePath.length &&
                            checkPath(nodePath, navPath)) {

                        // nodePath is along the navPath; if it's
                        // at the end of the path, highlight;
                        // otherwise, expand.
                        if (nodePath.length === navPath.length) {
                            isSelected = true;
                        } else { // node path is shorter: Expand!
                            if ($scope.toggle) {
                                $scope.toggle.setState(true);
                            }
                            trackExpansion();
                        }

                    }
                }
            }

            // Callback for the selection updates; track the currently
            // navigated object and update display parameters as needed.
            function setSelection(object) {
                selectedObject = object;
                checkSelection();
            }

            // Listen for changes which will effect display parameters
            $scope.$watch("ngModel.selectedObject", setSelection);
            $scope.$watch("domainObject", checkSelection);

            return {
                /**
                 * This method should be called when a node is expanded
                 * to record that this has occurred, to support one-time
                 * lazy loading of the node's subtree.
                 */
                trackExpansion: trackExpansion,
                /**
                 * Check if this not has ever been expanded.
                 * @returns true if it has been expanded
                 */
                hasBeenExpanded: function () {
                    return hasBeenExpanded;
                },
                /**
                 * Check whether or not the domain object represented by
                 * this tree node should be highlighted.
                 * An object will be highlighted if it matches
                 * ngModel.selectedObject
                 * @returns true if this should be highlighted
                 */
                isSelected: function () {
                    return isSelected;
                }
            };
        }

        return TreeNodeController;
    }
);