/**
 * smartfloauthapi
 */

/**
 * Represents remote SmartFlo™ Authentication Service APIs.
 * It is expected that this class will be injected into an
 * Angular module as a service.
 * <br><br>
 * This AngularJS service is built upon the SmartFlo™ RESTful Service APIs for
 * authentication in the SmartFlo™ system. It is provided to remove 
 * some of the complexities entailed by using RESTful APIs.
 * @description <strong>Warning!</strong> The SmartFlo™ APIs assume 2 global 
 * variables are set in 
 * $rootScope prior to using the APIs:
 * <ol>
 * <li><code>$rootScope.credential</code></li> set implicitely by the AuthAPI 
 * class upon success of the authenticate method
 * <li><code>$rootScope.site</code></li> set by the calling application to the  
 * domain (e.g., www.smartflo.biz) or IP address of the target SmartFlo™ system, 
 * possibly when initializing the module that declares an API service
 * </ol>
 * @copyright © Chalex Corp. 2002 - 2017. All rights reserved.
 * @example 
 * //
 * // Warning! The global '$rootScope.site' field value must be set 
 * // to the target SmartFlo™ domain prior to using theSmartFlo™ APIs. In this 
 * // example it is set by the configuration block of the 'sfauth' module.'
 * //
 * angular.module('sfauth', [])
 *      // inject API as a service
 *      .service('SFauthApi', ['$rootScope', '$http', AuthAPI]) 
 *      .controller('SFauthCtrl', AuthCtrl)
 .run(function ($rootScope, $location) { 
 *      // store the site for use by APIs
 *          $rootScope.site = $location.host();
 *          if ($location.port !== null & $location.port !== 80) {
 *          $rootScope.site += ':' + $location.port().toString();
 *     }
 * });
 * @constructor 
 * @param {object} $rootScope - The topmost parent scope in AngularJS.
 * @param {service} $http - The core AngularJS service that facilitates 
 * communication with the remote HTTP servers via the browser's XMLHttpRequest 
 * object or via JSONP.
 * @returns {class} A constructor that will be instantiated.
 */
