/**
 * smartflocollabapi
 */

/**
 * Represents remote SmartFlo™ Collaboration 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
 * handling collaboration tasks in the SmartFlo™ system. It extends basic
 * job task processing to support ad hoc review cycles where at each iteration
 * through a review cycle, a designated monitor watches over a new team of 
 * collaborators chosen to review some digital content, and a new set
 * of digital attachments are shown to these selected reviewers. 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 'sfcollaboration' 
 * // module.
 * //
 * angular.module('sfcollaboration', [])
 *      // inject API as a service
 *      .service('SFcollaborationApi', ['$rootScope', '$http', CollabAPI])
 *      .controller('SFcollaborationsListCtrl', CollaborationsReportCtrl);
 *      .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 CollabAPI {
    constructor($rootScope, $http) {
        return {
            /** 
             * Get's a table of the current collaborations for a given job ID 
             * and task ID.
             * @function 
             * @name getCollaborations
             * @description Get's a table of the current collaborations for a 
             * given job ID and task ID. 
             * @memberof CollabAPI.prototype
             * @param {object} params - An object that provides the job ID and 
             * task ID as well as a flag indicating whether to restrict the 
             * query to the current round of collaborations or include all rounds.
             * <pre><code>
             * var params = {
             *      jobid: $scope.currentTask.job_id,
             *      taskid: $scope.currentTask.task_id,
             *      currentonly: true | false
             * };
             * </code></pre>
             * @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],
             *      collabortionsList: [list of collaborations]
             * };
             * </code></pre>
             * @example 
             * function getCollaborations() {
             *      var params = {
             *          jobid: $scope.currentTask.job_id,
             *          taskid: $scope.currentTask.task_id,
             *          currentonly: true
             *      };
             *      SFcollaborationApi.getCollaborations(params, handleGetCollaborations);
             * }
             * 
             * function handleGetCollaborations(result) {
             *      if ('0' === status) {
             *          $scope.collabortionsList = result.collabortionsList;
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            getCollaborations: 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 = get('collaborations?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var collaborationsdata = {};
                            collaborationsdata.date = new Date();
                            if (jsonData.collaboration_service_response) { // from collaboration apis
                                collaborationsdata.api = jsonData.collaboration_service_response.api;
                                collaborationsdata.status = jsonData.collaboration_service_response.error_response.response_code;
                                collaborationsdata.statusMessage = jsonData.collaboration_service_response.error_response.response_message;
                            }

                            if ('0' === collaborationsdata.status) {
                                if (jsonData.collaboration_service_response.response.collaboration_list &&
                                        jsonData.collaboration_service_response.response.collaboration_list.collaboration_item) {
                                    var collabortionsList = jsonData.collaboration_service_response.response.collaboration_list.collaboration_item;
                                    if (collabortionsList) {
                                        if (!angular.isArray(collabortionsList)) {
                                            var collaborationsArr = [];
                                            collaborationsArr.push(collabortionsList);
                                            collabortionsList = collaborationsArr;
                                        }
                                        collaborationsdata.collaborationsList = collabortionsList;
                                    }
                                } else {
                                    collaborationsdata.collaborationsList = [];
                                }

                                if (jsonData.collaboration_service_response.response) {
                                    collaborationsdata.currentCollaboration = jsonData.collaboration_service_response.response.current_collaboration;
                                    collaborationsdata.currentCycle = jsonData.collaboration_service_response.response.current_cycle;
                                    collaborationsdata.currentInstruction = jsonData.collaboration_service_response.response.current_instruction;
                                }
                                successHandler(collaborationsdata);
                            } else {
                                failureHandler(collaborationsdata);
                            }
                        },
                        function (errorInfo) {
                            throw new Error(errorInfo.data);
                        });
            },
            /** 
             * Retrieves all the groups which the the user can access.
             * @function 
             * @name getAffiliateAndPublicGroups
             * @description Get's all the groups accessible by the user. 
             * @memberof CollabAPI.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.
             * <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],
             *      affiliateGroups: [list of groups]
             * };
             * </code></pre>
             * @example 
             * function getAffiliateAndPublicGroups() {
             *      SFcollaborationApi.getAffiliateAndPublicGroups(handleGetAffiliateAndPublicGroups);
             * }
             * 
             * function handleGetAffiliateAndPublicGroups(result) {
             *      if ('0' === status) {
             *          $scope.affiliateGroups = groupData.affiliateGroups;
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            getAffiliateAndPublicGroups: 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('affiliateandpublicgroups?');
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var groupdata = {};
                            groupdata.date = new Date();
                            if (jsonData.collaboration_service_response) { // from collaboration apis
                                groupdata.api = jsonData.collaboration_service_response.api;
                                groupdata.status = jsonData.collaboration_service_response.error_response.response_code;
                                groupdata.statusMessage = jsonData.collaboration_service_response.error_response.response_message;
                            }

                            if ('0' === groupdata.status) {
                                if (jsonData.collaboration_service_response.response.affiliate_group_list &&
                                        jsonData.collaboration_service_response.response.affiliate_group_list.group) {
                                    var ga = jsonData.collaboration_service_response.response.affiliate_group_list.group;
                                    if (!angular.isArray(ga)) {
                                        var gaArr = [];
                                        gaArr.push(ga);
                                        ga = gaArr;
                                    }
                                    groupdata.affiliateGroups = convertToNameValueList(ga);
                                }
                                successHandler(groupdata);
                            } else {
                                failureHandler(groupdata);
                            }
                        },
                        function (errorInfo) {
                            throw new Error(errorInfo.data);
                        });
            },
            /** 
             * Retrieves all the users that belong to a specified group
             * @function 
             * @name getUsersInGroup
             * @description Retrieves all the users that belong to a specified 
             * group. 
             * @memberof CollabAPI.prototype
             * @param {object} params - An object that provides the name of
             * the group to provide the users.
             * <pre><code>
             * var params = {
             *      group: $scope.currentAffiliateGroup
             * };
             * </code></pre>
             * @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],
             *      performers: [users in the specified group]
             * };
             * </code></pre>
             * @example 
             * function getUsersInGroup() {
             *      var params = {
             *          group: $scope.currentAffiliateGroup
             *      };
             *      SFcollaborationApi.getUsersInGroup(params, handleGetUsersInGroups);
             * }
             * 
             * function handleGetUsersInGroup(result) {
             *      if ('0' === status) {
             *          $scope.performers = result.performers;
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            getUsersInGroup: 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 = get('groups/users?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var userdata = {};
                            userdata.date = new Date();
                            if (jsonData.collaboration_service_response) { // from collaboration apis
                                userdata.api = jsonData.collaboration_service_response.api;
                                userdata.status = jsonData.collaboration_service_response.error_response.response_code;
                                userdata.statusMessage = jsonData.collaboration_service_response.error_response.response_message;
                            }

                            if ('0' === userdata.status) {
                                if (jsonData.collaboration_service_response.response.userid_list &&
                                        jsonData.collaboration_service_response.response.userid_list.userid) {
                                    var uil = jsonData.collaboration_service_response.response.userid_list.userid;
                                    if (!angular.isArray(uil)) {
                                        var uilArr = [];
                                        uilArr.push(uil);
                                        uil = uilArr;
                                    }
                                    userdata.performers = uil;
                                } else {
                                    userdata.performers = [];
                                }
                                successHandler(userdata);
                            } else {
                                failureHandler(userdata);
                            }
                        },
                        function (errorInfo) {
                            throw new Error(errorInfo.data);
                        });
            },
            /** 
             * Retrieves all the attachments currently assigned to a 
             * collaboration review task.
             * @function 
             * @name getCurrentCycleAttachments
             * @description Retrieves all the attachments currently assigned to 
             * a collaboration review task.
             * @memberof CollabAPI.prototype
             * @param {object} params - An object that the selected job ID and 
             * task ID.
             * <pre><code>
             * var params = {
             *      jobid: $scope.currentTask.job_id,
             *      taskid: $scope.currentTask.task_id
             * };
             * </code></pre>
             * @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],
             *      currentAttachments: [attachments assigned to the current collaboration round]
             * };
             * 
             * var attachment {
             *      affiliate: [SmartFlo™ affiliate to which the attachment belongs],
             *      accessible_by: [SmartFlo™ user ID or group access control],
             *      attachment_id: [unique SmartFlo™ ID for attachment],
             *      creator:SmartFlo™ user ID of user who uploaded the attachment content],
             *      creation_date: [date and time attachment content was uploaded],
             *      attachment_approval_status: [one of: 
             *          attachment.no.status (1)
             *          attachment.rejected (2)
             *          attachment.approved (3)
             *          attachment.pending (4)
             *          attachment.canceled (5)],
             *      attachment_approval_status_number: [one of: 1, 2, 3, 4, 5],
             *      display_name: user edited name given to attachment],
             *      checked_out_by: [if checked out, the user ID of the user who has checked it out],
             *      joinId: [full name of job to which the attachment belongs],
             *      comments: [Array of attachment comments],
             *      tags: [Array of attachment tags],
             *      properties: [Array of attachment properties],
             *      content: [URL to original content],
             *      content_mime_type:[MIME type of content],   
             *      content_low_res: [URL to reduced version of content suitable for use as a preview -
             *              for video content, an flv formated low-res movie, otherwise a reduced 
             *              image of bitmapped content and pdfs],
             *      content_preview: [URL to reduced version of content, used only for video files - 
             *              a still shot image from the video, suitable for use as a static preview],
             *      content_thumbnail: [URL to a reduced bitmapped image suitable for display as a thumbnail in a content gallery],
             *      size_in_bytes: [size of original content],
             *      previewImage: [URL to recommended content to display in web preview of image types - null if video type],
             *      previewVideo: [URL to recommended content to display in web preview of image types - null if image type],
             *      unsupportedMIME: [true or false depending upon whether the MIME type is supported in SmartFlo™]
             * };
             * </code></pre>
             * @example 
             * function getCurrentCycleAttachments() {
             *      var params = {
             *         jobid: $scope.currentTask.job_id,
             *         taskid: $scope.currentTask.task_id
             *      };
             *      SFcollaborationApi.getCurrentCycleAttachments(params, handleGetCurrentCycleAttachments);
             * }
             * 
             * function handleGetCurrentCycleAttachments(result) {
             *      if ('0' === status) {
             *          $scope.currentAttachments = result.currentAttachments;
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            getCurrentCycleAttachments: 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 = get('cycles/attachments?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var attachmentdata = {};
                            attachmentdata.date = new Date();
                            if (jsonData.collaboration_service_response) { // from collaboration apis
                                attachmentdata.api = jsonData.collaboration_service_response.api;
                                attachmentdata.status = jsonData.collaboration_service_response.error_response.response_code;
                                attachmentdata.statusMessage = jsonData.collaboration_service_response.error_response.response_message;
                            }

                            if ('0' === attachmentdata.status) {
                                if (angular.isDefined(jsonData.collaboration_service_response.response.attachment_list) &&
                                        angular.isDefined(jsonData.collaboration_service_response.response.attachment_list.attachment)) {

                                    var currentAttachments = jsonData.collaboration_service_response.response.attachment_list.attachment;
                                    if (!angular.isArray(currentAttachments)) {
                                        var arr = [];
                                        arr.push(currentAttachments);
                                        currentAttachments = arr;
                                    }
                                    attachmentdata.currentAttachments = fixAttachmentArrays(currentAttachments);
                                } else {
                                    attachmentdata.currentAttachments = [];
                                }
                                successHandler(attachmentdata);
                            } else {
                                failureHandler(attachmentdata);
                            }
                        },
                        function (errorInfo) {
                            throw new Error(errorInfo.data);
                        });
            },
            /** 
             * Completes a collaboration Initator task and thereby posts a new 
             * collaboration cycle using selected users and selected 
             * attachments. 
             * @function 
             * @name postInitiatorTaskCompletion
             * @description Completes a collaboration Initator task and thereby 
             * posts a new collaboration cycle using selected users 
             * and selected attachments. 
             * <br><br>
             * A collaboration cycle implements the "voter pattern" whereby
             * a set of 1 or more reviewers (collaborators) comment on a set of 
             * documents until a collaboration monitor ends the collaboration.
             * @memberof CollabAPI.prototype
             * @param {object} params - An object that provides the job ID, 
             * task ID, cyclename, collaborator IDs, due date, instruction and
             * attachment IDs of the attachments to be reviewed.
             * <pre><code>
             * var params = {
             *      jobid: $scope.currentTask.job_id,
             *      taskid: $scope.currentTask.task_id,
             *      cyclename: cyclename,
             *      collaborators: collaborators [comma separated list of SmartFlo™ user IDs],
             *      duedate: $scope.decisionDueDateStr,
             *      instruction: $scope.reviewInstruction,
             *      attachmentids: attachmentids [comma separated list of attachment IDs]
             * };
             * </code></pre>
             * @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],
             * };
             * </code></pre>
             * @example 
             * function postInitiatorTaskCompletion() {
             *      var params = {
             *          jobid: $scope.currentTask.job_id,
             *          taskid: $scope.currentTask.task_id,
             *          cyclename: cyclename,
             *          collaborators: collaborators [comma separated list of SmartFlo™ user IDs],
             *          duedate: $scope.decisionDueDateStr,
             *          instruction: $scope.reviewInstruction,
             *          attachmentids: attachmentids [comma separated list of attachment IDs]
             *      };
             *      SFcollaborationApi.postInitiatorTaskCompletion(params, handleGetCollaborations);
             * }
             * 
             * function handlePostInitiatorTaskCompletion(result) {
             *      if ('0' === status) {
             *          $scope.taskStatus = $translate.instant('label.yes');
             *          $scope.taskCompleted = true;
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            postInitiatorTaskCompletion: 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('initiatorcompletions?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var taskcompletiondata = {};
                            taskcompletiondata.date = new Date();
                            if (jsonData.collaboration_service_response) { // from collaboration apis
                                taskcompletiondata.api = jsonData.collaboration_service_response.api;
                                taskcompletiondata.status = jsonData.collaboration_service_response.error_response.response_code;
                                taskcompletiondata.statusMessage = jsonData.collaboration_service_response.error_response.response_message;
                            }

                            if ('0' === taskcompletiondata.status) {
                                successHandler(taskcompletiondata);
                            } else {
                                failureHandler(taskcompletiondata);
                            }
                        },
                        function (errorInfo) {
                            throw new Error(errorInfo.data);
                        });
            },
            /** 
             * Completes a collaboration Monitor task, thereby ending the 
             * current collaboration cycle. Also completes all open Collaborator 
             * tasks for the given collaboration.
             * @function 
             * @name postMonitorTaskCompletion
             * @description Completes a collaboration Monitor task, thereby 
             * ending the current collaboration cycle.
             * Also completes all open Collaborator 
             * tasks for the given collaboration.
             * @memberof CollabAPI.prototype
             * @param {object} params - An object that provides the job ID, 
             * task ID and routing selections.
             * <pre><code>
             * var params = {
             *      jobid: $scope.currentTask.job_id,
             *      taskid: $scope.currentTask.task_id,
             *      routes: routes.length > 0 ? routes : null [selection of 
             *          AND and OR routes defining the tasks that the job will  
             *          transition to.]
             * };
             * </code></pre>
             * @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],
             * };
             * </code></pre>
             * @example 
             * function postMonitorTaskCompletion() {
             *      var params = {
             *          jobid: $scope.currentTask.job_id,
             *          taskid: $scope.currentTask.task_id,
             *          routes: routes.length > 0 ? routes : null
             *      };
             *      SFcollaborationApi.postMonitorTaskCompletion(params, handlePostMonitorTaskCompletion);
             * }
             * 
             * function handlePostMonitorTaskCompletion(result) {
             *      if ('0' === status) {
             *          $scope.taskStatus = $translate.instant('label.yes');
             *          $scope.taskCompleted = true;
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            postMonitorTaskCompletion: 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('monitorcompletions?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var taskcompletiondata = {};
                            taskcompletiondata.date = new Date();
                            if (jsonData.collaboration_service_response) { // from collaboration apis
                                taskcompletiondata.api = jsonData.collaboration_service_response.api;
                                taskcompletiondata.status = jsonData.collaboration_service_response.error_response.response_code;
                                taskcompletiondata.statusMessage = jsonData.collaboration_service_response.error_response.response_message;
                            }

                            if ('0' === taskcompletiondata.status) {
                                successHandler(taskcompletiondata);
                            } else {
                                failureHandler(taskcompletiondata);
                            }
                        },
                        function (errorInfo) {
                            throw new Error(errorInfo.data);
                        });
            },
            /** 
             * Completes a Collaborator task, thereby submitting the user's 
             * "vote" (as in voter pattern) or input on the attachments that are
             * being reviewed.
             * @function 
             * @name postCollaboratorTaskCompletion
             * @description Completes a Collaborator task, thereby submitting 
             * the user's "vote" (as in voter pattern), i.e., input in the form 
             * of a comment and a judgement about whether the attachments under 
             * review are suitable.
             * @memberof CollabAPI.prototype
             * @param {object} params - An object that provides the job ID, 
             * task ID, cyclename, review decision and comment.
             * <pre><code>
             * var params = {
             *      jobid: $scope.currentTask.job_id,
             *      taskid: $scope.currentTask.task_id,
             *      cyclename: $scope.currentCollaborationCycle,
             *      decision: $scope.reviewDecision,
             *      comment: comment
             * };
             * <br>
             * The possible review decisions are:
             * <ul>
             * <li>cycle.no.response</li>
             * <li>cycle.accepted</li>
             * <li>cycle.rejected</li>
             * </ul>
             * </code></pre>
             * @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],
             * };
             * </code></pre>
             * @example 
             * function postCollaboratorTaskCompletion() {
             *      var params = {
             *          jobid: $scope.currentTask.job_id,
             *          taskid: $scope.currentTask.task_id,
             *          cyclename: $scope.currentCollaborationCycle,
             *          decision: $scope.reviewDecision,
             *          comment: comment
             *      };
             *      SFcollaborationApi.postCollaboratorTaskCompletion(params, handlePostCollaboratorTaskCompletion);
             * }
             * 
             * function handlePostCollaboratorTaskCompletion(result) {
             *      if ('0' === status) {
             *          $scope.taskStatus = $translate.instant('label.yes');
             *          $scope.taskCompleted = true;
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            postCollaboratorTaskCompletion: 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('collaboratorcompletions?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var taskcompletiondata = {};
                            taskcompletiondata.date = new Date();
                            if (jsonData.collaboration_service_response) { // from collaboration apis
                                taskcompletiondata.api = jsonData.collaboration_service_response.api;
                                taskcompletiondata.status = jsonData.collaboration_service_response.error_response.response_code;
                                taskcompletiondata.statusMessage = jsonData.collaboration_service_response.error_response.response_message;
                            }

                            if ('0' === taskcompletiondata.status) {
                                successHandler(taskcompletiondata);
                            } else {
                                failureHandler(taskcompletiondata);
                            }
                        },
                        function (errorInfo) {
                            throw new Error(errorInfo.data);
                        });
            },
            /** 
             * Completes a Private Collaborator task, thereby submitting the user's 
             * "vote" (as in voter pattern) or input on the attachments that are
             * being reviewed. Since the task is "private', the reviewer can 
             * also upload attachments that can only be viewed by the 
             * Monitor Task or Initiator Task and the current reviewer
             * (i.e., not by any other reviewer).
             * @function 
             * @name postCollaboratorTaskCompletion
             * @description Completes a Private Collaborator task, thereby submitting 
             * the user's "vote" (as in voter pattern), i.e., input in the form 
             * of a comment and a judgement about whether the attachments under 
             * review are suitable. 
             * <br><br>
             * Since the task is "private', the reviewer can 
             * also upload attachments that can only be viewed by the 
             * Monitor Task or Initiator Task and the current reviewer
             * (i.e., not by any other reviewer).
             * <br><br>
             * Since the task is "private', the reviewer can 
             * also upload attachments that can only be viewed by the 
             * Monitor Task or Initiator Task and the current reviewer
             * (i.e., not by any other reviewer).
             * @memberof CollabAPI.prototype
             * @param {object} params - An object that provides the job ID, 
             * task ID, cyclename, review decision and comment.
             * <pre><code>
             * var params = {
             *      jobid: $scope.currentTask.job_id,
             *      taskid: $scope.currentTask.task_id,
             *      cyclename: $scope.currentCollaborationCycle,
             *      decision: $scope.reviewDecision,
             *      comment: comment
             * };
             * <br>
             * The possible review decisions are:
             * <ul>
             * <li>cycle.no.response</li>
             * <li>cycle.accepted</li>
             * <li>cycle.rejected</li>
             * </ul>
             * </code></pre>
             * @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],
             * };
             * </code></pre>
             * @example 
             * function postPrivateCollaboratorTaskCompletion() {
             *      var params = {
             *          jobid: $scope.currentTask.job_id,
             *          taskid: $scope.currentTask.task_id,
             *          cyclename: $scope.currentCollaborationCycle,
             *          decision: $scope.reviewDecision,
             *          comment: comment
             *      };
             *      SFcollaborationApi.postPrivateCollaboratorTaskCompletion(params, handlePostPrivateCollaboratorTaskCompletion);
             * }
             * 
             * function handlePostPrivateCollaboratorTaskCompletion(result) {
             *      if ('0' === status) {
             *          $scope.taskStatus = $translate.instant('label.yes');
             *          $scope.taskCompleted = true;
             *      } else {
             *          $scope.errorCode = result.status;
             *          $scope.errorMessage = result.statusMessage;
             *      }
             * }
             */
            postPrivateCollaboratorTaskCompletion: 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('collaboratorcompletions?', params);
                request.then(
                        function (result) {
                            var x2js = new X2JS();
                            var jsonData = x2js.xml_str2json(result.data);
                            var taskcompletiondata = {};
                            taskcompletiondata.date = new Date();
                            if (jsonData.collaboration_service_response) { // from collaboration apis
                                taskcompletiondata.api = jsonData.collaboration_service_response.api;
                                taskcompletiondata.status = jsonData.collaboration_service_response.error_response.response_code;
                                taskcompletiondata.statusMessage = jsonData.collaboration_service_response.error_response.response_message;
                            }

                            if ('0' === taskcompletiondata.status) {
                                successHandler(taskcompletiondata);
                            } else {
                                failureHandler(taskcompletiondata);
                            }
                        },
                        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),
                headers: {
                    'Authorization': getAuthHeader()
                },
                data: validData(data) ? data : null
            };
            return $http(req);
        }

        function url(site, token, path, param) {
            var reqUrl = 'https://' + site + '/api/' + token + '/collab/';
            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 fixAttachmentComments(attachment) {
            if (!attachment.comment_list || !attachment.comment_list.comment) {
                // comments undefined == true
                attachment.comments = [];
            } else if (!angular.isArray(attachment.comment_list.comment)) {
                // not array = true
                var savedComment = attachment.comment_list.comment;
                attachment.comments = [];
                attachment.comments[0] = savedComment;
            } else {
                attachment.comments = attachment.comment_list.comment;
            }
        }

        function fixAttachmentProperties(attachment) {
            if (!attachment.property_list || !attachment.property_list.property) {
                // comments undefined == true
                attachment.properties = [];
            } else if (!angular.isArray(attachment.property_list.property)) {
                // not array = true
                var savedProperties = attachment.property_list.property;
                attachment.properties = [];
                attachment.properties[0] = savedProperties;
            } else {
                attachment.properties = attachment.property_list.property;
            }
        }

        function fixAttachmentTags(attachment) {
            if (!attachment.tag_list || !attachment.tag_list.tag) {
                // comments undefined == true
                attachment.tags = [];
            } else if (!angular.isArray(attachment.tag_list.tag)) {
                // not array = true
                var savedTags = attachment.tag_list.property;
                attachment.tags = [];
                attachment.tags[0] = savedTags;
            } else {
                attachment.tags = attachment.tag_list.tag;
            }
        }

        function determinePreviewImage(attachment) {
            var mimeType = attachment['content_mime_type'].toString();
            if (mimeType.indexOf('image/') === -1 &&
                    mimeType.indexOf('application/pdf') === -1 &&
                    mimeType.indexOf('video/mp4') === -1 &&
                    mimeType.indexOf('video/mpeg') === -1 &&
                    mimeType.indexOf('video/x-ms-wmv') === -1 &&
                    mimeType.indexOf('video/x-msvideo') === -1) {
                attachment.unsupportedMIME = true;
                attachment.previewImage = attachment['content_low_res'];
            } else {
                attachment.unsupportedMIME = false;
                // is this a video? We will need to inject a video tag and listen
                if (mimeType.indexOf('video/mp4') !== -1) {
                    attachment.previewVideo = attachment['content'];
                } else if (mimeType.indexOf('video/mpeg') !== -1) {
                    attachment.previewImage = attachment['content_preview'];
                } else if (mimeType.indexOf('video/x-ms-wmv') !== -1) {
                    attachment.previewImage = attachment['content_thumbnail'];
                } else if (mimeType.indexOf('video/x-msvideo') !== -1) {
                    attachment.previewImage = attachment['content_thumbnail'];
                } else if (mimeType.indexOf('application/pdf') !== -1) {
                    attachment.previewImage = attachment['content_low_res'];
                } else {
                    attachment.previewImage = attachment['content'];
                }
            }
        }

        function fixAttachmentArrays(attachments) {
            for (var i = 0; i < attachments.length; i++) {
                fixAttachmentComments(attachments[i]);
                fixAttachmentTags(attachments[i]);
                fixAttachmentProperties(attachments[i]);
                determinePreviewImage(attachments[i]);
            }
            return attachments;
        }

        function convertToNameValueList(list) {
            var lst = [];
            if (list) {
                for (var i = 0; i < list.length; i++) {
                    var obj = {
                        name: list[i],
                        value: list[i]
                    };
                    lst.push(obj);
                }
            }
            return lst;
        }
    }
}