Source: smartorg.js

// PhantomJS doesn't support bind yet
Function.prototype.bind = Function.prototype.bind || function (thisp) {
        var fn = this;
        return function () {
            return fn.apply(thisp, arguments);
        };
    };

import Protocols from './protocols.js'
import Wizard from './wizard.js'
class SmartOrg {
    /**
     * Any interaction with the SmartOrg API starts by creating a SmartOrg object.
     * @example
     * // If your API is at https://localhost/kirk
     * var endpoint = "https://localhost";
     * var path = "kirk";
     * var smartorg = new SmartOrg(endpoint, path);
     *
     * @param {string} endpoint The SmartOrg server endpoint
     * @param {string} path_ The path where the API is accessible
     */
    constructor(endpoint, path_) {
        this.protocols = new Protocols(endpoint, path_, this);
        this.wizard = new Wizard(this.protocols);
        console.info("SmartOrg Javascript API.");
        console.log("Manhattan Build");
        console.log("version 2.2");
        this.networkErrorHandler = null;
    }

    onNetworkError(callBackFunction) {
        this.networkErrorHandler = callBackFunction;
    }

    /** @typedef {object} UserInfo
     * @property {string} uid - User ID generated from server
     */

    /**
     * @external Promise
     * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise Promise}
     */

    /**
     * This callback is called when the result is loaded.
     *
     * @callback PromiseAuthenticateCallbackSuccess
     * @param {UserInfo} ret - The result is loaded.
     */

    /**
     * This callback is called when the result fails to load.
     *
     * @callback PromiseAuthenticateCallbackError
     * @param {Error} err - The error that occurred while loading the result.
     */

    /** Resolves with a {@link PromiseAuthenticateCallbackSuccess}, fails with a {@link PromiseAuthenticateCallbackError}
     *
     * @typedef {Promise} PromiseAuthenticate
     */

    /**
     * Authenticate the user using the credentials provided.
     * This is the first thing that needs to happen after the SmartOrg object
     * has been created.
     *
     * @param {Object} credentials The credentials with which the user is to be authenticated.
     * @param {string} credentials.username The username
     * @param {string} credentials.password The password
     * @returns {PromiseAuthenticate} A promise to authenticate
     *
     * @example
     * // Let's assume the SmartOrg API is accessible at https://localhost/kirk
     * var smartorg = new SmartOrg("https://localhost","kirk");
     * var username = ...; // get the username from somewhere
     * var password = ...; // get the password from somewhere
     * smartorg.authenticate({'username': username, 'password': password})
     *  .then(function (userInfo) {
     *      console.info("user ID " + userInfo.uid + " logged in");
     *  })
     *  .catch(function (err) {
     *      console.error("Error occurred "+err);
     *  });
     */
    authenticate(credentials) {

        this.protocols.makeCredentials(credentials);
        var path_ = this.protocols._getPath('framework/login');
        path_ = `${path_}/${credentials.username}`;

        return this.protocols.apiGet(path_);
    }

    /** @typedef {object} Template
     * @property {string} name - Name of this template
     * @property {string} hasPlatform - Does this template have a platform capability?
     * @property {object} [info] - Information about this template
     * @property {string} [info.Creator] - Name of the creator of this template
     * @property {string} [info.CreatorLink] - Link to the template creator's website
     * @property {string} [info.Description] - Description of this template
     * @property {string} [info.Email] - Email id of template creator
     */

    /**
     * @external Promise
     * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise Promise}
     */

    /**
     * This callback is called when the result is loaded.
     *
     * @callback PromiseTemplatesCallbackTemplateSuccess
     * @param {Template[]} ret - The result is loaded.
     */

    /**
     * This callback is called when the result fails to load.
     *
     * @callback PromiseTemplatesCallbackTemplateError
     * @param {Error} err - The error that occurred while loading the result.
     */

    /** Resolves with a {@link PromiseTemplatesCallbackTemplateSuccess}, fails with a {@link PromiseTemplatesCallbackTemplateError}
     *
     * @typedef {Promise} PromiseTemplates
     */

