'use strict';

/**
 * 指标模板控制器
 *
 * @author Mai
 * @data 2018/4/19
 * @version
 */

const fs = require('fs');
const streamToArray = require('stream-to-array');
const mathjs = require('mathjs');

module.exports = {
    /**
     * 转换数据
     *
     * @param {Array} list - 数据库查找的数据
     * @param {Boolean} tree - 是否展示为树形结构
     * @param {String} id - id属性名
     * @param {String} pid - pid属性名
     * @return {Object} - 返回转换后的数据
     */
    convertData(list, tree = true, id, pid) {
        const rootData = [];
        const childData = {};
        let listData = [];
        for (const tmp of list) {
            if (tmp[pid] === 0 || tmp[pid] === -1) {
                rootData.push(tmp);
                continue;
            }
            if (childData[tmp[pid]] === undefined) {
                childData[tmp[pid]] = [];
            }
            childData[tmp[pid]].push(tmp);
        }
        // 递归组织数据
        if (tree) {
            this._recursionTreeData(rootData, childData, id);
        } else {
            this._recursionData(rootData, childData, listData, id);
        }

        return tree ? rootData : listData;
    },
    /**
     * 递归组织数组结构数据
     *
     * @param {Array} parent - 父节点
     * @param {Object} childData - 子元素(以pid为key的对象)
     * @param {Array} outputData - 处理后的数据
     * @param {String} id - id属性名
     * @return {void}
     */
    _recursionData(parent, childData, outputData = [], id) {
        for (const index in parent) {
            // 父节点先进数组
            outputData.push(parent[index]);
            // 判断是否存在子项
            if (childData[parent[index][id]] !== undefined) {
                // 子项作为父节点递归查找子项中是否还有子项
                const tmpParent = childData[parent[index][id]];
                // 已用的子项删除
                delete childData[parent[index][id]];
                // 递归元素
                this._recursionData(tmpParent, childData, outputData);
            }
        }
    },
    /**
     * 递归组织树结构数据
     *
     * @param {Array} parent - 父节点
     * @param {Object} childData - 子元素(以pid为key的对象)
     * @param {String} id - id属性名
     * @return {void}
     */
    _recursionTreeData(parent, childData = {}, id) {
        if (Object.keys(childData).length <= 0) {
            return;
        }
        for (const index in parent) {
            // 判断是否存在子项
            if (childData[parent[index][id]] !== undefined) {
                // 子项作为父节点递归查找子项中是否还有子项
                const tmpParent = childData[parent[index][id]];
                // 已用的子项删除
                delete childData[parent[index][id]];

                this._recursionTreeData(tmpParent, childData, id);
                // 递归完赋值回原数据
                parent[index].children = tmpParent;
            }
        }
    },

    /**
     * 将文件流的数据保存至本地文件
     * @param stream
     * @param fileName
     * @returns {Promise<void>}
     */
    async saveStreamFile(stream, fileName) {
        // 读取字节流
        const parts = await streamToArray(stream);
        // 转化为buffer
        const buffer = Buffer.concat(parts);
        // 写入文件
        await fs.writeFileSync(fileName, buffer);
    },

    /**
     * 检查code是否是指标模板数据
     * @param {String} code
     * @returns {boolean}
     */
    ValidTemplateCode(code) {
        const reg1 = /(^[a-z]+)([a-z0-9\-]*)/i;
        const reg2 = /([a-z0-9]+$)/i;
        return reg1.test(code) && reg2.test(code);
    },
    /**
     * 检查code 是否是 指标节点编号
     * @param {String} code
     * @returns {boolean}
     */
    ValidTemplateNodeCode(code) {
        const reg1 = /(^[a-z]+)([a-z0-9\-]*$)/i;
        const reg2 = /([a-z0-9]+$)/i;
        const reg3 = /([\-][0-9]+$)/i;
        return reg1.test(code) && reg2.test(code) && (!reg3.test(code));
    },
    /**
     * 检查code 是否是 指标编号
     * @param {String} code
     * @returns {boolean}
     */
    ValidTemplateIndexCode(code) {
        const reg1 = /(^[a-z]+)([a-z0-9\-]*$)/i;
        const reg2 = /([0-9]+$)/i;
        return reg1.test(code) && reg2.test(code);
    },
    /**
     * 检查code 是否 是有效的绑定分项编号
     * @param {String} code - 编号
     * @return {Boolean}
     */
    validMatchCode(code) {
        const parts = code.split('-');
        const reg1 = /(^[a-z]+$)/i;
        const reg2 = /(^[0-9]+$)/;
        for (const i in parts) {
            if (i == 0) {
                if (!reg2.test(parts[i])) {
                    return false;
                }
            } else {
                if (!(reg1.test(parts[i]) || reg2.test(parts[i]))) {
                    return false;
                }
            }
        }
        return true;
    },

    /**
     * 在数组arr中查找a.field=value的成员a
     *
     * @param {Array} arr - 数组
     * @param {String} field - 匹配属性名称
     * @param {any} value - 匹配属性值
     * @returns {*}
     */
    findObj(arr, field, value) {
        if (arr.length === 0) { return undefined; }
        for (const a of arr) {
            if (a[field] && a[field] === value) {
                return a;
            }
        }
        return undefined;
    },

    /**
     * 读取json文件并解析
     *
     * @param {String} fileName - json文件路径
     * @returns {any}
     */
    loadJsonFile(fileName) {
        const sJson = fs.readFileSync(fileName);
        return JSON.parse(sJson);
    },

    /**
     * 增删改单条json数据
     * @param {string} json 单条json值
     * @param {String} key 属性
     * @param {String} value 值
     * @returns {string}
     */
    operationJson(json,key, value) {
        if(typeof value === "undefined") {
            delete json[key];
        } else {
            json[key] = value;
        }
        return json;
    },

    /**
     * 判断value是否是四则运算符
     * @param {String} value - 判断字符串
     * @return {Boolean}
     */
    isOperator(value) {
        const operatorString = "+-*/()";
        return operatorString.indexOf(value) > -1;
    },
    /**
     * Rpn解析栈
     * @param exp
     * @returns {Array}
     */
    parse2Rpn(exp) {
        const getPriority = function (value){
            switch(value){
                case '+':
                case '-':
                    return 1;
                case '*':
                case '/':
                    return 2;
                default:
                    return 0;
            }
        };
        const priority = function (o1, o2){
            return getPriority(o1) <= getPriority(o2);
        };

        const inputStack = [];
        const outputStack = [];
        const outputQueue = [];

        for (let i = 0, len = exp.length; i < len; i++) {
            const cur = exp[i];
            if (cur !== ' ' ) {
                inputStack.push(cur);
            }
        }
        let num = '', isNumPre = false, isOperatorPre = false;
        while(inputStack.length > 0){
            const cur = inputStack.shift();
            if (this.isOperator(cur) && !(cur === '-' && !isNumPre)) {
                if (isNumPre) {
                    outputQueue.push(parseFloat(num));
                    num = '';
                }
                if (cur === '(') {
                    outputStack.push(cur);
                } else if (cur === ')') {
                    let po = outputStack.pop();
                    while (po !== '(' && outputStack.length > 0) {
                        outputQueue.push(po);
                        po = outputStack.pop();
                    }
                    if (po !== '(') {
                        throw "error: unmatched ()";
                    }
                } else {
                    while(priority(cur, outputStack[outputStack.length - 1]) && outputStack.length > 0){
                        outputQueue.push(outputStack.pop());
                    }
                    outputStack.push(cur);
                }
                isNumPre = false;
                isOperatorPre = true;
            } else {
                num = num + cur;
                isNumPre = true;
                isOperatorPre = false;
                if (inputStack.length === 0) {
                    outputQueue.push(parseFloat(num));
                }
            }
        }
        if (outputStack.length > 0) {
            if (outputStack[outputStack.length - 1] === ')' || outputStack[outputStack.length - 1] === '(') {
                throw "error: unmatched ()";
            }
            while (outputStack.length > 0) {
                outputQueue.push(outputStack.pop());
            }
        }
        return outputQueue;
    },
    /**
     * 计算Rpn解析栈
     * @param rpnQueue
     * @returns {*}
     */
    evalRpn(rpnQueue) {
        const getResult = function (num1, num2, opera) {
            switch (opera) {
                case '+':
                    return num1 + num2;
                case '-':
                    return num1 - num2;
                case '*':
                    return num1 * num2;
                case '/':
                    return num1 / num2;
                default:
                    throw '参数错误';
            }
        };
        const outputStack = [];
        while(rpnQueue.length > 0){
            const cur = rpnQueue.shift();

            if (!this.isOperator(cur)) {
                outputStack.push(cur);
            } else {
                if (outputStack.length < 2) {
                    throw "unvalid stack length";
                }
                const sec = outputStack.pop();
                const fir = outputStack.pop();

                outputStack.push(getResult(fir, sec, cur));
            }
        }

        if (outputStack.length !== 1) {
            throw "unvalid expression";
        } else {
            return outputStack[0];
        }
    },
    /**
     * 解析四则运算字符串并计算
     * @param {String} expr
     * @returns
     */
    calcExprStrRpn(expr) {
        try {
            if (!isNaN(Number(expr))) {
                return Number(expr);
            } else {
                const rpnArr = this.parse2Rpn(expr);
                const result = this.evalRpn(rpnArr);
                return result === Infinity ? null : result;
            }
        } catch (err) {
            return null;
        }
    },

    /**
     * 使用四则运算符分隔
     * @param expr
     * @returns {Array}
     */
    splitByOperator (expr) {
        const exprStack = [];
        const result = [];
        for(let i = 0, len = expr.length; i < len; i++){
            const cur = expr[i];
            if(cur !== ' ' ){
                exprStack.push(cur);
            }
        }
        let param = '', isParamBefore = false;
        while(exprStack.length > 0){
            const cur = exprStack.shift();
            if (this.isOperator(cur)) {
                if (isParamBefore) {
                    result.push(param);
                    param = '';
                }
                result.push(cur);
                isParamBefore = false;
            } else {
                param = param + cur;
                isParamBefore = true;
                if (exprStack.length === 0) {
                    result.push(param);
                    param = '';
                }
            }
        }
        return result;
    },

    /**
     * 解析 数学计算式(字符串) 并计算
     * @param expr
     * @returns {null}
     */
    calcExprStr(expr) {
        try {
            const result = mathjs.eval(expr);
            return result === Infinity ? null : result;
        } catch (err) {
            return null;
        }
    }
};