/*!
 * Copyright 2014 Apereo Foundation (AF) Licensed under the
 * Educational Community 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://opensource.org/licenses/ECL-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.
 */

var _ = require('underscore');

var AuthzAPI = require('oae-authz');
var AuthzConstants = require('oae-authz/lib/constants').AuthzConstants;
var AuthzUtil = require('oae-authz/lib/util');
var SearchAPI = require('oae-search');
var SearchUtil = require('oae-search/lib/util');

/**
 * Initializes the child search documents for the Authz module
 *
 * @param  {Function}   callback        Standard callback function
 * @param  {Object}     callback.err    An error that occurred, if any
 */
var init = module.exports.init = function(callback) {
    var membersChildSearchDocumentOptions = {
        'resourceTypes': ['content', 'discussion', 'group'],
        'schema': require('./search/schema/resourceMembersSchema'),
        'producer': function(resources, callback) {
            return _produceResourceMembersDocuments(resources.slice(), callback);
        }
    };

    var membershipsChildSearchDocumentOptions = {
        'resourceTypes': ['group', 'user'],
        'schema': require('./search/schema/resourceMembershipsSchema'),
        'producer': function(resources, callback) {
            return _produceResourceMembershipsDocuments(resources.slice(), callback);
        }
    };

    // Create the members and memberships child search document mappings in elasticsearch
    SearchAPI.registerChildSearchDocument(AuthzConstants.search.MAPPING_RESOURCE_MEMBERS, membersChildSearchDocumentOptions, function(err) {
        if (err) {
            return callback(err);
        }

        return SearchAPI.registerChildSearchDocument(AuthzConstants.search.MAPPING_RESOURCE_MEMBERSHIPS, membershipsChildSearchDocumentOptions, callback);
    });
};

/**
 * Given a list of ids of principals whose membership has changed, fire the index tasks required to
 * update the appropriate membership documents
 *
 * @param  {String[]}   memberIds   The list of principals whose membership in a group changed
 */
var fireMembershipUpdateTasks = module.exports.fireMembershipUpdateTasks = function(memberIds) {
    if (_.isEmpty(memberIds)) {
        return;
    }

    var groupResources = [];
    var userResources = [];

    _.each(memberIds, function(memberId) {
        if (AuthzUtil.isGroupId(memberId)) {
            groupResources.push({'id': memberId});
        } else if (AuthzUtil.isUserId(memberId)) {
            userResources.push({'id': memberId});
        }
    });

    // Send the update tasks for the aggregated group and user membership updates
    if (!_.isEmpty(groupResources)) {
        SearchAPI.postIndexTask('group', groupResources, {'children': {'resource_memberships': true}});
    }

    if (!_.isEmpty(userResources)) {
        SearchAPI.postIndexTask('user', userResources, {'children': {'resource_memberships': true}});
    }
};

/**
 * Produce all the resource members documents that represent the given resources
 *
 * @param  {Object[]}   resources       An array of search resource documents
 * @param  {Function}   callback        Standard callback function
 * @param  {Object}     callback.err    An error that occurred, if any
 * @api private
 */
var _produceResourceMembersDocuments = function(resources, callback, _documents) {
    _documents = _documents || [];
    if (_.isEmpty(resources)) {
        return callback(null, _documents);
    }

    // Take the next resource
    var resource = resources.pop();
    _getMemberIds(resource, function(err, memberIds) {
        if (err) {
            return callback(err);
        }

        _documents.push(SearchUtil.createChildSearchDocument(AuthzConstants.search.MAPPING_RESOURCE_MEMBERS, resource.id, {'direct_members': memberIds}));
        return _produceResourceMembersDocuments(resources, callback, _documents);
    });
};

/**
 * Produce all the resource memberships documents that represent the given resources
 *
 * @param  {Object[]}   resources       An array of search resource documents
 * @param  {Function}   callback        Standard callback function
 * @param  {Object}     callback.err    An error that occurred, if any
 * @api private
 */
var _produceResourceMembershipsDocuments = function(resources, callback, _documents) {
    _documents = _documents || [];
    if (_.isEmpty(resources)) {
        return callback(null, _documents);
    }

    // Take the next resource
    var resource = resources.pop();
    _getMembershipIds(resource, function(err, membershipIds) {
        if (err) {
            return callback(err);
        }

        _documents.push(SearchUtil.createChildSearchDocument(AuthzConstants.search.MAPPING_RESOURCE_MEMBERSHIPS, resource.id, {'direct_memberships': membershipIds}));
        return _produceResourceMembershipsDocuments(resources, callback, _documents);
    });
};

/**
 * Get the members of the provided resource.
 *
 * @param  {Object}     resource            The resource whose members to get
 * @param  {Function}   callback            Standard callback function
 * @param  {Object}     callback.err        An error that occurred, if any
 * @param  {String[]}   callback.memberIds  The ids of the members of the provided resource
 * @api private
 */
var _getMemberIds = function(resource, callback) {
    if (resource.memberIds) {
        return callback(null, resource.memberIds);
    }

    AuthzAPI.getAuthzMembers(resource.id, null, 10000, function(err, memberIdRoles) {
        if (err) {
            return callback(err);
        }

        return callback(null, _.pluck(memberIdRoles, 'id'));
    });
};

/**
 * Get the memberships of the current resource.
 *
 * @param  {Function}   callback                Standard callback function
 * @param  {Object}     callback.err            An error that occurred, if any
 * @param  {String[]}   callback.membershipIds  The ids of the group memberships of the provided resource
 * @api private
 */
var _getMembershipIds = function(resource, callback) {
    if (resource.membershipIds) {
        return callback(null, resource.membershipIds);
    }

    AuthzAPI.getRolesForPrincipalsAndResourceType([resource.id], AuthzConstants.resourceTypes.GROUP, function(err, principalGroupRole) {
        if (err) {
            return callback(err);
        }

        // Just extract a set of the groupIds in which the resource is a member
        var memberships = {};
        _.each(principalGroupRole, function(groupRole, principalId) {
            _.each(groupRole, function(role, groupId) {
                memberships[groupId] = true;
            });
        });

        return callback(null, _.keys(memberships));
    });
};
