Source: gctypes/gcntree/gcntree.js

"use strict";

const Gcntree_node = require("./gcntree_node.js");

/**
* N-ary (k-way) tree
* @constructor
* @param {Object} config - configuration
* @param {Gcntree_node} config.root - node to set as root
*/
function Gcntree({root = null} = {}) {
    this.root = root;
}

/**
* Factory function to create a Gcntree from the JSON representation of a YAML document.
* Not to be confused with {@link Gcntree.from_json}
* @static
* @param {Object} obj - a JSON object representing a YAML document
* @param {function} trans - transformer function - one of {@link Gcntree.trans}
* @returns {Gcntree}
*/
Gcntree.from_json_doc = function(obj, trans) {
    function _process_obj(obj, ptr) {
        const keyvals = Object.entries(obj);

        ptr = ptr.add_child(new Gcntree_node({data: trans(keyvals[0][0], keyvals[0][1]), parent: ptr}));
        
        keyvals.slice(1).forEach((keyval) => {
            if (!Array.isArray(keyval[1])) {
                ptr = ptr.add_child(new Gcntree_node({data: trans(keyval[0], keyval[1]), parent: ptr}));
            } else {
                const new_ptr = ptr.add_child(new Gcntree_node({data: keyval[0], parent: ptr}));
                
                keyval[1].forEach((elem) => {
                    _process_obj(elem, new_ptr);
                });
            }
        });
    }

    const tree = new Gcntree({root: new Gcntree_node()});
    _process_obj(obj, tree.root);
    return tree;
}

/**
* Transformer functions for {@link Gcntree.from_json_doc}. 
* A transformer function describes how to transform a (key, value) pair 
* in a JSON object to data in a {@link Gcntree_node}
* @namespace Gcntree.trans
*/
Gcntree.trans = {
    /**
    * Transform to simple object 
    * @memberof Gcntree.trans
    * @param {string} key - key
    * @param {any} val - value
    * @returns {Object} as {key: val}
    */
    to_obj: (key, val) => {
        const obj = {};
        obj[key] = val;
        return obj;
    }
};

/**
* Deserialize a {@link Gcntree} that was previously serialized using {@link Gcntree.toJSON}
* @static
* @param {string} json - a serialized {@link Gcntree}
* @returns {Gcntree}
*/
Gcntree.from_json = function(json) {
    const arr = JSON.parse(json);
    const tree = new Gcntree({root: new Gcntree_node({data: arr[0]})});
    
    let node = tree.root;

    arr.slice(1).forEach((elem) => {
        if (elem === null) {
            node = node.parent;        
            return; 
        }

        node = node.add_child(new Gcntree_node({data: elem, parent: node}));
    });

    return tree;
}

/**
* Depth first search
* @param {Gcntree~dfs_cb} pre - preorder visitation callback
* @param {Gcntree~dfs_cb} post - postorder visitation callback
* @param {Array} data - this array will be returned upon termination, useful for accumulation, etc.
* @returns {Array} the data array
*/
Gcntree.prototype.dfs = function(pre, post, node = this.root, data = []) {
    if (typeof pre === "function") {
        pre(node, data);
    }
    
    const children = node.get_all_children();

    node.get_all_children().forEach((child) => {
        data = this.dfs(pre, post, child, data); 
    });
    
    if (typeof post === "function") {
        post(node, data);
    }

    return data;
}

/**
* Depth first search visitation callback
* @callback Gcntree~dfs_cb
* @param {Gcntree_node} node - reference to the currently visited node
* @param {Array} data - reference to the data array
*/

/**
* Fetch a node from this tree by node number as enumerated by {@link Gcntree#dfs} preorder traversal
* @param {number} node_num - the node number
* @returns {Gcntree_node}
*/
Gcntree.prototype.get_node = function(node_num) {
    let n = 0;

    return this.dfs((node, data) => {
        if (n === node_num) {
            data.push(node)
        }
        
        n += 1;
    })[0];
}

/**
* Serialize this {@link Gcntree}. This method serializes trees as linear arrays of {@link Gcntree_node} objects
* with null sentinels indicating "end of children"
* @see https://www.geeksforgeeks.org/serialize-deserialize-n-ary-tree/
* @returns {string} the serialized tree
*/
Gcntree.prototype.toJSON = function() {
    const arr = [];

    this.dfs((node, data) => {
        arr.push(node.data);
    }, (node, data) => {
        arr.push(null);
    });
    
    return JSON.stringify(arr);
}

module.exports = Gcntree;