    /**
     * Obtain all the templates applicable to a node.
     * @param {string} nodeID The ID of the node for which templates are to be fetched.
     * Normally, this would result in all the templates in the system, unless the administrator has restricted
     * the templates that are visible in the portfolio of which this node is a part. In that case,
     * only the restricted templates will be returned.
     * @returns {PromiseTemplates} A promise to provide the list of templates
     * @example
     * smartorg.templatesFor(templatesNodeID)
     *  .then(function (ret) {
     *      console.warn(ret);
     *      // ret[0] --> First Template
     *      // ret[0].name --> Name of first template
     *      // ret[0].hasPlatform --> Does this have a platform?
     *      // ret[0].info.Creator --> Optional name of creator of template
     *      // ret[0].info.CreatorLink --> Optional link to creator website
     *      // ret[0].info.Description --> Optional description of template
     *      // ret[0].info.Email --> Optional email id of creator of template
     *  })
     *  .catch(function (err) {
     *      console.error(err);
     *  })
     */
    templatesFor(nodeID) {
        var path_ = this.protocols._getPath('domain/templates');
        path_ = `${path_}/${nodeID}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Get all templates. Maybe duplicated to templatesFor when node
     * id is empty.
     *
     * @returns {*}
     */
    getAllTemplates() {
        var path_ = this.protocols._getPath('domain/alltemplates');
        path_ = `${path_}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * @typedef {object} AclGroup
     * @property {Array<string>} editors - Array of user ids of editors
     * @property {Array<string>} portfolioAdmins - Array of user ids of portfolio administrators
     * @property {Array<string>} viewers - Array of user ids of viewers
     */
    /**
     * @typedef {object} Acl
     * @property {AclGroup} group - Group Access Control List
     * @property {string} owner - User ID of owner of portfolio
     * @property {AclGroup} user - What's this for?
     */
    /**
     * @typedef {object} Node
     * @property {string} name - Name of this node
     * @property {string} _id - ID of this node
     * @property {string} _rev - Revision number of this node
     * @property {string} data - A pointer to the data node of the portfolio
     * @property {string} treeID - The name of the tree to which this node belongs
     * @property {Array<string>} children - An array of child node IDs under this node
     * @property {Array<string>} commands - An array of templates that apply to this node. Portfolio nodes tend not to have commands, so this array is likely 0 for such nodes. If there is a template, then only the first item in this array would be populated. This has been kept an array just in case the design evolves to support multiple templates for a node.
     * @property {boolean} isPlatform - Is the current node a platform node? This is always false for portfolio nodes.
     * @property {Acl} [acl] - The access control list information for this portfolio. This attribute is only present if the node is a root (portfolio) node.
     */

    /**
     * @external Promise
     * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise Promise}
     */

    /**
     * This callback is called when the result is loaded.
     *
     * @callback PromisePortfoliosCallbackSuccess
     * @param {Node[]} ret - A collection of root (or portfolio) nodes that the user is allowed to access.
     */

    /**
     * This callback is called when the result fails to load.
     *
     * @callback PromisePortfoliosCallbackError
     * @param {Error} err - The error that occurred while loading portfolios.
     */

    /** Resolves with a {@link PromisePortfoliosCallbackSuccess}, fails with a {@link PromisePortfoliosCallbackError}
     *
     * @typedef {Promise} PromisePortfolios
     */

    /**
     * Fetches all the portfolios available to user, based on access control rules
     * setup by the administrator. If no rules are set for the user, all
     * portfolios will be returned.
     *
     * @returns {PromisePortfolios} A promise to the portfolios
     * @example
     * smartorg.portfolios()
     *  .then(function (portfolios) {
     *      portfolios.map(function (portfolio) {
     *          console.log(portfolio);
     *      })
     *  })
     *  .catch(function (err) {
     *      console.error(err);
     *  })
     */
    portfolios() {
        var path_ = this.protocols._getPath('template/portfolios');

        return this.protocols.apiGet(path_);
    }

    /** @typedef {object} Input
     * @property {string} Key The key of this input
     * @property {string} Type The type of this input (SCALAR|DISTRIBUTION|TABLE)
     * @property {string} Units The units of this input
     * @property {string} Display The display prompt of this input
     * @property {string} Description The description of this input
     * @property {string} Constraint The constraint placed on this input (double|integer|year|date|text)
     * @property {string} Val The value of this input
     * @property {boolean} Inherited Is this an inherited input (shared variable)?
     * @property {string} InputType Is this data local to the node or overridden? (LOCAL|OVERRIDE)
     * @property {number} isLeaf If this node is a leaf, this is set to 1 otherwise 0
     * @property {string} CellLink The link to the Excel cell
     */
    /** @typedef {object} MenuItems
     * @property {string} nodeAttribute - This has identifiers like "r" or "rw" to specify if this node is editable.
     * @property {string} nodeID - The node for which the inputs have been fetched
     * @property {Input[]} inputs - The inputs that have been found
     * @property {string} selectedInputKey - what is this?
     * @property {string} targetDS - The dataset ID. Why is it called "target"?
     * @property {string} templateID - The ID of the template associated with this node
     * @property {string} user - The name of the user who has made the request
     */

    /**
     * @external Promise
     * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise Promise}
     */

    /**
     * This callback is called when the result is loaded.
     *
     * @callback PromiseInputsCallbackSuccess
     * @param {MenuItems} ret - A collection of inputs matching the keys at the node
     */

    /**
     * This callback is called when the result fails to load.
     *
     * @callback PromiseInputsCallbackError
     * @param {Error} err - The error that occurred while loading inputs.
     */

    /** Resolves with a {@link PromiseInputsCallbackSuccess}, fails with a {@link PromiseInputsCallbackError}
     *
     * @typedef {Promise} PromiseInputs
     */

    /**
     * Given the keys for inputs and a node ID, all the input data
     * at that node matching the keys are fetched.
     *
     * @param {string} nodeID The ID of the node at which the inputs are to be fetched
     * @param {string} inputKeys A pipe-delimited list of input keys that are to be fetched at the node
     * @returns {PromiseInputs} A promise to the inputs
     * @example
     * nodeID = "012345";
     * inputKeys = "marketSize|marketShare|discountRate";
     * smartorg.inputsFor(nodeID, inputKeys)
     *  .then(function (inputs) {
     *      inputs.menuItems.map(function (input) {
     *          console.log(input);
     *      })
     *  })
     *  .catch(function (err) {
     *      console.error(err);
     *  })
     */

    inputsFor(nodeID, inputKeys) {
        var path_ = this.protocols._getPath('template/inputs');
        path_ = `${path_}/${nodeID}/${encodeURIComponent(inputKeys)}`;

        return this.protocols.apiGet(path_);
    }

    /** @typedef {object} CompareValueChildData
     * @property {Array<string>} childLeafNames - A list of names of all children immediately below the selected node
     * @property {Array<object>} compareValueData - A list of values of all children immediately below the selected node. The values are picked from the template (portfolio or platformPortfolio) based on the *Keys* parameter (an array of output keys) for the COMPARE_VALUE command.
     * @property {string} nodeName - Name of the selected node
     */

    /** @typedef {object} CompareValueData
     * @property {object} commandJSON - This has attributes that setup the chart display
     * @property {CompareValueChildData} highlightedNode - This is the appropriate value data in the node that has been selected
     * @property {CompareValueChildData} siblingNode - This is the appropriate value data in the sibling node that has been selected. It will be *null* if no sibling has been selected.
     */

    /** @typedef {object} CompareUncOutputData
     * @property {string} Display - Full display describing the output
     * @property {string} Units - Units of the output
     * @property {number} Mean - The mean value of the combined uncertainty bar of this output in the Tornado
     * @property {string} NodeName - Name of the child
     * @property {Array<number>} Summary - The low (0), medium (1) and high (2) values of this output based on the combined uncertainty bar in the Tornado.
     */

    /** @typedef {object} CompareUncData
     * @property {Array<Array<CompareUncOutputData>>} highlightedNode - This is a nested array. First, it has as many elements as there are children. Each element is an array of outputs, one for each Tornado *ValueMetricKey*.
     * @property {Array<Array<CompareUncOutputData>>} siblingNode - This is a nested array. First, it has as many elements as there are children. Each element is an array of outputs, one for each Tornado *ValueMetricKey*. An empty array is returned if no sibling has been selected.
     * @property {string} nodeName - Node name of highlighted node
     * @property {string} shadowNodeName - Node name of sibling node. This is null if no sibling has been selected.
     */
    /**
     * This callback is called when the result is loaded.
     *
     * @callback PromiseActionCallbackSuccess
     * @param {(CompareValueData|CompareUncData)} ret - An object that differs based on the command that was executed
     */

    /**
     * This callback is called when the result fails to load.
     *
     * @callback PromiseActionCallbackError
     * @param {Error} err - The error that occurred while loading inputs.
     */

    /** Resolves with a {@link PromiseActionCallbackSuccess}, fails with a {@link PromiseActionCallbackError}
     *
     * @typedef {Promise} PromiseActionResults
     */

    /**
     * Executes action command and fetches the results. This is a general way to access any template menu command.
     *
     * @param {string} actionID - The ID for the action specified in the template menu JSON (portfolio or application structure).
     * @param {string} nodeID - The node for which the action is to be applied
     * @param {object} packedReportOptions - Report options for this action.
     * @returns {PromiseActionResults} A promise to the result of running the action
     * @example
     * smartorg.actionFor(actionID, nodeID)
     *  .then(function (data) {
     *      console.log(data); // The data object is structured differently for each command
     *  })
     *  .catch(function (err) {
     *      console.error(err);
     *  })
     */
    actionFor(actionID, nodeID, packedReportOptions) {
        var path_ = this.protocols._getPath('template/actionMenu');
        path_ = `${path_}/${actionID}/${nodeID}/${packedReportOptions}`;

        return this.protocols.apiGet(path_);
    }

    /** @typedef {object} MenuObject
     * @property {string} Command - The command to be invoked by this action
     * @property {string} displayText - The display for this menu item
     * @property {string} menuID - The unique key for this menu item
     * @property {string} nodeID - The node ID for from this menu item has been fetched
     * @property {boolean} isOutput - Is this command an output command? *TABLE_INPUT* and *INPUT_SCREEN* are input commands, and all else are output commands.
     * @property {number} level - What is this?
     * @property {object} parameters - These specify what needs to go into the command. They differ based on the command.
     * @property {string} A - Legacy Javascript code injection. Ignore when writing your applications.
     */

    /**
     * This callback is called when the result is loaded.
     *
     * @callback PromiseActionMenuCallbackSuccess
     * @param {Object} menu - An object that contains the menu items
     * @param {Object} menu.menuItems - The menu items for the node
     * @param {Array<MenuObject>} menu.menuItems.Actions - The menu items under the "Actions" section
     * @param {Array<MenuObject>} menu.menuItems.Management - The menu items under the "Management" section
     */

    /**
     * This callback is called when the result fails to load.
     *
     * @callback PromiseActionMenuCallbackError
     * @param {Error} err - The error that occurred while loading inputs.
     */

    /** Resolves with a {@link PromiseActionMenuCallbackSuccess}, fails with a {@link PromiseActionMenuCallbackError}
     *
     * @typedef {Promise} PromiseActionMenu
     */

    /**
     * Fetches the menu (from the appropriate template) that is associated with a node.
     *
     * @param {string} nodeID The ID of the node for which a menu is to be fetched
     * @returns {PromiseActionMenu} A promise to return the action menu for the specified node
     * @example
     * smartorg.actionMenuFor(nodeID)
     *  .then(function (menu) {
     *      console.log(menu);
     *  })
     *  .catch(function (err) {
     *      handleError(err);
     *  });
     */
    actionMenuFor(nodeID) {
        var path_ = this.protocols._getPath('template/actionMenu');
        path_ = `${path_}/${nodeID}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Get share data (Input screen) structure.
     *
     * @param nodeID {string} Tree node ID.
     * @returns {Promise} A promise to return shared data for the
     *     specified node.
     * @example
     * smartorg.sharedDataFor(nodeID)
     *  .then(function (menu) {
     *      console.log(menu);
     *  })
     *  .catch(function (err) {
     *      handleError(err);
     *  });
     */
    sharedDataFor(nodeID) {
        var path_ = this.protocols._getPath('template/sharedData');
        path_ = `${path_}/${nodeID}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * @typedef {object} InputKeyValPair
     * @property {string} Key - the key of the input, eg startYear or discountRate
     * @property {string} Val - the value of the input eg 0.89
     */

    /**
     * Saves inputs
     * @param {string} nodeID The ID of the node that's inputs are to be saved
     * @param {Array<InputKeyValPair>} inputs array of inputs to be saved
     * @returns {string} msg - save status message
     * @example saved dataset for node 'leafNodeExample' by calling template 'exampleTemplateName'
     * @example
     *
     * //to save inputs for a given node
     * smartorg.saveInputs(nodeID, inputs)
     *  .then(function (ret) {
     *      console.log(ret); // Need documentation on ret
     *  })
     *  .catch(function (err) {
     *      handleError(err);
     *  });
     */
    saveInputs(nodeID, inputs) {
        var path_ = this.protocols._getPath('domain/template');
        var body_ = {
            nodeID: nodeID,
            inputs: inputs
        };

        return this.protocols.apiPut(path_, body_);
    }

    overrideInput(targetNodeID, sourceNodeID, inputKey) {
        var path_ = this.protocols._getPath('domain/overrideInput');

        var body_ = {
            'targetNodeID': targetNodeID,
            'sourceNodeID': sourceNodeID,
            'inputKey': inputKey
        };

        return this.protocols.apiPut(path_, body_);
    }

    deoverrideInput(targetNodeID, inputKey) {
        var path_ = this.protocols._getPath('domain/deoverrideInput');

        var body_ = {
            targetNodeID: targetNodeID,
            inputKey: inputKey
        };

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * This callback is called when the Paste Node succeeds
     *
     * @callback PromisePasteStatusCallbackSuccess
     * @param {Number} status - 0=success non-zero=failure
     * @param {string} message - e.g. 'pasted data'
     */

    /**
     * This callback is called when the Paste Node fails
     *
     * @callback PromisePasteStatusCallbackError
     * @param {Number} status - http status code, e.g. 500
     * @param {string} statusText -http status text, e.g. 'INTERNAL SERVER ERROR'
     * @param {string} message - e.g. incorrect _id 'ERROR PasteNode Exception ('_id')'
     */

    /** Resolves with a {@link PromisePasteStatusCallbackSuccess}, fails with a {@link PromisePasteStatusCallbackError}
     *
     * @typedef {Promise} PromisePasteStatus
     */

    /**
     * Paste Node
     * @param {string} targetParentNodeID parent Node ID
     * @param {string}  nodeToPaste Node ID to paste
     * @returns {PromisePasteStatus} A promise to provide status message of the Paste Node operation.

     *
     * @example
     * // to paste a node under a target parent node
     * smartorg.pasteNode(targetParentNodeID, nodeToPaste)
     *  .then(function (ret) {
     *      console.warn(ret);
     *  })
     *.catch(function (err) {
     *      handleError(err);
     *  })
     */
    pasteNode(targetParentNodeID, nodeToPaste) {
        var path_ = this.protocols._getPath('domain/paste');
        path_ = `${path_}/${targetParentNodeID}/${nodeToPaste}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Set a node (and its children) readonly
     * @param nodeID
     * @param includeAllChildren
     * @returns {string} msg - Made node (and its descendants) readonly
     * @example
     * smartorg.makeReadOnly(nodeID, includeAllChildren)
     * .then(function (ret) {
     *      console.warn(ret);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })
     */
    makeReadOnly(nodeID, includeAllChildren) {
        var path_ = this.protocols._getPath('domain/readonly');
        var attribute = "r";
        path_ = `${path_}/${nodeID}/${attribute}/${includeAllChildren}`;

        return this.protocols.apiPost(path_, {});
    }

    /**
     * Set a node (and its children) editable
     * @param nodeID
     * @param includeAllChildren
     * @returns {string} msg - Made node (and its descendants) editable
     * @example
     * smartorg.makeEditable(nodeID, includeAllChildren)
     * .then(function (ret) {
     *      console.warn(ret);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })
     */
    makeEditable(nodeID, includeAllChildren) {
        var path_ = this.protocols._getPath('domain/readonly');
        var attribute = "rw";
        path_ = `${path_}/${nodeID}/${attribute}/${includeAllChildren}`;

        return this.protocols.apiPost(path_, {});
    }

    /**
     * Fetch change log at or under current node.
     *
     * @param {string} nodeID - The ID of the node at or under which change log is to be fetched
     * @returns {Array<changes>}
     */
    changeLog(nodeID) {
        var path_ = this.protocols._getPath('domain/changes');
        path_ = `${path_}/${nodeID}`;

        return this.protocols.apiPost(path_, {});
    }

    /**
     * Recalculate entire portfolio
     * @param treeID
     * @param recalType
     * @param startNodeId
     * @returns {string} msg - tree is recalculated
     * @example
     * smartorg.recalculatePortfolio(treeID)
     * .then(function (ret) {
     *      console.warn(ret);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })
     */
    recalculatePortfolio(treeID, recalType, startNodeId) {
        var path_ = this.protocols._getPath('domain/recalculate');
        path_ = `${path_}/${encodeURIComponent(treeID)}`;

        var body_ = {
            recalType: recalType,
            startNodeId: startNodeId
        };

        return this.protocols.apiPost(path_, body_);
    }

    /**
     * Get the total number of orphan nodes in tree.
     *
     * @param treeID {string} Tree id aka root node name.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     *     then: number of nodes.
     */
    getOrphanNodesCount(treeID) {
        var path_ = this.protocols._getPath('domain/tree/analyse/orphan');
        path_ = `${path_}/${encodeURIComponent(treeID)}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Fix (delete) all orphan nodes in tree.
     *
     * @param treeID {string} Tree id aka root node name.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     *     then: number of deleted nodes.
     */
    fixOrphanNodesCount(treeID) {
        var path_ = this.protocols._getPath('domain/tree/analyse/orphan');
        path_ = `${path_}/${encodeURIComponent(treeID)}`;

        return this.protocols.apiDelete(path_);
    }

    /**
     * Get calculation engine version number and log level.
     *
     * @return {{}} Promise.
     *     then: {logLevel: "something", versionNumber: "something"}
     *     catch: Error.
     */
    getCalculationEngineInfo() {
        var path_ = this.protocols._getPath('domain/calcengine/version');

        return this.protocols.apiGet(path_);
    }

    /**
     * Fetch lines from bottom of calculation engine log
     * @param linesFromBottom
     * @returns {string} msg - fetched lines from bottom of calculation engine log
     * @example
     * smartorg.fetchCalculationEngineLog(treeID)
     * .then(function (ret) {
     *      console.warn(ret);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })
     */
    fetchCalculationEngineLog(linesFromBottom) {
        var path_ = this.protocols._getPath('domain/fetchCalculationEngineLog');
        path_ = `${path_}/${linesFromBottom}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Export portfolio
     * @param nodeID
     * @returns {string} msg - entire portfolio is exported
     * @example
     * smartorg.exportPortfolio(treeID)
     * .then(function (ret) {
     *      console.warn(ret);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })
     */
    exportPortfolio(nodeID) {
        var path_ = this.protocols._getPath('domain/exportPortfolio');
        path_ = `${path_}/${nodeID}`;

        return this.protocols.apiPost(path_, {});
    }

    /**
     * Fetch all exported portfolio paths
     * @returns {string} exported portfolio path list
     * @example
     * smartorg.fetchAllExportedPortfolioPaths(treeID)
     * .then(function (ret) {
     *      console.warn(ret);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })
     */
    fetchAllExportedPortfolioPaths() {
        var path_ = this.protocols._getPath('domain/fetchAllExportedPortfolioPaths');
        path_ = `${path_}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Import portfolio
     * @param includeData
     * @param pathToImportFiles64
     * @param newTreeName64
     * @returns {string} msg - portfolio is imported
     * @example
     * smartorg.exportPortfolio(treeID)
     * .then(function (ret) {
     *      console.warn(ret);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })
     */
    importPortfolio(includeData, pathToImportFiles64, newTreeName64) {
        var path_ = this.protocols._getPath('domain/importPortfolio');
        path_ = `${path_}/${includeData}/${pathToImportFiles64}/${newTreeName64}`;

        return this.protocols.apiPost(path_, {});
    }

    /**
     * Create new Portfolio
     * @param {string} newPortfolioName Name of the new portfolio
     * @returns {string} msg - create Portfolio status message
     * @example
     * smartorg.createPortfolio(newPortfolioName)
     * .then(function (ret) {
     *      console.warn(ret);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })
     */
    createPortfolio(newPortfolioName) {
        var path_ = this.protocols._getPath('domain/tree');
        path_ = `${path_}/${encodeURIComponent(newPortfolioName)}`;

        var body_ = {};

        return this.protocols.apiPost(path_, body_);
    }

    /**
     * Create new Portfolio from existing portfolio
     * @param {string} newPortfolioName Name of the new portfolio
     * @param categoriesConfig
     * @param acl
     * @param chosenTemplates
     * @param chosenGroups
     * @returns {string} msg - create Portfolio status message
     * @example
     * smartorg.createPortfolioFromExisting(newPortfolioName, categoriesConfig, acl)
     * .then(function (ret) {
     *      console.warn(ret);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })
     */
    createPortfolioFromExisting(newPortfolioName, categoriesConfig, acl, chosenTemplates, chosenGroups) {
        var path_ = this.protocols._getPath('domain/portfolio');
        var body_ = {
            treeID: newPortfolioName,
            categoriesConfig: categoriesConfig,
            acl: acl,
            chosenTemplates: chosenTemplates,
            chosenGroups: chosenGroups
        };

        return this.protocols.apiPost(path_, body_);
    }

    /**
     * Create a new Node under an existing portfolio
     * @param {string} parentNodeID parent node ID
     * @param {string} newNodeName new Node name
     * @param {string} templateName template name
     * @param {string} platformOrLeaf 'platform' or 'leaf'
     * @returns {string} msg - create Node status message
     * @example
     * smartorg.createNode(parentNodeID, newNodeName, templateName, platformOrLeaf)
     *  .then(function (ret) {
     *      console.warn(ret);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })
     */
    createNode(parentNodeID, newNodeName, templateName, platformOrLeaf) {
        var path_ = this.protocols._getPath('domain/node');

        var body_ = {
            nodeID: parentNodeID,
            newNodeName: newNodeName,
            templateName: templateName,
            platformOrLeaf: platformOrLeaf
        };

        return this.protocols.apiPost(path_, body_);
    }

    /**
     * This callback is called when the node is loaded.
     *
     * @callback PromiseNodeCallbackSuccess
     * @param {Node} node - The node object
     */

    /**
     * This callback is called when the result fails to load.
     *
     * @callback PromiseNodeCallbackError
     * @param {Error} err - The error that occurred while reading node.
     */

    /** Resolves with a {@link PromiseNodeCallbackSuccess}, fails with a {@link PromiseNodeCallbackError}
     *
     * @typedef {Promise} PromiseNodeResults
     */

    /**
     * Retrieves a node by its ID
     * @param {string} nodeID The ID of the node to get
     * @returns {PromiseNodeResults} A promise to return the node
     * @example
     * smartorg.nodeBy(nodeID)
     * .then(function (node) {
     *     console.log(node);
     *  })
     * .catch(function (err) {
     *       handleError(err);
     *  })
     */
    nodeBy(nodeID) {
        var path_ = this.protocols._getPath('domain/node');
        path_ = `${path_}/${nodeID}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Edit Node
     * @param {string} nodeID The ID of the node to edit
     * @param {string} newNodeName The new node name
     * @param {string} templateName The template name
     * @param {Array<string>} children List of children ids.
     * @returns {axios.Promise}
     * @example
     * smartorg.editNode(nodeID, newNodeName, templateName)
     * .then(function (ret) {
     *          console.warn(ret);
     *      })
     * .catch(function (err) {
     *          handleError(err);
     *      })
     *
     */
    editNode(nodeID, newNodeName, templateName, children) {
        var path_ = this.protocols._getPath('domain/node');

        var body_ = {
            nodeID: nodeID,
            newNodeName: newNodeName,
            templateName: templateName,
            children: children
        };

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * Delete Node
     * @param {string} nodeID The ID of the node to delete
     * @returns {string} msg - delete status message
     * @example
     * smartorg.deleteNode(nodeToDeleteID)
     * .then(function (ret) {
     *      console.warn(ret);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })
     */
    deleteNode(nodeID) {
        var path_ = this.protocols._getPath('domain/node');
        path_ = `${path_}/${nodeID}`;

        return this.protocols.apiDelete(path_);
    }

    /**
     * Tornado Report
     * @param {string} nodeID The ID of the node for the Tornado
     * @returns {*} tornadoData in JSON
     * @example
     * smartorg.tornado(nodeID)
     * .then(function (tornado) {
     *     console.log(tornado);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })
     */
    tornado(nodeID) {
        var path_ = this.protocols._getPath('template/tornado');
        path_ = `${path_}/${nodeID}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * This callback is called when the treeFor response succeeds
     *
     * @callback PromiseTreeCallbackSuccess
     * @param {Node[]} nodes - tree structure of Nodes
     */

    /**
     * This callback is called when the treeFor response fails
     *
     * @callback PromiseTreeCallbackError
     * @param {string} message - base64 encoded error message
     */

    /** Resolves with a {@link PromiseTreeCallbackSuccess}, fails with a {@link PromiseTreeCallbackError}
     *
     * @typedef {Promise} PromiseTree
     */

    /**
     * Gets the Tree for a Portfolio
     * @param {string} portfolioName Portfolio Name
     * @returns {PromiseTree} A promise representing the Portfolio Tree structure
     * @example
     * smartorg.treeFor(treeID)
     * .then(function (tree) {
     *      console.log(tree);
     *  })
     * .catch(function (err) {
     *      handleError(err);
     *  })

     */
    treeFor(portfolioName) {
        var path_ = this.protocols._getPath('domain/tree');
        path_ = `${path_}/${encodeURIComponent(portfolioName)}`;

        return this.protocols.apiGet(path_);
    }

    /** @typedef {object} CategoryConfig
     * @property {string} AppliesTo
     * @property {boolean} IsMultiSelect
     * @property {string} CategoryName - e.g. Region
     * @property {array.<string>} CategoryEntries - e.g US, Asia, Europe
     */

    /**
     * This callback is called when the categoriesConfigFor response succeeds
     *
     * @callback CategoryConfigPromiseCallbackSuccess
     * @param {CategoryConfig[]} categoryConfigs - array of CategoryConfig objectsj
     */

    /**
     * This callback is called when the categoriesConfigFor response fails
     *
     * @callback CategoryConfigPromiseCallbackError
     * @param {string} message - base64 encoded error message
     */

    /** Resolves with a {@link CategoryConfigPromiseCallbackSuccess}, fails with a {@link CategoryConfigPromiseCallbackSuccess}
     *
     * @typedef {Promise} CategoryConfigPromise
     */

    /**
     *
     *
     * @param rootNodeID
     * @returns {CategoryConfigPromise}
     * @example
     *  smartorg.categoriesConfigFor(rootNodeID)
     *    .then(function(categoryConfigs) {
     *          // categoryConfigs is an array of categoryConfig
     *          categoryConfigs.forEach(function(categoryConfig) {
     *                console.log(categoryConfig.AppliesTo);
     *                console.log(categoryConfig.IsMultiSelect);
     *                console.log(categoryConfig.CategoryName);                  //e.g. Region
     *                console.log(categoryConfig.AutoPropagateUp);
     *
     *                categoryConfig.CategoryEntries.forEach(function(entry) {
     *                      console.log("category entry "+ entry);               // e.g. US, Asia, Europe
     *                });
     *           });
     *    })
     *       .catch(function(err) {
     *           handleError(err);
     *   });
     */

    categoriesConfigFor(rootNodeID) {
        var path_ = this.protocols._getPath('domain/categoryConfig');
        path_ = `${path_}/${rootNodeID}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * This callback is called when the saveCategoryConfig response succeeds
     *
     * @callback CategoryConfigSavePromiseCallbackSuccess
     * @param {string} statusMessage - status of saveCategoryConfig ( new or existing )
     */

    /** Resolves with a {@link CategoryConfigSavePromiseCallbackSuccess}, fails with a {@link CategoryConfigPromiseCallbackSuccess}
     *
     * @typedef {Promise} CategoryConfigSavePromise
     */

    /**
     * @param rootNodeID
     * @param {CategoryConfig} categoryConfig new or existing category config object ( not string )
     * @returns  {CategoryConfigSavePromise}
     * @example
     *  smartorg.saveCategoryConfig(rootNodeID, categoryConfig)
     *    .then(function(statusMessage) {
     *         console.log(statusMessage);
     *    })
     *       .catch(function(err) {
     *           handleError(err);
     *   });
     */
    saveCategoryConfig(rootNodeID, categoryConfig) {
        var path_ = this.protocols._getPath('domain/categoryConfig');
        path_ = `${path_}/${rootNodeID}`;

        return this.protocols.apiPost(path_, categoryConfig);
    }

    /**
     * Delete a category from config.
     *
     * @param rootNodeID
     * @param categoryName {string} Category name, should be an existing one.
     * @returns {*}
     * @example
     *  smartorg.deleteCategoryConfig(rootNodeID, categoryName)
     *    .then(function(statusMessage) {
     *         console.log(statusMessage);
     *    })
     *       .catch(function(err) {
     *           handleError(err);
     *   });
     */
    deleteCategoryConfig(rootNodeID, categoryName) {
        var path_ = this.protocols._getPath('domain/categoryConfig');
        path_ = `${path_}/${rootNodeID}/${encodeURIComponent(categoryName)}`;

        return this.protocols.apiDelete(path_);
    }

    /**
     * Get array of tags for a node.
     *
     * @param nodeID {string}
     * @returns {*} Tags promise.
     *     then: array of tags
     *     catch: {status: err code, statusText: status text,
     *         message: err message}
     *
     * @example
     * smartorg.tagsFor(nodeId).then(
     *   function (result) {
     *     console.log(result);
     *   }).catch(function (err) {
     *     console.log(err);
     *   });
     */
    tagsFor(nodeID) {
        var path_ = this.protocols._getPath('domain/tags');
        path_ = `${path_}/${nodeID}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * This callback is called when the saveTags response succeeds
     *
     * @callback SaveTagsPromisePromiseCallbackSuccess
     * @param {string} statusMessage - status of saveTags
     */

    /**
     * This callback is called when the saveTags response fails
     *
     * @callback SaveTagsPromisePromiseCallbackSuccess
     * @param {string} statusMessage - error message
     */

    /** Resolves with a {@link SaveTagsPromisePromiseCallbackSuccess}, fails with a {@link SaveTagsPromisePromiseCallbackSuccess}
     *
     * @typedef {Promise} SaveTagsPromise
     */

    /**
     * @typedef {Array<String>} TagSet
     * @example
     * //First element in the array is the node ID
     * //Second element in the array is a comma delimited string of 1 or more CategoryName:CategoryItemName, all is required
     * ["b8652f5d935cf2e22e8e5d01de065d5d","all, Region:Africa, Funded:No, CropType:legumes"]
     */

    /**
     * Saves array of tags for a node
     * @param nodeID
     * @param {Array<TagSet>} newTagString
     * @returns {SaveTagsPromise}
     * @example
     *  smartorg.saveTags(nodeId, newTagString)
     *  .then(function(msg) {
     *
     *       console.log(msg);
     * })
     * .catch(function(err) {
     *       console.error(err);
     * });
     */
    saveTags(nodeID, newTagString) {
        var path_ = this.protocols._getPath('domain/tags');
        path_ = `${path_}/${nodeID}`;
        var body_ = JSON.parse(newTagString);

        return this.protocols.apiPost(path_, body_);
    }

    /**
     * Save a node's description.
     * Both two parameters need to encode in base 64.
     *
     * @param nodeID {string} Tree node Id.
     * @param description {string} Html format description.
     */
    saveDescripton(nodeID, description) {
        var path_ = this.protocols._getPath('domain/description');
        path_ = `${path_}/${nodeID}`;
        var body_ = {
            'description': description
        };

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * Save a node's smartText.
     * Both two parameters need to encode in base 64.
     *
     * @param nodeID {string} Tree node Id.
     * @param smartText {string} Html format smartText.
     */
    saveSmartText(nodeID, smartText) {
        var path_ = this.protocols._getPath('domain/smartText');
        path_ = `${path_}/${nodeID}`;
        var body_ = {
            'smartText': smartText
        };

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * Lookup inputs and outputs that need to go into the universal table
     * by key. We need to supply nodeID, and at least one of
     * input or output keys (or both).
     * @param nodeID {string} Tree node Id.
     * @param schema {object} - contains selectedInputKeys {Array},
     *      selectedOutputKeys {Array} and selectedTableInputKeys {object}
     * @param packedReportOptions - contains encoded reportOptions
     * @returns list for fields to be displayed in universal output
     */
    getFieldList(nodeID, schema, packedReportOptions) {
        var path_ = this.protocols._getPath('domain/universal/io/fields');
        path_ = `${path_}`;

        var body = {
            "nodeID": nodeID,
            "schema": schema,
            "packedReportOptions": packedReportOptions
        };
        return this.protocols.apiPost(path_, body);
    }

    /**
     * lookup inputs from astro_data and gives the specific fields
     * @param templateName
     * @param leafOrPlatform
     * @param nodeID {string} Tree node Id.
     * @returns list for fields to be displayed in universal output
     */
    getDisplayNameList(templateName, leafOrPlatform, nodeID) {
        var path_ = this.protocols._getPath('domain/universal/io/displayNames');
        path_ = `${path_}/${encodeURIComponent(templateName)}/${leafOrPlatform}/${nodeID}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * saving univSchema to rootNode
     * @param nodeID {string}
     * @param schema - has templateName, IOKeys & access
     * @param oldName - to check and replace it with new schema name
     */
    saveUnivSchemaToRoot(nodeID, schema, oldName) {
        var path_ = this.protocols._getPath('domain/universal/io/saveUnivSchemaToRoot');
        path_ = `${path_}`;
        var body = {
            "nodeID": nodeID,
            "schema": schema,
            "oldName": oldName
        };
        return this.protocols.apiPost(path_, body);
    }

    /**
     * delete specific schema from rootNode
     * @param nodeID {string}
     * @param schemaName {string}
     */
    deleteUnivSchemaFromRoot(nodeID, schemaName) {
        var path_ = this.protocols._getPath('domain/universal/io/deleteUnivSchemaFromRoot');
        path_ = `${path_}/${nodeID}/${encodeURIComponent(schemaName)}`;
        return this.protocols.apiPut(path_, undefined);
    }

    /**
     * replace univSchema in rootNode
     * @param nodeID {string}
     * @param schemaName {string}
     * @param oldSchemaName {string}
     * @param univSchema - schema has templateName, inputKeys, outputKeys & access
     */
    replaceUnivSchemaInRoot(nodeID, schemaName, univSchema, oldSchemaName) {
        var path_ = this.protocols._getPath('domain/universal/io/replaceUnivSchemaInRoot');
        path_ = `${path_}/${nodeID}/${encodeURIComponent(schemaName)}/${encodeURIComponent(oldSchemaName)}`;
        return this.protocols.apiPost(path_, univSchema);
    }

    /**
     * save inputs to corresponding nodes
     * @param inputs {{}}
     *
     */
    universalSaveInputs(inputs) {
        var path_ = this.protocols._getPath('domain/universal/io/saveInputs');
        var body = {
            "inputs": inputs
        };
        return this.protocols.apiPost(path_, body);
    }

    /**
     * Get current api version number.
     *
     * @returns {*} Api version number promise.
     *
     *      //to do: Fan this is not complete needs @example
     *     then: string of version number.
     *     catchL {status: err code, statusText: status text,
     *         message: err message}
     */
    getApiVersionNumber() {
        var path_ = this.protocols._getPath('domain/api/version/number');

        return this.protocols.apiGetNoAuth(path_);
    }

    /**
     * Get simple outputs from node data
     * @returns {*} simple outputs
     *
     */
    getSimpleOutputs(nodeID) {
        var path_ = this.protocols._getPath('domain/output');
        path_ = `${path_}/${nodeID}`;
        return this.protocols.apiGet(path_);
    }

    /**
     * lookup host specific short code to return url
     * @param {string} shortCode - the short code to lookup url
     * @returns ShortUrl or 404 Not Found
     */
    getInfoForShortURL(shortCode) {
        var path_ = this.protocols._getPath('framework/shorturl');
        path_ = `${path_}/${shortCode}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * @typedef {object} ShortUrl
     * @property {string} _id - short url code e.g.  JwijDXE
     * @property {string} _rev - internal document revision ( ignore )
     * @property {string} url - long url
     * @property {boolean} byPassLogin
     * @property {string} urlLinkName - e.g. Report 2016 PMF submission
     * @property {string} csum - checksum of the long url used for duplicate prevention ( ignore )
     * @property {array.<string>} CategoryEntries - e.g US, Asia, Europe
     */

    /**
     * This callback is called when the ShortUrlPromise response succeeds
     *
     * @callback ShortUrlPromiseeCallbackSuccess
     * @param {ShortUrl[]} ShortUrl - a ShortUrl object
     */

    /**
     * This callback is called when the ShortUrlPromise response fails
     *
     * @callback ShortUrlPromiseCallbackError
     * @param {string} message - base64 encoded error message
     */

    /**
     * Resolves with a {@link ShortUrlPromiseeCallbackSuccess},
     * fails with a {@link ShortUrlPromiseCallbackError}
     *
     * @typedef {Promise} ShortUrlPromise
     */

    /**
     *
     * @param {string} urlToShorten - long url to shorten
     * @param {boolean} byPassLogin - true or false flag to bypass login
     * @param {string} urlLinkName - optional link or report name
     * @returns ShortUrlPromise
     */
    createShortURL(urlToShorten, byPassLogin, urlLinkName) {

        var path_ = this.protocols._getPath('framework/shorturl/');   // note the / at the end, this is important to match url for POST

        var body_ = {
            "ByPassLogin": byPassLogin,
            "urlToShorten": encodeURIComponent(urlToShorten),
            "urlLinkName": urlLinkName
        };

        return this.protocols.apiPost(path_, body_);
    }

    /**
     * Generate zendesk authentication url (token) for one login.
     *
     * @returns {*} Get url promise.
     *     then: url string.
     *     catch: error status, text and message.
     *
     * @example
     * smartorg.zendeskAuthUrl().then(
     *   function (result) {
     *     console.log(result);
     *   })
     *   .catch(function (err) {
     *     console.err(err);
     *   });
     */
    zendeskAuthUrl(page) {
        var path_ = this.protocols._getPath('domain/zenlogin');
        path_ = `${path_}/${page}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Generate zendesk authentication url (token) for one login.
     * Return to page specified by front end.
     *
     * If url is not start with "https://smartorg.zendesk.com",
     * it will redirect you to that page.
     *
     * @returns {*} Get url promise.
     *     then: url string.
     *     catch: error status, text and message.
     *
     * @example
     * smartorg.zendeskRedirect(returnTo).then(
     *   function (result) {
     *     console.log(result);
     *   })
     *   .catch(function (err) {
     *     console.err(err);
     *   });
     */
    zendeskRedirect(returnTo) {
        var path_ = this.protocols._getPath('domain/zendesk/redirect');
        path_ = `${path_}/${encodeURIComponent(returnTo)}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Get current logged in user profile.
     *
     * @returns {*} Get user profile promise.
     *     then: dict of user profile info.
     *     catch: error status, text and message.
     *
     * @example
     * smartorg.getUserProfile().then(
     *   function (result) {
     *     console.log(result);
     *   })
     *   .catch(function (err) {
     *     console.err(err);
     *   });
     */
    getUserProfile() {
        var path_ = this.protocols._getPath('domain/user');

        return this.protocols.apiGet(path_);
    }

    /**
     * This callback is called when the SaveUserProfilePromise
     * response succeeds
     *
     * @callback SaveUserProfilePromiseCallbackSuccess
     * @param {string} message - plaintext message
     */

    /**
     * This callback is called when the SaveUserProfilePromise
     * response fails
     *
     * @callback SaveUserProfilePromiseCallbackError
     * @param {string} message - plaintext message
     */

    /**
     * Resolves with a {@link SaveUserProfilePromiseCallbackSuccess},
     * fails with a {@link SaveUserProfilePromiseCallbackError}
     *
     * @typedef {Promise} SaveUserProfilePromise
     */

    /**
     * Save (update) user profile information. This one is for user
     * to change their own profiles.
     *
     * @param username {string} Username as login name.
     * @param name {string} User full name in "Lastname, Firstname"
     * @param email {string}
     * @param phone1 {string} Phone number, only + and numbers.
     * @param organisation {string}
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    saveUserProfile(username, name, email, phone1, organisation) {
        var path_ = this.protocols._getPath('domain/user');

        var body_ = {
            'username': username,
            'name': name,
            'phone1': phone1,
            'email': email,
            'organisation': organisation
        };

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * This callback is called when the UserProfileByIdPromise
     * response succeeds
     *
     * @callback UserProfileByIdPromiseCallbackSuccess
     * @param {object} userInfo - A user object with all property
     *     except password
     */

    /**
     * This callback is called when the UserProfileByIdPromise
     * response fails
     *
     * @callback UserProfileByIdPromiseCallbackError
     * @param {string} message - plaintext message
     */

    /**
     * Resolves with a {@link UserProfileByIdPromiseCallbackSuccess},
     * fails with a {@link UserProfileByIdPromiseCallbackError}
     *
     * @typedef {Promise} UserProfileByIdPromise
     */

    /**
     * Read user profile by his id.
     *
     * @param userId {string} Unique user database id.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    getUserProfileByID(userId) {
        var path_ = this.protocols._getPath('framework/user');
        path_ = `${path_}/${userId}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * This callback is called when the NewUserPromise
     * response succeeds
     *
     * @callback NewUserPromiseCallbackSuccess
     * @param {string} message - plaintext message
     */

    /**
     * This callback is called when the NewUserPromise
     * response fails
     *
     * @callback NewUserPromiseCallbackError
     * @param {string} message - plaintext message
     */

    /**
     * Resolves with a {@link NewUserPromiseCallbackSuccess},
     * fails with a {@link NewUserPromiseCallbackError}
     *
     * @typedef {Promise} NewUserPromise
     */

    /**
     * Creates a new user
     * @param username {string} Unique username. Server will double
     *    check if it is unique.
     * @param name {string} Full name of user in format "Last, First".
     * @param password {string} Password in plain text. x_x
     * @param email {string}
     * @param phone1 {string} In format +14081231234.
     * @param organisation {string}
     * @param defaultGroupName {string} Group name instead of id.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    newUser(username, name, password, email, phone1, organisation, defaultGroupName) {
        //debugger;
        var path_ = this.protocols._getPath('framework/user');

        var body_ = {
            'username': username,
            'name': name,
            'password': password,
            'phone1': phone1,
            'email': email,
            'organisation': organisation,
            'defaultGroup': defaultGroupName
        };

        return this.protocols.apiPost(path_, body_);
    }

    /**
     * This callback is called when the ModifyUserPromise
     * response succeeds
     *
     * @callback ModifyUserPromiseCallbackSuccess
     * @param {string} message - plaintext message
     */

    /**
     * This callback is called when the ModifyUserPromise
     * response fails
     *
     * @callback ModifyUserPromiseCallbackError
     * @param {string} message - plaintext message
     */

    /**
     * Resolves with a {@link ModifyUserPromiseCallbackSuccess},
     * fails with a {@link ModifyUserPromiseCallbackError}
     *
     * @typedef {Promise} ModifyUserPromise
     */

    /**
     * Similar to save user profile. This one is for address book use.
     *
     * @param userID {string} Unique user database id.
     * @param username {string} Unique username. Server will double
     *    check if it is unique.
     * @param name {string} Full name of user in format "Last, First".
     * @param email {string}
     * @param phone1 {string} In format +14081231234.
     * @param organisation {string}
     * @param defaultGroupName {string} Group name instead of id.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    modifyExistingUser(userID, username, name, email, phone1, organisation, defaultGroupName) {
        var path_ = this.protocols._getPath('framework/user');

        var body_ = {
            'userID': userID,
            'username': username,
            'name': name,
            'phone1': phone1,
            'email': email,
            'organisation': organisation,
            'defaultGroup': defaultGroupName
        };

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * Update user admin settings, including force user to change
     * password on login and reset user to first login status.
     *
     * @param userId {string} Unique user id (couch db doc id).
     * @param adminSettings {object} A dict contains everything needs
     *     to be set. For now it has passwordChange and
     *     resetToFirstLogin. Anything come in later just add to the
     *     dict object.
     *
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     *     Result status promise.
     *     then: status 0, no data.
     *     catch: status not 0, exception object or message array.
     */
    updateUserAdminSettings(userId, adminSettings) {
        var path_ = this.protocols._getPath('framework/user/settings');
        var body_ = {
            'userID': userId,
            'passwordChange': adminSettings['passwordChange'],
            'resetToFirstLogin': adminSettings['resetToFirstLogin']
        };

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * This callback is called when the ListUserPromise
     * response succeeds
     *
     * @callback ListUserPromiseCallbackSuccess
     * @param {Array<object>} userlist - List of user objects.
     */

    /**
     * This callback is called when the ListUserPromise
     * response fails
     *
     * @callback ListUserPromiseCallbackError
     * @param {string} message - plaintext message
     */

    /**
     * Resolves with a {@link ListUserPromiseCallbackSuccess},
     * fails with a {@link ListUserPromiseCallbackError}
     *
     * @typedef {Promise} ListUserPromise
     */

    /**
     * Get list of all user with all info except password.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    getListOfUsers() {
        var path_ = this.protocols._getPath('framework/user');

        return this.protocols.apiGet(path_);
    }

    /**
     * This callback is called when the DeleteUserPromise
     * response succeeds
     *
     * @callback DeleteUserPromiseCallbackSuccess
     * @param {string} message - plaintext message
     */

    /**
     * This callback is called when the DeleteUserPromise
     * response fails
     *
     * @callback DeleteUserPromiseCallbackError
     * @param {string} message - plaintext message
     */

    /**
     * Resolves with a {@link DeleteUserPromiseCallbackSuccess},
     * fails with a {@link DeleteUserPromiseCallbackError}
     *
     * @typedef {Promise} DeleteUserPromise
     */

    /**
     * Delete a user from database by his id.
     *
     * @param userId {string} User unique id.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    deleteUser(userId) {
        var path_ = this.protocols._getPath('framework/user');
        path_ = `${path_}/${userId}`;

        return this.protocols.apiDelete(path_);
    }

    /**
     * Call this api to change the password for user.
     *
     * @param oldpassword {string} - users: pass in clear text old
     *     password. admin use: pass in clear text string *optional*
     *     for admin reset ( must be called by user in administrators
     *     group )
     * @param newpassword {string} - clear text new user password
     * @returns {string} - status message of modifying password
     *     A promise.
     *     then: dict of result, status 0 as success, message as
     *         array of strings of messages.
     *     catch: error status, text and message.
     */
    resetPassword(oldpassword, newpassword) {
        var path_ = this.protocols._getPath('framework/password');

        var body_ = {
            'oldpassword': oldpassword,
            'newpassword': newpassword
        };

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * Call this api to change password to current logged in user.
     * Special version for first time login and reset password.
     *
     * TODO: Fix this security glitch!
     *
     * @param newpassword {string} Clear text new user password
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     *     A promise.
     *     then: dict of result, status 0 as success, message as
     *         array of strings of messages.
     *     catch: error status, text and message.
     */
    changePasswordWithoutOld(newpassword) {
        var path_ = this.protocols._getPath('framework/password');

        var body_ = {
            'newpassword': newpassword
        };

        return this.protocols.apiPost(path_, body_);
    }

    /**
     * Admin users call this function to set other user's password.
     * Server will check if the user calling this function is admin.
     *
     * @param username {string} This function use username instead of
     *     user id. Username should also be unique.
     * @param password {string} Plain text new user password
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    adminSetPassword(username, password) {
        var path_ = this.protocols._getPath('framework/password/admin');

        var body_ = {
            'username': username,
            'newpassword': password
        };

        return this.protocols.apiPost(path_, body_);
    }

    /**
     * Get a list of all groups with name, id, and list of users of
     * groups.
     *
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    getListOfGroups() {
        var path_ = this.protocols._getPath('framework/group');

        return this.protocols.apiGet(path_);
    }

    /**
     * Create a new group.
     *
     * @param groupName {string} Group name, should be unique.
     * @param description {string} Description of the group.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    createNewGroup(groupName, description) {
        var path_ = this.protocols._getPath('framework/group');

        var body_ = {
            'groupName': groupName,
            'groupDescription': description
        };

        return this.protocols.apiPost(path_, body_);
    }

    /**
     * Edit a group info. Only group description can be change.
     *
     * @param groupId {string} Unique database id for group.
     * @param groupName {string} Unique group name.
     * @param description {string}
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    editGroup(groupId, groupName, description) {
        // Group name should be able to change but now we are
        // using group name as id in some place, so we must keep it
        // unchanged.

        var path_ = this.protocols._getPath('framework/group');
        path_ = `${path_}/${groupId}`;

        var body_ = {
            'groupName': groupName,
            'groupDescription': description
        };

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * Delete a group from database.
     *
     * @param groupId {string} Unique database id for group.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    deleteGroup(groupId) {
        // Chosen group will remain there. Need to fix sometime.
        var path_ = this.protocols._getPath('framework/group');
        path_ = `${path_}/${groupId}`;

        return this.protocols.apiDelete(path_);
    }

    /**
     * Add a user to a group.
     *
     * @param groupId {string} Group to add a user to.
     * @param userId {string} Add this user to group.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    groupAddUser(groupId, userId) {
        var path_ = this.protocols._getPath('framework/usergroup');
        path_ = `${path_}/${groupId}/${userId}`;

        return this.protocols.apiPost(path_, {});
    }

    /**
     * Remove a user from a group.
     *
     * @param groupId {string} Group to remove user.
     * @param userId {string} This user will be removed from given
     *     group.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    groupRemoveUser(groupId, userId) {
        var path_ = this.protocols._getPath('framework/usergroup');
        path_ = `${path_}/${groupId}/${userId}`;

        return this.protocols.apiDelete(path_);
    }

    /**
     * Update user email address. Will be obselete soon.
     *
     * @param email {string} Email address.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    updateUserEmail(email) {
        var path_ = this.protocols._getPath('domain/user/email');
        path_ = `${path_}/${email}`;

        var body_ = {};

        return this.protocols.apiPost(path_, body_);
    }

    /**
     * Confirm email address by username and key. At this point user
     * should not be logged in, so this call will bypass authentication
     * and use username.
     *
     * @param username {string} Username, should be unique.
     * @param key {string} System generated one time key.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    confirmUserEmail(username, key) {
        // Maybe user id is a better idea.
        var path_ = this.protocols._getPath('domain/user/email');
        path_ = `${path_}/${username}/confirm/${key}`;

        return this.protocols.apiPostNoAuth(path_, {});
    }

    /**
     * @param treeID {string} - The treeID for which category
     *     assignment is sought
     * @returns {object} - Category assignment object
     */
    getAssignCategory(treeID) {
        var path_ = this.protocols._getPath('domain/getAssignCategory');
        path_ = `${path_}/${encodeURIComponent(treeID)}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Get template restriction by tree id.
     *
     * @param treeId {string} Root node name as tree id.
     *
     * @returns {*|Promise.<T>|Promise|axios.Promise} Resolved will
     *     be a list of chosen template names and a list of unchosen.
     */
    getTemplateRestrictions(treeId) {
        var path_ = this.protocols._getPath('domain/restrict/template');
        path_ = `${path_}/${encodeURIComponent(treeId)}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Set template restriction.
     *
     * @param treeId {string} Root node name as tree id.
     * @param chosenTemplates {Array<string>} Array of chosen template
     *     names. We do not save unchosen ones.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    setTemplateRestrictions(treeId, chosenTemplates) {
        var path_ = this.protocols._getPath('domain/restrict/template');
        path_ = `${path_}/${encodeURIComponent(treeId)}`;

        var body_ = {'chosen': chosenTemplates};

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * Get group restrictions. Same thing as templates.
     *
     * @param treeId {string} Root node name as tree id.
     * @returns {*|Promise.<T>|Promise|axios.Promise} Two arrays of
     *     chosen and unchosen.
     */
    getGroupRestrictions(treeId) {
        var path_ = this.protocols._getPath('domain/restrict/group');
        path_ = `${path_}/${encodeURIComponent(treeId)}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Set group restrictions.
     *
     * @param treeId {string} Root node name as tree id.
     * @param chosenGroups {Array<string>} Array of chosen group
     *     names. We do not save unchosen ones.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    setGroupRestrictions(treeId, chosenGroups) {
        // Deleted groups will still be in this list. Need to fix.
        var path_ = this.protocols._getPath('domain/restrict/group');
        path_ = `${path_}/${encodeURIComponent(treeId)}`;

        var body_ = {'chosen': chosenGroups};

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * Set group restrictions and template restrictions at the same
     * time.
     *
     * @param treeId {string} Root node name as tree id.
     * @param groups {Array<string>} Array of chosen group names.
     * @param templates {Array<string>} Array of chosen template
     *     names.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    setGroupsAndTemplatesRestrictions(treeId, groups, templates) {
        var path_ = this.protocols._getPath('domain/restrict/both');
        path_ = `${path_}/${encodeURIComponent(treeId)}`;

        var body_ = {
            'groups': groups,
            'templates': templates
        };

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * Get access control list through tree id.
     *
     * @param treeId {string} Root node name as tree id.
     * @returns {*|Promise.<T>|Promise|axios.Promise} Resolved will
     *     get a acl object with owner and acl groups.
     */
    getAcl(treeId) {
        var path_ = this.protocols._getPath('domain/acl');
        path_ = `${path_}/${encodeURIComponent(treeId)}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Set acl info.
     *
     * @param treeId {string} Root node name as tree id.
     * @param acl {object} Though we request acl object here, we only
     *     save group info into database.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    setAcl(treeId, acl) {
        var path_ = this.protocols._getPath('domain/acl');
        path_ = `${path_}/${encodeURIComponent(treeId)}`;

        var body_ = {
            'acl': acl
        };

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * Get welcome message, including license and security warning.
     *
     * @param messageType {string} Can be LICENSE and SECURITY_WARNING.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    getWelcomeMessage(messageType) {
        var path_ = this.protocols._getPath('domain/welcome/message');
        path_ = `${path_}/${messageType}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Set welcome message. This function will push whatever string
     * passed in without doing any encoding. So encode the message
     * before calling this.
     *
     * @param messageType {string} Can be LICENSE and SECURITY_WARNING.
     * @param message {string} Message to be save into database.
     * @param state {boolean} True for always display.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    setWelcomeMessage(messageType, message, state) {
        var path_ = this.protocols._getPath('domain/welcome/message');
        path_ = `${path_}/${messageType}`;

        var body_ = {
            'message': message,
            'state': state
        };

        return this.protocols.apiPut(path_, body_);
    }

    /**
     * Accept license or security warnings.
     *
     * @param messageType {string} Can be LICENSE and SECURITY_WARNING.
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    acceptWelcomeMessage(messageType) {
        var path_ = this.protocols._getPath('domain/welcome/accept');
        path_ = `${path_}/${messageType}`;

        return this.protocols.apiPut(path_, {});
    }

    /**
     * Fetch input data in a excel format string.
     *
     * @param treeID {string} Tree id, ie. root node name.
     * @param nodeID {string} Node id, can be any node. Will download
     *     input of a subtree from this node.
     *
     * @return {*|Promise.<T>|Promise|axios.Promise}
     *     Download input promise.
     *     then:
     *     catch:
     */
    fetchInputData(treeID, nodeID) {
        var path_ = this.protocols._getPath('domain/download/input');
        path_ = `${path_}/${encodeURIComponent(treeID)}/${nodeID}`;

        return this.protocols.apiGet(path_);
    }

    /**
     * Request reset password.
     * @param username {string}
     * @param email {string}
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    requestResetPassword(username, email) {
        var path_ = this.protocols._getPath('framework/password/request');
        path_ = `${path_}/${username}/${email}`;

        return this.protocols.apiGetNoAuth(path_);
    }

    /**
     * Check if the username and key matches and not expired.
     *
     * @param username {string}
     * @param key {string}
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    checkResetPasswordKey(username, key) {
        var path_ = this.protocols._getPath('framework/password/reset');
        path_ = `${path_}/${username}/${key}`;

        return this.protocols.apiGetNoAuth(path_);
    }

    /**
     * Reset password to new password. Will check the username and
     * key again to make sure everything is correct.
     *
     * @param username {string}
     * @param key {string}
     * @param newPassword {string}
     * @returns {*|Promise.<T>|Promise|axios.Promise}
     */
    resetPasswordThroughEmail(username, key, newPassword) {
        var path_ = this.protocols._getPath('framework/password/reset');
        path_ = `${path_}/${username}/${key}`;
        var body_ = {
            newpass: newPassword
        };

        return this.protocols.apiPostNoAuth(path_, body_);

    }

    /**
     * Downloads spreadsheet model applicable at node with given nodeID.
     * Data is a json object with the following structure:
     * @example
     * smartorg.downloadSpreadsheet(myNodeID).then(function(answer) {
     *    console.log(answer.templateName); // e.g. simpleLaunch
     *    console.log(answer.extension); // e.g. xls
     *    console.log(answer.modelData); // Base-64 encoded binary data
     * }).catch(function(err) {
     *    console.log(err);
     * });
     * @param nodeID
     * @return {*|Promise|axios.Promise}
     */
    downloadSpreadsheet(nodeID) {
        var path_ = this.protocols._getPath('domain/downloadTemplate');
        path_ = `${path_}/${nodeID}`;
        var body_ = {};

        return this.protocols.apiPostNoAuth(path_, body_);
    }
}

export default SmartOrg;
// module.exports = SmartOrg;

//For users not using a module loading eg(browersify), need a way
// to expose it to them via the window object
if (typeof window != 'undefined') {
    window.SmartOrg = SmartOrg;
}

//[NOTES]
//
// [1] - Promises - alt way to bubble promise up to next catch statement ...
// throw new APIErr({'status':error.status,'statusText':error.statusText,'message':error.data.message})
//
// [2] - Symbols - a neat way to set ACL/scope on methods etc ...
// export const createHMACget = Symbol('createHMACget');
// is possible to use reflection but hides this sufficiently for now ...
// also if you wanted to extend SmartOrg ... avoids name collisions
//
// [3] - Symbols - Private Fxn in action
// symbols way to define unique primatives  and give controll over access to properties and avoid collisions
// prototype instances named props on fxns
// this is a way to hide -

/*
 var rangal = new Rangal("server.smartorg.com");
 rangal.login(username,password, function(loginStatus) {});
 rangal.treeFor("Product Launch", function(treeJSON) {});
 rangal.actionByDisplayName("Product Launch", "Breath Strips Node ID", "Tornado Action ID", function(tornadoJSON) {});
 rangal.portfolios(function(portfolioJSON) {});
 rangal.actionMenuFor("Product Launch", "Breath Strips", function(actionMenuJSON){});
 */