/*global define*/
define(
    [],
    function () {
        "use strict";

        // Utility functions for reducing truth arrays
        function and(a, b) { return a && b; }
        function or(a, b) { return a || b; }


        /**
         * Provides initial structure and state (as suitable for provision
         * to the `mct-toolbar` directive) for a view's tool bar, based on
         * that view's declaration of what belongs in its tool bar and on
         * the current selection.
         *
         * @param structure toolbar structure, as provided by view definition
         * @param {Array} selection the current selection state
         * @param {Function} commit callback to invoke after changes
         * @constructor
         */
        function EditToolbar(structure, selection, commit) {
            var toolbarStructure = Object.create(structure || {}),
                toolbarState,
                properties = [];

            // Generate a new key for an item's property
            function addKey(property) {
                properties.push(property);
                return properties.length - 1; // Return index of property
            }

            // Update value for this property in all elements of the
            // selection which have this property.
            function updateProperties(property, value) {
                // Update property in a selected element
                function updateProperty(selected) {
                    // Ignore selected elements which don't have this property
                    if (selected[property] !== undefined) {
                        // Check if this is a setter, or just assignable
                        if (typeof selected[property] === 'function') {
                            selected[property](value);
                        } else {
                            selected[property] = value;
                        }
                    }
                }

                // Update property in all selected elements
                selection.forEach(updateProperty);
            }

            // Look up the current value associated with a property
            // in selection i
            function lookupState(property, selected) {
                var value = selected[property];
                return (typeof value === 'function') ? value() : value;
            }

            // Get initial value for a given property
            function initializeState(property) {
                var result;
                // Look through all selections for this property;
                // values should all match by the time we perform
                // this lookup anyway.
                selection.forEach(function (selected) {
                    result = (selected[property] !== undefined) ?
                            lookupState(property, selected) :
                            result;
                });
                return result;
            }

            // Check if all elements of the selection which have this
            // property have the same value for this property.
            function isConsistent(property) {
                var consistent = true,
                    observed = false,
                    state;

                // Check if a given element of the selection is consistent
                // with previously-observed elements for this property.
                function checkConsistency(selected) {
                    var next;
                    // Ignore selections which don't have this property
                    if (selected[property] !== undefined) {
                        // Look up state of this element in the selection
                        next = lookupState(property, selected);
                        // Detect inconsistency
                        if (observed) {
                            consistent = consistent && (next === state);
                        }
                        // Track state for next iteration
                        state = next;
                        observed = true;
                    }
                }

                // Iterate through selections
                selection.forEach(checkConsistency);

                return consistent;
            }

            // Used to filter out items which are applicable (or not)
            // to the current selection.
            function isApplicable(item) {
                var property = (item || {}).property,
                    method = (item || {}).method,
                    exclusive = !(item || {}).inclusive;

                // Check if a selected item defines this property
                function hasProperty(selected) {
                    return (property && (selected[property] !== undefined)) ||
                            (method && (typeof selected[method] === 'function'));
                }

                return selection.map(hasProperty).reduce(
                    exclusive ? and : or,
                    exclusive
                ) && isConsistent(property);
            }

            // Invoke all functions in selections with the given name
            function invoke(method, value) {
                if (method) {
                    // Make the change in the selection
                    selection.forEach(function (selected) {
                        if (typeof selected[method] === 'function') {
                            selected[method](value);
                        }
                    });
                    // ...and commit!
                    commit();
                }
            }

            // Prepare a toolbar item based on current selection
            function convertItem(item) {
                var converted = Object.create(item || {});
                if (item.property) {
                    converted.key = addKey(item.property);
                }
                if (item.method) {
                    converted.click = function (v) {
                        invoke(item.method, v);
                    };
                }
                return converted;
            }

            // Used to filter out sections that have become empty
            function nonEmpty(section) {
                return section && section.items && section.items.length > 0;
            }

            // Prepare a toolbar section based on current selection
            function convertSection(section) {
                var converted = Object.create(section || {});
                converted.items =
                    ((section || {}).items || [])
                            .map(convertItem)
                            .filter(isApplicable);
                return converted;
            }

            toolbarStructure.sections =
                ((structure || {}).sections || [])
                        .map(convertSection)
                        .filter(nonEmpty);

            toolbarState = properties.map(initializeState);

            return {
                /**
                 *
                 */
                getStructure: function () {
                    return toolbarStructure;
                },
                getState: function () {
                    return toolbarState;
                },
                updateState: function (key, value) {
                    updateProperties(properties[key], value);
                }
            };
        }

        return EditToolbar;
    }
);

