// canThis(someUser).edit.posts([id]|[[ids]])
// canThis(someUser).edit.post(somePost|somePostId)

var _                   = require('underscore'),
    when                = require('when'),
    Models              = require('../models'),
    objectTypeModelMap  = require('./objectTypeModelMap'),
    UserProvider        = Models.User,
    PermissionsProvider = Models.Permission,
    init,
    refresh,
    canThis,
    CanThisResult,
    exported;

function hasActionsMap() {
    // Just need to find one key in the actionsMap

    return _.any(exported.actionsMap, function (val, key) {
        return Object.hasOwnProperty(key);
    });
}

// Base class for canThis call results
CanThisResult = function () {
    this.userPermissionLoad = false;
};

CanThisResult.prototype.buildObjectTypeHandlers = function (obj_types, act_type, userId) {
    var self = this,
        obj_type_handlers = {};

    // Iterate through the object types, i.e. ['post', 'tag', 'user']
    _.each(obj_types, function (obj_type) {
        var TargetModel = objectTypeModelMap[obj_type];

        // Create the 'handler' for the object type;
        // the '.post()' in canThis(user).edit.post()
        obj_type_handlers[obj_type] = function (modelOrId) {
            var modelId;

            if (_.isNumber(modelOrId) || _.isString(modelOrId)) {
                // It's an id already, do nothing
                modelId = modelOrId;
            } else if (modelOrId) {
                // It's a model, get the id
                modelId = modelOrId.id;
            }

            // Wait for the user loading to finish
            return self.userPermissionLoad.then(function (userPermissions) {
                // Iterate through the user permissions looking for an affirmation
                var hasPermission;

                // Allow for a target model to implement a "Permissable" interface
                if (TargetModel && _.isFunction(TargetModel.permissable)) {
                    return TargetModel.permissable(modelId, userId, act_type, userPermissions);
                }

                // Otherwise, check all the permissions for matching object id
                hasPermission = _.any(userPermissions, function (userPermission) {
                    var permObjId;

                    // Look for a matching action type and object type first
                    if (userPermission.get('action_type') !== act_type || userPermission.get('object_type') !== obj_type) {
                        return false;
                    }

                    // Grab the object id (if specified, could be null)
                    permObjId = userPermission.get('object_id');

                    // If we didn't specify a model (any thing)
                    // or the permission didn't have an id scope set
                    // then the user has permission
                    if (!modelId || !permObjId) {
                        return true;
                    }

                    // Otherwise, check if the id's match
                    // TODO: String vs Int comparison possibility here?
                    return modelId === permObjId;
                });

                if (hasPermission) {
                    return when.resolve();
                }

                return when.reject();
            }).otherwise(function () {
                // No permissions loaded, or error loading permissions

                // Still check for permissable without permissions
                if (TargetModel && _.isFunction(TargetModel.permissable)) {
                    return TargetModel.permissable(modelId, userId, act_type, []);
                }

                return when.reject();
            });
        };
    });

    return obj_type_handlers;
};

CanThisResult.prototype.beginCheck = function (user) {
    var self = this,
        userId = user.id || user;

    if (!hasActionsMap()) {
        throw new Error("No actions map found, please call permissions.init() before use.");
    }

    // TODO: Switch logic based on object type; user, role, post.

    // Kick off the fetching of the user data
    this.userPermissionLoad = UserProvider.effectivePermissions(userId);

    // Iterate through the actions and their related object types
    _.each(exported.actionsMap, function (obj_types, act_type) {
        // Build up the object type handlers;
        // the '.post()' parts in canThis(user).edit.post()
        var obj_type_handlers = self.buildObjectTypeHandlers(obj_types, act_type, userId);

        // Define a property for the action on the result;
        // the '.edit' in canThis(user).edit.post()
        Object.defineProperty(self, act_type, {
            writable: false,
            enumerable: false,
            configurable: false,
            value: obj_type_handlers
        });
    });

    // Return this for chaining
    return this;
};

canThis = function (user) {
    var result = new CanThisResult();

    return result.beginCheck(user);
};

init = refresh = function () {
    // Load all the permissions
    return PermissionsProvider.browse().then(function (perms) {
        var seenActions = {};

        exported.actionsMap = {};

        // Build a hash map of the actions on objects, i.e
        /*
        {
            'edit': ['post', 'tag', 'user', 'page'],
            'delete': ['post', 'user'],
            'create': ['post', 'user', 'page']
        }
        */
        _.each(perms.models, function (perm) {
            var action_type = perm.get('action_type'),
                object_type = perm.get('object_type');

            exported.actionsMap[action_type] = exported.actionsMap[action_type] || [];
            seenActions[action_type] = seenActions[action_type] || {};

            // Check if we've already seen this action -> object combo
            if (seenActions[action_type][object_type]) {
                return;
            }

            exported.actionsMap[action_type].push(object_type);
            seenActions[action_type][object_type] = true;
        });

        return when(exported.actionsMap);
    });
};

module.exports = exported = {
    init: init,
    refresh: refresh,
    canThis: canThis,
    actionsMap: {}
};