const sax = require('./sax');

function formatXml(xmlParsed, encoding = 'utf-8', textFilterFunc) {
    let out = `<?xml version="1.0" encoding="${encoding}"?>`;
    out += formatXmlNode(xmlParsed, textFilterFunc);
    return out;
}

function formatXmlNode(node, textFilterFunc) {
    textFilterFunc = (textFilterFunc ? textFilterFunc : text => text);

    const formatNode = (node, name) => {
        let out = '';

        if (Array.isArray(node)) {
            for (const n of node) {
                out += formatNode(n);
            }
        } else if (typeof node == 'string') {
            if (name)
                out += `<${name}>${textFilterFunc(node)}</${name}>`;
            else
                out += textFilterFunc(node);
        } else {
            if (node._n)
                name = node._n;

            let attrs = '';
            if (node._attrs) {
                for (let attrName in node._attrs) {
                    attrs += ` ${attrName}="${node._attrs[attrName]}"`;
                }
            }

            let tOpen = '';
            let tBody = '';
            let tClose = '';
            if (name)
                tOpen += `<${name}${attrs}>`;
            if (node.hasOwnProperty('_t'))
                tBody += textFilterFunc(node._t);

            for (let nodeName in node) {
                if (nodeName && nodeName[0] == '_' && nodeName != '_a')
                    continue;

                const n = node[nodeName];
                tBody += formatNode(n, nodeName);
            }
            
            if (name)
                tClose += `</${name}>`;

            out += `${tOpen}${tBody}${tClose}`;
        }
        return out;
    }

    return formatNode(node);
}

function parseXml(xmlString, lowerCase = true) {
    let result = {};
    let node = result;

    const onTextNode = (text, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
        node._t = text;
    };

    const onStartNode = (tag, tail, singleTag, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
        if (tag == '?xml')
            return;
        
        const newNode = {_n: tag, _p: node};

        if (tail) {
            const parsedAttrs = sax.getAttrsSync(tail, lowerCase);
            const atKeys = Object.keys(parsedAttrs);
            if (atKeys.length) {
                const attrs = {};
                for (let i = 0; i < atKeys.length; i++) {
                    const attrName = atKeys[i];
                    attrs[parsedAttrs[attrName].fn] = parsedAttrs[attrName].value;
                }

                newNode._attrs = attrs;
            }
        }

        if (!node._a)
            node._a = [];
        node._a.push(newNode);
        node = newNode;
    };

    const onEndNode = (tag, tail, singleTag, cutCounter, cutTag) => {// eslint-disable-line no-unused-vars
        if (node._p && node._n == tag)
            node = node._p;
    };

    sax.parseSync(xmlString, {
        onStartNode, onEndNode, onTextNode, lowerCase
    });

    if (result._a)
        result = result._a[0];
    
    return result;
}

function simplifyXmlParsed(node) {
    
    const simplifyNodeArray = (a) => {
        const result = {};

        for (let i = 0; i < a.length; i++) {
            const child = a[i];
            if (child._n && !result[child._n]) {
                result[child._n] = {};
                if (child._a) {
                    result[child._n] = simplifyNodeArray(child._a);
                }
                if (child._t) {
                    result[child._n]._t = child._t;
                }
                if (child._attrs) {
                    result[child._n]._attrs = child._attrs;
                }
            }
        }

        return result;
    };

    return simplifyNodeArray([node]);
}

module.exports = {
    formatXml,
    formatXmlNode,
    parseXml,
    simplifyXmlParsed
}