// Copyright 2014, Renasar Technologies Inc.
// Created by heckj on 5/30/14.
/* jslint node: true */
/* jslint phantom: true */
(function () {
    'use strict';

    var authZ = {};
    authZ.rules = {};
    authZ.policies = {};

    authZ.reset = function() {
        // really for testing only - a function to wipe out any existing rules or policies
        this.rules = {};
        this.policies = {};
    };

    authZ.addRule = function(name, authzCallback) {

        /* authzCallback needs to be a function that accepts a
           session (express request) object and returns something
           truthy if access is allowed.

           for example, to allow anything:

           authZ.addrule('guest', function(req) {
             return true;
           }

           Or to work with passport to verify a user is authenticated:

           authZ.addrule('any_authenticated', function(req) {
             return req.user;
           }

         */
        var rule = {};
        rule.name = name;

        if (typeof authzCallback !== 'function') {
            var type = {}.toString.call(authzCallback);
            var msg = 'addRule requires callback functions but got a ' + type;
            throw new Error(msg);
        }
        rule.authzCheck = authzCallback;
        this.rules[name] = rule;
    };

    authZ.ruleNames = function() {
        // returns a list of all the rule names as an array of strings
        var names = [];
        var rule;
        for (rule in this.rules) {
            if (this.rules.hasOwnProperty(rule)) {
                names.push(rule);
            }
        }
        return names;
    };

    authZ.addPolicy = function(policyName, rules) {
        if (rules.length && rules.forEach && rules.length > 0) {
            var allnames = this.ruleNames();

            var i, value;
            // verify that the rule being specified in the policy exists...
            for (i in rules) {
                if (rules.hasOwnProperty(i)) {
                    value = rules[i];
                    if (allnames.indexOf(value) < 0) {
                        throw new Error("Invalid rule in policy: " + value);
                    }
                }
            }
            var policy = {};
            policy.name = policyName;
            policy.rules = rules;
            this.policies[policyName] = policy;
        } else {
            throw new Error("rules must have a length of at least 1 item.");
        }
    };

    authZ.checkPolicy = function(policyName, req) {
        var i, rulename, rule;
        var policy = this.policies[policyName];
        if (policy === undefined) {
            throw new Error("No policy named "+policyName);
        }
        for (i in policy.rules) {
            // iterate and look up rules, invoking them to check for access allowed
            if (policy.rules.hasOwnProperty(i)){
                rulename = policy.rules[i];
                rule = this.rules[rulename];
                if (rule === undefined) {
                    throw new Error("No rule named "+rulename+" found in rules.");
                }
                if (rule.authzCheck && typeof rule.authzCheck === 'function') {
                    if (rule.authzCheck(req)) {
                        return true;
                    }
                } else {
                    throw new Error("rule "+rulename+" doesn't have an authzCheck function");
                }
            }
        }
        return false;
    };

    authZ.enforcePolicy = function(policyName) {
        /*
         usage example:

         route.get('/something/:id',
         authZ.enforcePolicy('guest'),
         function (req, res) { ... });

         OR:

         router.use(authZ.enforcePolicy('guest');

         */

        var policy = this.policies[policyName];
        policy.middleware = function(req, res, next) {
            /*
             default policy state is to fail. If any of the rules processed
             success, then we call out and return next() to allow access.

             Otherwise, we create an error with a 403 status code and return
             that via next()
             */

            if (authZ.checkPolicy(policyName, req)) {
                return next();
            }

            var err = new Error();
            err.status = 403;
            next(err);
        };
        return policy.middleware;

    };


    /*
    general form of rules:

     authorization.addRule('allAccess', function() {
         return true;
     });

     authorization.addRule('applicationOwner', function(req) {
         return (req.user._id === req.application.owner);
     });

     */

    module.exports = authZ;
}(module.exports));
