(function() {
    'use strict';

    /**
    * @ngdoc object
    * @name seco.facetedSearch.FacetHandler
    */
    angular.module('seco.facetedSearch')
    .factory('FacetHandler', FacetHandler);

    /* ngInject */
    function FacetHandler(_, EVENT_FACET_CONSTRAINTS, EVENT_FACET_CHANGED, EVENT_REQUEST_CONSTRAINTS,
            EVENT_INITIAL_CONSTRAINTS) {

        return FacetHandler;

        /**
        * @ngdoc function
        * @name seco.facetedSearch.FacetHandler
        * @constructor
        * @description
        * Service for mediating the communication between facets.
        *
        * When created, the FacetHandler object broadcasts a `sf-initial-constraints` event
        * on the given scope, with the following structure:
        *
        * - **constraint** - `{Array}` - The constraints as generated by all the facets,
        *   plus any initial constraints defined in the init configuration.
        * - **facets** - `{Object}` - A collection of facet states where the key is the
        *   `facetId` of the facet, and value is the state as emitted by that facet.
        * - **config** - `{Object}` - The configuration given when instantiating
        *   the FacetHandler.
        *
        * The FacetHandler object listens for two events:
        *
        * - **`sf-request-constraints`** - In response will broadcast a
        *   `sf-initial-constraints` event
        *   with the same object as above as the argument.
        * - **`sf-facets-changed`** - In response will broadcast a
        *   `sf-facet-constraints` event on the scope with the same object as
        *   above, except omitting `config`.
        *
        * The event names are injectable as constants:
        * - `sf-initial-constraints`: `EVENT_INITIAL_CONSTRAINTS`
        * - `sf-facet-constraints`: `EVENT_FACET_CONSTRAINTS`
        * - `sf-request-constraints`: `EVENT_REQUEST_CONSTRAINTS`
        * - `sf-facet-changed`: `EVENT_FACET_CHANGED`
        *
        * @param {Object} config Configuration object broadcast to all facets at init.
        *   The object has the following properties:
        *
        *   - **scope** - `{Object}` - The facets' parent scope. This is used for
        *   broadcasting and listening for events.
        *   - **[endpointUrl]** - `{string}` - The SPARQL endpoint URL. Optional,
        *   as it is just sent to all listening facets at init, and can also
        *   be given to facets individually.
        * - **[headers]** `{Object}` - Additional HTTP headers.
        *   - **[constraint]** - `{string}` - A SPARQL triple pattern that selects
        *   all the resources that are being faceted.
        *   - **[rdfClass]** - `{string}` - The `rdf:type` of the resources being
        *   faceted. A shorthand for `constraint: '?id a <class> .'`.
        *   - **[preferredLang]** - `{string|Array}` - The language tag that is preferred
        *   when getting labels for facet values, in case the value is a resource.
        *   If the value is an Array, the specified languages are tried in order.
        *   The default is 'en'.
        *   This argument can also be given directly to the individual facets.
        *   - **[initialState]** - `{Object}` - The initial state of the facets.
        *   Used when loading the state from URL parameters, for example.
        *   See {@link seco.facetedSearch.facetUrlStateHandlerService `facetUrlStateHandlerService`}.
        *
        *   The object is sent to facets as is, and so any extra fields will be included,
        *   although the above fields are the only ones used by FacetHandler and the
        *   built-in facets.
        */
        function FacetHandler(config) {
            var self = this;

            init();

            function init() {
                self.state = { facets: {} };

                var defaultConfig = {
                    preferredLang: 'en'
                };

                self.config = angular.extend({}, defaultConfig, config);

                self.changeListener = self.config.scope.$on(EVENT_FACET_CHANGED, update);
                self.initListener = self.config.scope.$on(EVENT_REQUEST_CONSTRAINTS, broadCastInitial);

                self.removeListeners = removeListeners;

                self.state.facets = self.config.initialState || {};
                self.state.default = getInitialConstraints(self.config);
                broadCastInitial();
            }

            // Update state, and broadcast it to listening facets.
            function update(event, constraint) {
                event.stopPropagation();
                if (!_.isEqual(self.state.facets[constraint.id], constraint)) {
                    self.state.facets[constraint.id] = constraint;
                    broadCastConstraints(EVENT_FACET_CONSTRAINTS);
                }
            }

            function broadCastInitial(event) {
                if (event) {
                    event.stopPropagation();
                }
                var data = {
                    config: self.config
                };
                broadCastConstraints(EVENT_INITIAL_CONSTRAINTS, data);
            }

            function broadCastConstraints(eventType, data) {
                data = data || {};

                var constraint = getConstraint();
                constraint.push(self.state.default);

                data.facets = self.state.facets;
                data.constraint = constraint;

                self.config.scope.$broadcast(eventType, data);
            }

            function getConstraint() {
                return _(self.state.facets).values().sortBy('priority').map('constraint').compact().value();
            }

            // Combine the possible RDF class and constraint definitions in the config.
            function getInitialConstraints(config) {
                var state = config.rdfClass ? ' ?id a ' + config.rdfClass + ' . ' : '';
                state = state + (config.constraint || '');
                return state;
            }

            /**
            * @ngdoc method
            * @methodOf seco.facetedSearch.FacetHandler
            * @name seco.facetedSearch.FacetHandler#removeListeners
            * @description
            * Remove event listeners from the scope.
            * Probably only needed if you, for some reason, need to
            * use `$rootScope` as the communication scope.
            */
            function removeListeners() {
                self.initListener();
                self.changeListener();
            }
        }
    }
})();