class AuthAPI {
    constructor($rootScope, $http) {
        return {
            /** Authenticate a user.
             * @function 
             * @name authenticate
             * @description Authenticate a user based upon username/password or 
             * an accessid provided by the SmartFlo system.
             * @memberof AuthAPI.prototype
             * @param {object} params - An object providing either a userid 
             * @param {function} successHandler - A function that is 
             * called back upon success.
             * @param {function} [failureHandler] - An optional 
             * function that is called back upon failure. If none is specified,
             * the success callback is called with failure data. On failure 
             * however, the only valid fields returned are: 
             * <code>status, statusMessage, api</code> and <code>date</code>.
             * @throws (string) "missing.callback.exception"
             * @throws {} $http errors
             * @returns {object} A result object that contains a credential
             * object providing the user's access permissions. For conveniance, 
             * this method also implicitly assigns this same credential object 
             * to global scope using a $rootScope.credential field.
             * <pre><code>
             * var result {
             *      status: 0,
             *      statusMessage: "auth.no.errors", 
             *      api: "auth::post::users::passwords", // name of the call
             *      date: [Date object providing time call was made]
             *      credential: userCredential
             * };
             * 
             * var userCredential {
             *      status: 0,
             *      statusMessage: "auth.no.errors", 
             *      api: "auth::post::users", // name of the call
             *      date: [Date object providing time call was made],
             *      token: "694b", // accessid token
             *      userid: [SmartFlo™ userid],
             *      username: [SmartFlo™ user name],
             *      userGroups: [Array of SmartFlo™ groups that the user belongs to],
             *      affiliate: [SmartFlo™ affiliate of user],
             *      isPrivateCommentsUser: [true or false], // whether or not the user can access private comments
             *      isVproofUser: [true or false], // whether or not the user can access online Viki Proofing
             *      isReviewStudioUser: [true or false] //whether or not the user can access online ReviewStudio Proofing
             * };</pre></code>
             * @example 
             * function authenticate(userid, userpassword) {
             *      var params {
             *          username: userid,
             *          userpwd: userpassword
             *      };
             *      // alternately:
             *      // var params {
             *      //      40 character hash - 
             *      //      e.g., '4a9fe33129e58339Ba99af16dec8bf6b72975fd6'
             *      //      accessid: [SmartFlo™ accessid]
             *      // };
             *      SFauthApi.authenticate(params, handleAuthentication);
             * }
             * 
             * function handleAuthentication(credential) {
             *      if ('0' === credential.status) {
             *          $scope.$emit(SF_EVENTS().LOGIN_SUCCESS, credential);
             *      } else {
             *          $scope.$emit(SF_EVENTS().LOGIN_FAILURE, credential);
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            authenticate: function (params, successHandler, failureHandler) {
                this.successHandler = successHandler;
                // require a successHandler
                if (!successHandler) {
                    throw new Error("missing.callback.exception");
                }
                // if no failureHandler is defined, use successHandler as 
                // general callback
                if (!failureHandler) {
                    failureHandler = successHandler;
                }
                $rootScope.credential = null;
                var request = post('users?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var credentialdata = {};
                            credentialdata.date = new Date();
                            credentialdata.status = jsonData.auth_service_response.error_response.response_code;
                            credentialdata.statusMessage = jsonData.auth_service_response.error_response.response_message;
                            credentialdata.api = jsonData.auth_service_response.api;
                            if ('0' === credentialdata.status) {
                                var data2 = jsonData.auth_service_response.response;
                                var credential = {};
                                credential.token = data2.security_token;
                                credential.userGroups = data2.smartflo_group_list.group;
                                credential.username = data2.username;
                                credential.userid = data2.userid;
                                credential.affiliate = data2.affiliate;
                                credential.isPrivateCommentsUser = isInGroup('Private Comments User', credential.userGroups);
                                credential.isVproofUser = isInVproofGroup(credential.userGroups);
                                credential.isReviewStudioUser = isInReviewStudioGroup(credential.userGroups);
                                $rootScope.credential = credential;
                                credentialdata.credential = credential;
                                successHandler(credentialdata);
                            } else {
                                failureHandler(credentialdata);
                            }
                        },
                        function (errorInfo) {
                            throw new Error(errorInfo.data);
                        });
            },
            /** Change a user's password.
             * @function 
             * @name changePassword
             * @description Change a user's password. 
             * <br><br>
             * Note: this method 
             * for changing a password requires that the user be logged 
             * into the SmartFlo™ system.
             * @memberof AuthAPI.prototype
             * @param {object} params - An object providing the new password.
             * @param {function} successHandler - A function that is 
             * called back upon success.
             * @param {function} [failureHandler] - An optional 
             * function that is called back upon failure. If none is specified,
             * the success callback is called with failure data.
             * <code>status, statusMessage, api</code> and <code>date</code>.
             * @throws (string) "missing.callback.exception"
             * @throws {} $http errors
             * @returns {object} A result object. 
             * <pre><code>
             * var result {
             *      status: 0,
             *      statusMessage: "auth.no.errors", 
             *      api: "auth::post::users::passwords", // name of the call
             *      date: [Date object providing time call was made]
             * };</pre></code>
             * @example 
             * function changePassword(newpassword) {
             *      var params = {
             *          newpwd: newpassword
             *      };
             *      SFauthApi.changePassword(params, handleChangePassword);
             * }
             * 
             * function handleChangePassword(result) {
             *      // '0' is success
             *      if ('0' === status) {
             *          $scope.passwordChangeSuccessful = true;
             *      } else {
             *          $scope.passwordChangeSuccessful = false;
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            changePassword: function (params, successHandler, failureHandler) {
                // require a successHandler
                if (!successHandler) {
                    throw new Error("missing.callback.exception");
                }

                // if no failureHandler is defined, use successHandler as 
                // general callback
                if (!failureHandler) {
                    failureHandler = successHandler;
                }

                var request = post('users/passwords?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var result = {};
                            result.date = new Date();
                            result.status = jsonData.auth_service_response.error_response.response_code;
                            result.statusMessage = jsonData.auth_service_response.error_response.response_message;
                            result.api = jsonData.auth_service_response.api;
                            if ('0' === result.status) {
                                successHandler(result);
                            } else {
                                failureHandler(result);
                            }
                        },
                        function (errorInfo) {
                            throw new Error(errorInfo.data);
                        });
            },
            /** Reset a user's password.
             * @function 
             * @name resetPassword
             * @description Resets a user's password. This method for changing 
             * a password emails a secure URL to the requesting user that 
             * contains a "one-time' hash-token. When this link is followed, a
             * <strong><code>Change Password</code></strong> screen is displayed 
             * that permits the user to change their password. 
             * <br><br>
             * Note: this method 
             * can be used without first logging into the SmartFlo™ system.
             * @memberof AuthAPI.prototype
             * @param {object} params - An object providing 
             * <strong>either</strong> the 
             * user's SmartFlo™ userid or the user's SmartFlo™ email address.
             * @param {function} successHandler - A function that is 
             * called back upon success.
             * @param {function} [failureHandler] - An optional 
             * function that is called back upon failure. If none is specified,
             * the success callback is called with failure data.
             * @throws (string) "missing.callback.exception"
             * @throws {} $http errors
             * @returns {object} A result object. 
             * <pre><code>var result {
             *      status: 0,
             *      statusMessage: "auth.no.errors", 
             *      api: "auth::post::resetpasswords", // name of the call
             *      date: [Date object providing time call was made]
             * };</pre></code>
             * @example 
             * function resetPassword(useridoremail) {
             *      var params = {
             *          useroremail: useridoremail
             *      };
             *      SFauthApi.resetPassword(params, handleResetPassword);
             * }
             *
             * function handleResetPassword(result) {
             *      // '0' is success
             *      if ('0' === result.status) {
             *          $scope.passwordResetSuccessful = true;
             *      } else {
             *          $scope.passwordResetSuccessful = false;
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             * 
             */
            resetPassword: function (params, successHandler, failureHandler) {
                // require a successHandler
                if (!successHandler) {
                    throw new Error("missing.callback.exception");
                }

                // if no failureHandler is defined, use successHandler as 
                // general callback
                if (!failureHandler) {
                    failureHandler = successHandler;
                }

                var request = post('resetpasswords?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var response = {};
                            response.date = new Date();
                            response.status = jsonData.auth_service_response.error_response.response_code;
                            response.statusMessage = jsonData.auth_service_response.error_response.response_message;
                            response.api = jsonData.auth_service_response.api;
                            if ('0' === response.status) {
                                successHandler(response);
                            } else {
                                failureHandler(response);
                            }
                        },
                        function (errorInfo) {
                            throw new Error(errorInfo.data);
                        });
            },
            /** Reset a user's password.
             * @function 
             * @name getPanAffiliates
             * @description Retrieves the complete list of affiliates to which 
             * the user has access. Affiliates are hierarchically ordered so
             * the user's affiliate can have child affiliates and each of its 
             * children affiliates can have thier own children affiliates and 
             * so on. The Array(string) returned is a flat list however, with 
             * none of the hierarchical relationsships shown.
             * @memberof AuthAPI.prototype
             * @param {function} successHandler - A function that is 
             * called back upon success.
             * @param {function} [failureHandler] - An optional 
             * function that is called back upon failure. If none is specified,
             * the success callback is called with failure data.
             * @throws (string) "missing.callback.exception"
             * @throws {} $http errors
             * @returns {object} A result object. 
             * <pre><code>var result {
             *      status: 0,
             *      statusMessage: "auth.no.errors", 
             *      api: "auth::get::panaffiliates", // name of the call
             *      date: [Date object providing time call was made],
             *      panAffiliates: [Array of user accessible pan-affiliates]
             * };</pre></code>
             * @example 
             * function getPanAffiliates() {
             *      SFauthApi.getPanAffiliates(handleGetPanAffiliates);
             * }
             *
             * function handleGetPanAffiliatesd(result) {
             *      // '0' is success
             *      if ('0' === result.status) {
             *          $scope.panAffiliates = result.panAffiliates;
             *      } else {
             *          $scope.panAffiliates = [];
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             * 
             */
            getPanAffiliates: function (successHandler, failureHandler) {
                // require a successHandler
                if (!successHandler) {
                    throw new Error("missing.callback.exception");
                }

                // if no failureHandler is defined, use successHandler as 
                // general callback
                if (!failureHandler) {
                    failureHandler = successHandler;
                }

                var request = get('panaffiliates?');
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var response = {};
                            response.date = new Date();
                            response.status = jsonData.auth_service_response.error_response.response_code;
                            response.statusMessage = jsonData.auth_service_response.error_response.response_message;
                            response.api = jsonData.auth_service_response.api;
                            if ('0' === response.status) {
                                if (jsonData.auth_service_response.response &&
                                        jsonData.auth_service_response.response.affiliate_list)
                                {
                                    response.panAffiliates = jsonData.auth_service_response.response.affiliate_list.affiliate;
                                }
                                successHandler(response);
                            } else {
                                failureHandler(response);
                            }
                        },
                        function (errorInfo) {
                            throw new Error(errorInfo.data);
                        });
            }
        };

        function get(path, param) {
            return request("GET", path, param);
        }

        function post(path, param, data) {
            return request("POST", path, param, data);
        }

        function put(path, param, data) {
            return request("PUT", path, param, data);
        }

        function del(path, param) {
            return request("DELETE", path, param);
        }

        function request(verb, path, param, data) {
            var site = $rootScope.site;
            var token = null;
            if (validData($rootScope.credential) && validData($rootScope.credential.token)) {
                token = $rootScope.credential.token;
            }
            var req = {
                method: verb,
                url: url(site, token, path, param, data),
                headers: {
                    'Authorization': getAuthHeader()
                },
                data: validData(data) ? data : null
            };
            return $http(req);
        }

        function url(site, token, path, param) {
            var reqUrl = null;
            if (path === 'users?') {
                reqUrl = 'https://' + site + '/api/auth/';
            } else if (path === 'resetpasswords?') {
                reqUrl = 'https://' + site + '/api/auth/';
            } else {
                reqUrl = 'https://' + site + '/api/' + token + '/auth/';
            }
            if (validData(path)) {
                reqUrl += path;
            }

            if (validData(param)) {
                reqUrl += $.param(param);
            }
            return reqUrl;
        }

        function validData(data) {
            return data !== undefined && data !== null;
        }

        function getAuthHeader() {
            return "";
        }

        function isInGroup(group, userGroups) {
            if ($.inArray(group, userGroups) !== -1) {
                return true;
            } else {
                return false;
            }
        }

        function isInVproofGroup(userGroups) {
            var isIn = false;
            for (var i = 0; i < userGroups.length; i++) {
                if (userGroups[i].indexOf('Vproof ') > -1) {
                    isIn = true;
                    break;
                }
            }
            return isIn;
        }

        function isInReviewStudioGroup(userGroups) {
            var isIn = false;
            for (var i = 0; i < userGroups.length; i++) {
                if (userGroups[i].indexOf('Review Studio ') > -1) {
                    isIn = true;
                    break;
                }
            }
            return isIn;
        }
    }
}