/**
 * 【老版本兼容，新版本弃用】
 * OFramework 统一底层架构
 * author:zack zou
 * create time:2017-03-23
 */

const ofInstance = require("./OFInstance.js");

/**
 * SQL模型ORM映射对象
 */
module.exports = service;
/**
 * 存储过程
 */
module.exports.proc = proc;

/**
 * 内部方法，必须是通用的方法
 */
var m = {};

/**
 * ！！！非常关键的配置信息
 * 当前模块配置
 * 表配置：变量放到顶部，代码重用只需要更改头部的配置代码
 */

function Service(oodbc, _config) {
    this.instanceConfig = _config;
    this.databaseConfig = _config.database || "MySQL";
    //数据源
    this.oodbc = oodbc;
}
/**
 * 常规SQL的方式
 * @param _config SQL的配置结构
 * @param oodbc 数据源
 */
function service(oodbc, _config) {

    if (_config === undefined) {
        throw new TypeError('Expected object for argument _config')
    }

    if (!(this instanceof Service)) {
        return new Service(oodbc, _config)
    }

    throw new TypeError('Expected object for argument _config');
}

function Proc(oodbc, _config) {
    this.instanceConfig = _config;
    this.databaseConfig = _config.database || "MySQL";
    //数据源
    this.oodbc = oodbc;
}
/**
 * 存储过程的结构
 * @param _config 存储过程的配置结构
 */
function proc(_config, oodbc) {
    //初始化配置
    if (!(this instanceof Proc)) {
        return new Proc(_config, oodbc);
    }
}

/**
 * s.1 分页查询用户账户信息
 * @param paras 参数集合，任意参数的组合
 * 参数 结构
 * {
 *   start: 开始数据索引
 *   length: 获取数据行数
 *   orderBy:{"字段名":"规则（ASC|DESC）"  字段名:默认为 createtime  规则:默认为 DESC }
 *   keyword:
 *     [
         {"key": "字段名", "value":"值", "logic": "连接联符 （默认为and 非必须 ）", operator: "关联符号 (默认为: = 可以为空)"},
         {"key": "valid", "value": "1"}
      ]
      limit:[0,10]
 * }
 * 返回:{
     data: [],                  //数据列表
     recordsTotal: 0,           //查询记录总数
     start: 0,                  //当前页索引
     pageSize: 10                  //页大小
  }
 * author:zack zou
 * create time:2016-09-01
 */
Service.prototype.getEntityList = function (paras) {
    //参数
    var databaseConfig = this.databaseConfig;
    var instanceConfig = this.instanceConfig;
    var oodbc = this.oodbc;
    //定义结构
    return new Promise(function (resolve, reject) {
        m.getEntityList(oodbc, databaseConfig, instanceConfig, paras)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/**
 * s.2 查询瀑布数据列表，不返回记录总数，当返回的isLastPage=true，查询到了末尾
 * 查询记录length，查询可以length+1，前台数据只显示length的记录，判断尾页，利用length+1判断，小于等于length的记录到了末尾页面
 * @param paras 关键参数：paras.command、paras.keyword、paras.orderBy
 * 参数 结构
 * {
 *   orderBy:{"字段名":"规则（ASC|DESC）"  字段名:默认为 createtime  规则:默认为 DESC }
 *   keyword:
 *     [
         {"key": "字段名", "value":"值", "logic": "连接联符 （默认为and 非必须 ）", operator: "关联符号 (默认为: = 可以为空)"},
         {"key": "valid", "value": "1"}
      ],
      limit:[0,10]          //[开始位置,查询数据个数]
 * }
 * 返回:{
     data: [],                  //数据列表
     isLastPage:false,          //当前页是否为最后页
     start: 0,                  //当前页索引
     pageSize: 10                 //页大小
  }
 * author:zack zou
 * create time:2016-06-27
 */
Service.prototype.getEntityListByWaterfall = function (paras) {
    //参数
    var databaseConfig = this.databaseConfig;
    var instanceConfig = this.instanceConfig;
    var oodbc = this.oodbc;
    //定义结构
    return new Promise(function (resolve, reject) {
        m.getEntityListByWaterfall(oodbc, databaseConfig, instanceConfig, paras)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/**
 * s.3 根据动态参数条件查询数据实体对象
 * @param paras.id 主键id
 * 参数 结构
 * {
 *   orderBy:{"字段名":"规则（ASC|DESC）"  字段名:默认为 createtime  规则:默认为 DESC }
 *   keyword:
 *     [
 *        {"key": "字段名", "value":"值", "logic": "连接联符 （默认为and 非必须 ）", operator: "关联符号 (默认为: = 可以为空)"},
 *        {"key": "valid", "value": "1"}
 *     ],
 *     limit:[0,1]          //[开始位置,查询数据个数]
 * }
 * 返回:[]
 * author:zack zou
 * create time:2016-09-01
 */
Service.prototype.getEntity = function (paras) {
    //参数
    var databaseConfig = this.databaseConfig;
    var instanceConfig = this.instanceConfig;
    var oodbc = this.oodbc;
    //定义结构
    return new Promise(function (resolve, reject) {
        m.getEntity(oodbc, databaseConfig, instanceConfig, paras)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });

    });
}

/**
 * s.4 新增用户信息
 * @param paras 参数
 * 参数 结构
 * {
 *      //新增主键id，可缺省，自动赋值，也可以补全id值
 *      “字段1”:"值"
 *      “字段1”:"值"
 *      ...
 * }
 * author:zack zou
 * create time:2016-09-01
 */
Service.prototype.insertEntity = function (paras) {
    //参数
    var databaseConfig = this.databaseConfig;
    var instanceConfig = this.instanceConfig;
    //数据源
    var oodbc = this.oodbc;
    //定义结构
    return new Promise(function (resolve, reject) {
        m.insertEntity(oodbc, databaseConfig, instanceConfig, paras)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });

    });
}

/**
 * s.5 批量新增数据
 * @param paras 实体列表数组
 * 参数结构
 * [{
 *      //新增主键id，可缺省，自动赋值，也可以补全id值
 *      “字段1”:"值"
 *      “字段1”:"值"
 *      ...
 * }]
 * author:zack zou
 * create time:2017-01-10
 */
Service.prototype.insertBatch = function (paras) {
    //参数
    var databaseConfig = this.databaseConfig;
    var instanceConfig = this.instanceConfig;
    //数据源
    var oodbc = this.oodbc;
    //定义结构
    return new Promise(function (resolve, reject) {
        m.insertBatch(oodbc, databaseConfig, instanceConfig, paras)
            .then(function (result) {
                resolve(result);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/**
 * s.6 修改实体信息，可以进行运算的字段的更新（加减）
 * @param paras 参数（限制字段类型为数字类型）
 * 参数结构
 {
     "update": [
         //字段：替换更新
         {"key": "字段1", "value": '值', "operator": "replace"},
         //字段：累加（数值类型）
         {"key": "字段2", "value": 1, "operator": "plus"},
         //字段：累减（数值类型）
         {"key": "字段3", "value": 1, "operator": "reduce"}
     ],
     "keyword": [
        //where条件：一般以主键id作为更新条件，支持多条件组合语句
        {"key": "条件字段1", "value": '值', "logic": "and", "operator": "="}
     ]
 }
 * author:zack zou
 * create time:2016-09-01
 */
Service.prototype.updateBySenior = function (paras) {
    //参数
    var databaseConfig = this.databaseConfig;
    var instanceConfig = this.instanceConfig;
    //数据源
    var oodbc = this.oodbc;
    //定义结构
    return new Promise(function (resolve, reject) {
        m.updateBySenior(oodbc, databaseConfig, instanceConfig, paras)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/**
 * s.7 批量更新数据
 * @param data 实体列表数组
 * 参数结构
 {
    "keyword": [
        {"key": "查询条件1", "value": "值", "logic": "and", "operator": "="},
        {"key": "查询条件2", "value": "值", "logic": "and", "operator": "="},
        {"key": "查询条件3", "value": "值", "logic": "and", "operator": "="}
    ],
    "orderBy": {"排序字段（created_time）": "DESC"},
    "sumField": "sum求和字段（数值类型）";
 }
 *
 * author:zack zou
 * create time:2016-09-01
 */
Service.prototype.updateBatchBySenior = function (data) {
    //参数
    var databaseConfig = this.databaseConfig;
    var instanceConfig = this.instanceConfig;
    //数据源
    var oodbc = this.oodbc;
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 批量更新，这个方式是采用遍历数据单条记录更新的方式（不合适大批量的数据更新）
         */
        var index = 0;

        async.whilst(
            function () {
                return index < data.length;
            },
            function (callback) {
                /**
                 * 更新
                 */
                m.updateBySenior(oodbc, databaseConfig, instanceConfig, data[index])
                    .then(function (result) {
                        //更新索引
                        index++;
                        return callback(null, null);
                    })
                    .catch(function (err) {
                        //更新索引
                        console.log("更新" + index + "条信息出错");
                        index++;
                        return callback(null, null);
                    });
            },
            function (err) {
                /**
                 * 返回结果
                 */
                if (err) {
                    reject(err);
                } else {
                    /**
                     * 返回结果数据
                     */
                    resolve(data);
                }
            });
    });
}

/**
 * s.8 获取统计信息，count统计
 * @param paras 参数集合，任意参数的组合
 * 参数结构
 {
    "keyword": [
        {"key": "查询条件1", "value": "值", "logic": "and", "operator": "="},
        {"key": "查询条件2", "value": "值", "logic": "and", "operator": "="},
        {"key": "查询条件3", "value": "值", "logic": "and", "operator": "="}
    ],
    "orderBy": {"排序字段（created_time）": "DESC"},
    "aggregate":[{"count": "money"}]
 }
 * author:zack zou
 * create time:2016-09-01
 */
Service.prototype.getEntityByAggregate = function (paras) {
    //参数
    var databaseConfig = this.databaseConfig;
    var instanceConfig = this.instanceConfig;
    //数据源
    var oodbc = this.oodbc;
    //定义结构
    return new Promise(function (resolve, reject) {
        m.getEntityByAggregate(oodbc, databaseConfig, instanceConfig, paras)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/**
 * s.9 物理删除数据实体对象
 * @param paras 删除条件模型
 * 参数 结构
 * {
 *   keyword:
 *     [
 *        {"key": "字段名", "value":"值", "logic": "连接联符 （默认为and 非必须 ）", operator: "关联符号 (默认为: = 可以为空)"}
 *     ]
 * }
 * author:zack zou
 * create time:2017-04-06
 */
Service.prototype.deleteEntity = function (paras) {
    //参数
    var databaseConfig = this.databaseConfig;
    var instanceConfig = this.instanceConfig;
    //数据源
    var oodbc = this.oodbc;
    //定义结构
    return new Promise(function (resolve, reject) {
        m.deleteEntity(oodbc, databaseConfig, instanceConfig, paras)
            .then(function (result) {
                resolve(result);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/************************************************************************************************************
 * 储存过程
 * **********************************************************************************************************
 */

/**
 * p.1 存储过程执行方式
 * @param paras 执行存储过程需要的参数
 */
Proc.prototype.call = function (paras) {
    //参数
    var databaseConfig = this.databaseConfig;
    var instanceConfig = this.instanceConfig;
    //数据源
    var oodbc = this.oodbc;
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 定义存储过程名称
         * 请注意：存储过程名称配置在此数据层的方法体内部，一个方法对照一个存储过程的名称，标准化的存储过程名称可批量定制
         * paras 直接转换成为参数集合的keyword值
         */
        var _instance = instanceConfig.instance || "DEFAULT";      //数据库配置实例节点名称
        paras = {
            "proc_name": instanceConfig.proc_name,
            "keyword": paras
        }
        //取配置信息，每个存储过程由BDA在配置节点定义（相当于开发文档，又是程序中间件）
        //直接使用初始化的这个配置
        paras.config = instanceConfig;
        /**
         * 执行存储过程
         */
        ofInstance(oodbc, databaseConfig, _instance).call(paras)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/************************************************************************************************************
 * 内部方法
 * update contnet：1.3.0版本删除工具类的相关方法，onela-tools项目独立存在（源码地址：https://github.com/zouwei/onela-tools）
 * update time：2017-10-25
 * **********************************************************************************************************
 */

/**
 *
 * @param name
 * @param tp
 * @param cb
 * @returns {string}
 */
var record = {};

/**
 * 生成主键
 * @param name
 * @param tp
 * @param cb
 * @returns {string}
 */
m.getID = function (name, tp, cb) {
    //console.log(name);
    var buf = new Buffer(12);
    var host = 1;//process.env.RISKEYS_HOST;
    var pid = process.pid;
    var time = Math.round((new Date).getTime() / 1000);

    if (record[name] == null) record[name] = {time: 0, seq: 0};
    //console.log(record[name].time,time);
    if (record[name].time != time) {
        record[name].time = time;
        record[name].seq = 0;
    } else {
        record[name].seq++;
    }
    var seq = record[name].seq;
    var pname = name + "\0";
    buf.write(pname, 0, 2);
    // buf.writeUInt8(name.charCodeAt(0));
    //buf.writeUInt8(name.charCodeAt(1),1);
    buf.writeUInt16LE(host, 2);
    buf.writeUInt16LE(pid & 0xffff, 4);
    buf.writeUInt32LE(time, 6);
    buf.writeUInt16LE(seq, 10);
    var id = buf.toString("hex").toUpperCase();

    if (cb) cb(id);
    else return id;

};

/************************************************************************************************************
 * 通用方法
 * **********************************************************************************************************
 */
m.getEntityList = function (oodbc, databaseConfig, instanceConfig, paras) {
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 定义SQL命令参数
         * 不同的业务表，只需要配置参数实现对应的数据查询，底层代码通用
         * 参数：paras.command
         */
        var _instance = instanceConfig.instance || "DEFAULT";      //数据库配置实例节点名称
        paras.command = {
            //查询的表名
            "tableName": instanceConfig.tableName,
        };
        /**
         * 兼容性修复，用limit替换之前的start、length参数
         */
        if (!paras.hasOwnProperty("limit") && paras.hasOwnProperty("start") && paras.hasOwnProperty("length")) {
            paras.limit = [paras["start"], paras["length"]];
        }
        if (paras.hasOwnProperty("limit")) {
            //如果limit里面参数个数不符合
            if (paras.limit.length == 1) {
                paras.limit = [0, paras.limit[0]];       //默认10个
            }
            //这里处理
            paras.limit[0] = parseInt(paras.limit[0]);
            //长度
            paras.limit[1] = parseInt(paras.limit[1]);
        }

        /**
         * 执行数据查询，数据库分页查询，queryEntityList底层通用
         * 对应结果直接返回
         */
        ofInstance(oodbc, databaseConfig, _instance).queryEntityList(paras)
            .then(function (data) {
                /**
                 * 开始位置、pageSize
                 */
                data.start = paras.limit[0];
                data.pageSize = paras.limit[1];
                //返回结构
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/**
 * 查询瀑布数据列表，不返回记录总数
 * @param databaseConfig
 * @param instanceConfig
 * @param paras
 * @returns {Promise}
 */
m.getEntityListByWaterfall = function (oodbc, databaseConfig, instanceConfig, paras) {
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 定义SQL命令参数
         * 不同的业务表，只需要配置参数实现对应的数据查询，底层代码通用
         * 参数：paras.command
         */
        var _instance = instanceConfig.instance || "DEFAULT";      //数据库配置实例节点名称
        paras.command = {
            //查询的表名
            "tableName": instanceConfig.tableName
        };
        /**
         * 兼容性修复，用limit替换之前的start、length参数
         */
        if (!paras.hasOwnProperty("limit") && paras.hasOwnProperty("start") && paras.hasOwnProperty("length")) {
            paras.limit = [paras["start"], paras["length"]];
        }
        if (paras.hasOwnProperty("limit")) {
            //如果limit里面参数个数不符合
            if (paras.limit.length == 1) {
                paras.limit = [0, paras.limit[0]];       //默认10个
            }
            //这里处理
            paras.limit[0] = parseInt(paras.limit[0]);
            //长度+1
            paras.limit[1] = parseInt(paras.limit[1]) + 1;
        }

        /**
         * 执行数据查询，数据库分页查询，queryEntityList底层通用
         * 对应结果直接返回
         */
        ofInstance(oodbc, databaseConfig, _instance).queryWaterfallList(paras)
            .then(function (data) {
                /**
                 * 获取到了分页数据
                 * 检测是否为最后一页数据
                 * 查询结果行数删除末尾一条数据
                 */
                data.isLastPage = false;
                if (data.data.length < (paras.limit[1] - 1)) {
                    //最后一页数据
                    data.isLastPage = true;
                }
                else {
                    //记录数等于length+1，删除最后一条数据
                    if (data.data.length > (paras.limit[1] - 1))
                        data.data.pop();
                }
                /**
                 * 开始位置
                 * pageSize减去1
                 */
                data.start = paras.limit[0];
                data.pageSize = paras.limit[1] - 1;
                //数据返回
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/**
 * 根据动态参数条件查询数据实体对象（定义内部通用方法）
 * 主要是底层数据方法
 */
m.getEntity = function (oodbc, databaseConfig, instanceConfig, paras) {
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 定义SQL命令参数
         * 不同的业务表，只需要配置参数实现对应的数据查询，底层代码通用
         * 参数：paras.command
         */
        var _instance = instanceConfig.instance || "DEFAULT";      //数据库配置实例节点名称
        paras.command = {
            //查询的表名
            "tableName": instanceConfig.tableName,
        };

        /**
         * 兼容性修复，用limit替换之前的start、length参数
         */
        if (!paras.hasOwnProperty("limit") && paras.hasOwnProperty("start") && paras.hasOwnProperty("length")) {
            paras.limit = [paras["start"], paras["length"]];
        }
        if (paras.hasOwnProperty("limit")) {
            //如果limit里面参数个数不符合
            if (paras.limit.length == 1) {
                paras.limit = [0, paras.limit[0]];       //默认10个
            }
            //这里处理
            paras.limit[0] = parseInt(paras.limit[0]);
            //长度
            paras.limit[1] = parseInt(paras.limit[1]);
        }

        /**
         * 执行数据查询，数据库分页查询，queryEntityList底层通用
         * 对应结果直接返回
         * ORM底层实例
         */
        ofInstance(oodbc, databaseConfig, _instance).queryEntity(paras)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/**
 * 新增用户信息
 * @param databaseConfig
 * @param instanceConfig
 * @param paras
 */
m.insertEntity = function (oodbc, databaseConfig, instanceConfig, paras) {
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * ：：以下代码重用时不需要更改，特殊情况除外
         * 定义SQL命令参数
         * 不同的业务表，只需要配置参数实现对应的数据查询，底层代码通用
         * 参数：paras.command
         */
        var _instance = instanceConfig.instance || "DEFAULT";      //数据库配置实例节点名称
        var p = {
            "command": {
                //查询的表名
                "tableName": instanceConfig.tableName,
            },
            "insertion": {}
        };

        //检测字段是否存在
        var completion = false;
        for (var i in instanceConfig.tableFiles) {
            if (instanceConfig.tableFiles[i] == "id") {
                completion = true;                          //兼容以前的代码
                break;
            }
        }

        /**
         * 自动创建主键
         * 如果未指定主键，则使用
         */
        if (!paras.hasOwnProperty('id') && completion) {
            paras.id = m.getID('A', null, null);
        }

        /**
         * 参数封装：insert表字段从字段配置表中遍历
         * 每个表的字段不一致，封装的参数直接拼接成insert语句的SQL，请注意字段名称必须是同数据库字段名称一致的
         * 不用参数遍历，这里手动指定
         */
        for (var i = 0; i < instanceConfig.tableFiles.length; i++) {
            /**
             * 字段多少、字段的排序，以配置结构为准
             */
            if (paras.hasOwnProperty(instanceConfig.tableFiles[i])) {
                p.insertion[instanceConfig.tableFiles[i]] = paras[instanceConfig.tableFiles[i]];
            }
        }
        /**
         * 参数默认值处理
         */
        //创建时间
        if (!p.insertion['created_time'] && instanceConfig.tableFiles['created_time']) {
            p.insertion['created_time'] = new Date();
        }
        //最近更新时间
        if (!p.insertion['update_time'] && instanceConfig.tableFiles['update_time']) {
            p.insertion['update_time'] = new Date();
        }
        //是否有效（0无效；1有效）
        if (!p.insertion['valid'] && instanceConfig.tableFiles['valid']) {
            p.insertion['valid'] = 1;
        }

        /**
         * 执行SQL，底层方法通用，配置好了参数集合列表底层自动完成
         */
        ofInstance(oodbc, databaseConfig, _instance).insertion(p)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });

    });
}

/**
 * 批量新增数据
 * @param databaseConfig
 * @param instanceConfig
 * @param paras
 */
m.insertBatch = function (oodbc, databaseConfig, instanceConfig, paras) {
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * ：：以下代码重用时不需要更改，特殊情况除外
         * 定义SQL命令参数
         * 不同的业务表，只需要配置参数实现对应的数据查询，底层代码通用
         * 参数：paras.command
         */
        var _instance = instanceConfig.instance || "DEFAULT";      //数据库配置实例节点名称
        var p = {
            "command": {
                //查询的表名
                "tableName": instanceConfig.tableName,
            },
            "insertion": []         //这是个数组
        };

        /**
         * 遍历结果集
         */
        console.time('【onela】批量新增参数预处理');
        //检测字段是否存在
        var completion = false;
        for (var i in instanceConfig.tableFiles) {
            if (instanceConfig.tableFiles[i] == "id") {
                completion = true;                          //兼容以前的代码
                break;
            }
        }

        for (var i in paras) {
            //数据项
            var item = paras[i];
            //新增实体
            var _target = {};

            //id规则：兼容以前的代码，自动补充主键id值
            if (!item.hasOwnProperty('id') && completion) {
                item.id = m.getID('A', null, null);
            }
            /**
             * 参数封装：insert表字段从字段配置表中遍历
             * 每个表的字段不一致，封装的参数直接拼接成insert语句的SQL，请注意字段名称必须是同数据库字段名称一致的
             * 不用参数遍历，这里手动指定
             */
            for (var f in item) {
                for (var j = 0; j < instanceConfig.tableFiles.length; j++) {
                    /**
                     * 字段多少、字段的排序，以配置结构为准
                     */
                    if (item.hasOwnProperty(instanceConfig.tableFiles[j])) {
                        _target[instanceConfig.tableFiles[j]] = item[instanceConfig.tableFiles[j]];
                        /**
                         * 参数默认值处理
                         */
                        //创建时间
                        if (!_target['created_time'] && instanceConfig.tableFiles['created_time']) {
                            _target['created_time'] = new Date();
                        }
                        //最近更新时间
                        if (!_target['update_time'] && instanceConfig.tableFiles['update_time']) {
                            _target['update_time'] = new Date();
                        }
                        //是否有效（0无效；1有效）
                        if (!_target['valid'] && instanceConfig.tableFiles['valid']) {
                            _target['valid'] = 1;
                        }
                    }
                }
            }
            p.insertion.push(_target);

        }
        console.timeEnd('【onela】批量新增参数预处理');

        /**
         * 执行SQL，底层方法通用，配置好了参数集合列表底层自动完成
         */
        ofInstance(oodbc, databaseConfig, _instance).insertionBatch(p)
            .then(function (result) {
                resolve(result);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/**
 * 修改实体信息
 * @param databaseConfig
 * @param instanceConfig
 * @param paras
 */
m.updateBySenior = function (oodbc, databaseConfig, instanceConfig, paras) {
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 定义SQL命令参数
         * 不同的业务表，只需要配置参数实现对应的数据查询，底层代码通用
         * 参数：paras.command
         */
        var _instance = instanceConfig.instance || "DEFAULT";      //数据库配置实例节点名称
        var p = {
            "command": {
                //查询的表名
                "tableName": instanceConfig.tableName,
            },
            /**
             * 累加
             */
            "update": paras["update"],
            /**
             * 更新的where条件
             */
            "keyword": paras.keyword
        };

        /**
         * 执行sql
         */
        ofInstance(oodbc, databaseConfig, _instance).updateBySenior(p)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/**
 * 聚合函数的应用
 * @param databaseConfig
 * @param instanceConfig
 * @param paras
 */
m.getEntityByAggregate = function (oodbc, databaseConfig, instanceConfig, paras) {
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 条件判断
         */
        if (!paras.aggregate || paras.aggregate.length == 0) {
            reject("缺少aggregate参数，数组参数，长度大于0");
            return;
        }
        /**
         * 定义SQL命令参数
         * 不同的业务表，只需要配置参数实现对应的数据查询，底层代码通用
         * 参数：paras.command
         */
        var _instance = instanceConfig.instance || "DEFAULT";      //数据库配置实例节点名称
        paras.command = {
            //查询的表名
            "tableName": instanceConfig.tableName
        };

        /**
         * 执行数据查询，数据库分页查询，statsByCount底层通用
         * 对应结果直接返回
         */
        ofInstance(oodbc, databaseConfig, _instance).statsByAggregate(paras)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}

/**
 * 物理删除实体信息
 * 注意：请谨慎使用该方法，因为会时数据物理删除
 * @param databaseConfig
 * @param instanceConfig
 * @param paras
 */
m.deleteEntity = function (oodbc, databaseConfig, instanceConfig, paras) {
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 定义SQL命令参数
         * 不同的业务表，只需要配置参数实现对应的数据查询，底层代码通用
         * 参数：paras.command
         */
        var _instance = instanceConfig.instance || "DEFAULT";      //数据库配置实例节点名称
        paras.command = {
            //查询的表名
            "tableName": instanceConfig.tableName
        };

        /**
         * 物理删除，根据
         */
        ofInstance(oodbc, databaseConfig, _instance).deleteEntity(paras)
            .then(function (data) {
                resolve(data);
            })
            .catch(function (err) {
                reject(err);
            });
    });
}


/**
 * 【老版本兼容，新版本弃用】
 * 通用模块-命令行参数处理
 * author:zack zou
 * create time:2016-06-27
 */
var oParameters = require("./grammar/mysql.js");

/**
 * 数据库实例对象
 */
function Service(oodbc, _database, _instance) {
    //数据库标签识别，如果没有标识默认使用MYSQL
    var database = _database || "MYSQL";
    database = database.toUpperCase();
    this.database = database;
    var source = oodbc[database];
    //判断数据库识别
    if (source) {
        //实例识别
        var dbsource = source[_instance.toUpperCase()];
        if (dbsource) {
            //识别正确，返回数据源实例对象，读写分离的实例一起返回
            this.db = dbsource;
        }
        else {
            console.log('数据源实例配置节点识别错误，请检查' + _instance.toUpperCase() + '节点是否存在');
            throw new TypeError('数据源实例配置节点识别错误，请检查' + _instance.toUpperCase() + '节点是否存在');
        }
    }
    else {
        console.log('数据库类型节点配置识别错误，请检查' + this.database.toUpperCase() + '节点是否存在');
        throw new TypeError('数据库类型节点配置识别错误，请检查' + this.database.toUpperCase() + '节点是否存在');
    }
}

/**
 * 数据库实例
 * @param database 数据库名称
 * @param instance
 * @returns {Promise}
 */
function service(oodbc, _database, _instance) {

    if (!(this instanceof Service)) {
        return new Service(oodbc, _database, _instance);
    }
}

/**
 * 数据库实例命令执行（高频执行）
 * 读写实例走这一个对象
 * @param instance 数据库实例对象
 * @param sql 执行的SQL
 * @param parameters 参数
 * @returns {Promise}
 */
var execute = function (instance, sql, parameters) {
    return new Promise(function (resolve, reject) {
        //执行对数据库的访问
        console.time('【onela】执行SQL时间');
        //console.log('执行sql', sql, '参数', parameters);
        if (instance) {
            instance.query(sql, parameters, function (err, doc) {
                console.timeEnd('【onela】执行SQL时间');
                if (err) {
                    reject(err);
                }
                else {
                    resolve(doc);
                }
            });
        }
        else {
            reject("数据库instance实例未正确指向，请检查oodbc数据实例配置和表结构配置（onelaInstanceConfig.json）的实例对照是否正确");
        }
    });
}

/**
 * 查询瀑布数据列表，不返回记录总数，当返回的记录数小于每页显示记录数时，查询到了末尾
 * 查询记录length，查询可以length+1，前台数据只显示length的记录，判断尾页，利用length+1判断，小于等于length的记录到了末尾页面
 * @param paras 关键参数：paras.command、paras.keyword、paras.orderBy
 * author:zack zou
 * create time:2017-03-22
 */
Service.prototype.queryWaterfallList = function (paras) {
    //参数
    var database = this.database;
    var db = this.db;
    /**
     * 数据工厂
     */
    var factory = {
        "MYSQL": function () {
            return new Promise(function (resolve, reject) {
                /**
                 * 参数模型处理
                 */
                var p = oParameters.getParameters(paras);
                //瀑布流数据查询（单次数据查询）
                var sql = "select " + p["select"] + "  from " + paras.command.tableName + " as t " + p.where + " " + p.orderBy + p.limit + ";";
                //执行SQL
                execute(db['READER'], sql, p.parameters)
                    .then(function (doc) {
                        //定义返回数据结构
                        var data = {
                            data: doc,                          //数据列表
                        }
                        //返回数据
                        resolve(data);
                    })
                    .catch(function (err) {
                        console.log('执行execute查询数据列表出错', err);
                        reject(err);
                    });
            });
        }
    }

    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 数据工厂
         */
        var workshop = factory[database];
        if (workshop) {
            workshop()
                .then(function (data) {
                    resolve(data);
                })
                .catch(function (err) {
                    reject(err);
                });
        }
        else {
            reject('非常抱歉，OFranmework目前对' + database + '数据库还未支持！');
        }
    });
}

/**
 * 查询实体列表集合数据
 * @param paras 关键参数：paras.command、paras.keyword、paras.orderBy
 * author:zack zou
 * create time:2017-03-22
 */
Service.prototype.queryEntityList = function (paras) {
    //参数
    var database = this.database;
    var db = this.db;
    /**
     * 数据工厂
     */
    var factory = {
        "MYSQL": function () {
            return new Promise(function (resolve, reject) {
                /**
                 * 业务模型处理
                 */
                var p = oParameters.getParameters(paras);
                //变量定义
                var result = {
                    "data": [],                                         //数据列表
                    "recordsTotal": 0                                  //查询记录总数
                }
                /**
                 * 分页数据查询
                 */
                var sql = "select " + p["select"] + " from " + paras.command.tableName + " t " + p.where + " " + p.orderBy + p.limit + ";";
                //执行SQL
                execute(db['READER'], sql, p.parameters)
                    .then(function (doc) {
                        //数据绑定
                        result.data = doc;
                        //执行正确，接下来再执行记录总数查询
                        return execute(db['READER'], "select count(0) total from " + paras.command.tableName + " t " + p.where, p.parameters);
                    })
                    .then(function (counts) {
                        //执行成功，绑定记录数据
                        result.recordsTotal = counts[0].total;
                        //返回结构
                        resolve(result);
                    })
                    .catch(function (err) {
                        console.log('执行execute查询数据列表出错', err);
                        reject(err);
                    });

            });
        }
    }

    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 数据工厂
         */
        var workshop = factory[database];
        if (workshop) {
            workshop()
                .then(function (data) {
                    resolve(data);
                })
                .catch(function (err) {
                    reject(err);
                });
        }
        else {
            reject('非常抱歉，OFranmework目前对' + database + '数据库还未支持！');
        }
    });
}

/**
 * SQL查询结果列表，根据参数返回符合条件的全部结果
 * @param paras 参数集合
 * author:zack zou
 * create time:2017-03-22
 */
Service.prototype.queryEntity = function (paras) {
    //参数
    var database = this.database;
    var db = this.db;
    /**
     * 数据工厂
     */
    var factory = {
        "MYSQL": function () {
            return new Promise(function (resolve, reject) {
                /**
                 * 业务模型处理
                 */
                var p = oParameters.getParameters(paras);
                //瀑布流数据查询（单次数据查询）
                var sql = "select  " + p["select"] + " from " + paras.command.tableName + " as t " + p.where + p.orderBy + p.limit + ";";
                /**
                 * 执行SQL
                 * 根据制定条件获取查询数据列表
                 * 自由组合查询条件
                 */
                execute(db['READER'], sql, p.parameters)
                    .then(function (data) {
                        //返回数据
                        resolve(data);
                    })
                    .catch(function (err) {
                        console.log('执行execute查询数据列表出错', err);
                        reject(err);
                    });
            });
        }
    }

    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 数据工厂
         */
        var workshop = factory[database];
        if (workshop) {
            workshop()
                .then(function (data) {
                    resolve(data);
                })
                .catch(function (err) {
                    reject(err);
                });
        }
        else {
            reject('非常抱歉，OFranmework目前对' + database + '数据库还未支持！');
        }
    });
}

/**
 * 统计信息查询，count统计
 * @param paras 执行参数
 * @param paras.command.aggregate 统计字段
 * author:zack zou
 * create time:2016-08-12
 */
Service.prototype.statsByAggregate = function (paras) {
    //参数
    var database = this.database;
    var db = this.db;
    /**
     * 数据工厂
     */
    var factory = {
        "MYSQL": function () {
            return new Promise(function (resolve, reject) {
                /**
                 * 业务模型处理
                 */
                var p = oParameters.getParameters(paras);
                /**
                 * 参数比对，符合条件的参数才执行处理
                 * 其他聚合函数（SQL）根据需要再添加
                 */
                var check = {
                    "count": "COUNT",
                    "sum": "SUM",
                    "max": "MAX",
                    "min": "MIN",
                    "abs": "ABS",
                    "avg": "AVG"
                }
                /**
                 * 循环遍历执行的聚合函数
                 */
                var show = [];
                for (var i in paras.aggregate) {
                    var c = paras.aggregate[i];
                    var item = check[c.function.toLowerCase()];
                    if (item) {
                        show.push(item + "(" + c.field + ") as " + c.name);
                    }
                }
                //sql
                var sql = "select " + show.join(',') + " from " + paras.command.tableName + " " + p.where + p.limit + ";";
                /**
                 * 执行SQL，进行统计查询
                 */
                execute(db['READER'], sql, p.parameters)
                    .then(function (data) {
                        //返回数据
                        resolve(data);
                    })
                    .catch(function (err) {
                        console.log('执行execute查询统计信息出错', err);
                        reject(err);
                    });

            });
        }
    }
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 数据工厂
         */
        var workshop = factory[database];
        if (workshop) {
            workshop()
                .then(function (data) {
                    resolve(data);
                })
                .catch(function (err) {
                    reject(err);
                });
        }
        else {
            reject('非常抱歉，OFranmework目前对' + database + '数据库还未支持！');
        }
    });
}

//下面的方法是写入相关的操作

/**
 * 新增操作
 * @param paras 新增参数配置
 * author:zack zou
 * create time:2016-07-01
 */
Service.prototype.insertion = function (paras) {
    //参数
    var database = this.database;
    var db = this.db;
    /**
     * 数据工厂
     */
    var factory = {
        "MYSQL": function () {
            return new Promise(function (resolve, reject) {
                /**
                 * 业务模型处理
                 * 参数处理
                 * p：参数值集合，f字段名称集合，s占位参数符号
                 */
                var p = [], f = [], s = [];
                for (var i in paras.insertion) {
                    //参数值
                    p.push(paras.insertion[i]);
                    //字段名称集合
                    f.push(i);
                    //sql参数化处理符合
                    s.push('?');
                }
                var sql = "insert into " + paras.command.tableName + "(" + f.join(',') + ") values(" + s.join(',') + ");";
                /**
                 * 执行SQL
                 * 根据制定条件获取查询数据列表
                 * 自由组合查询条件
                 */
                execute(db['WRITER'], sql, p)
                    .then(function (data) {
                        //返回数据
                        resolve(paras.insertion);
                    })
                    .catch(function (err) {
                        console.log('执行execute查询insert出错', err);
                        reject(err);
                    });

            });
        }
    }
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 数据工厂
         */
        var workshop = factory[database];
        if (workshop) {
            workshop()
                .then(function (data) {
                    resolve(data);
                })
                .catch(function (err) {
                    reject(err);
                });
        }
        else {
            reject('非常抱歉，OFranmework目前对' + database + '数据库还未支持！');
        }
    });
}

/**
 * 批量新增操作
 * @param paras 新增参数配置
 * author:zack zou
 * create time:2016-07-10
 */
Service.prototype.insertionBatch = function (paras) {
    //参数
    var database = this.database;
    var db = this.db;
    /**
     * 数据工厂
     */
    var factory = {
        "MYSQL": function () {
            return new Promise(function (resolve, reject) {
                /**
                 * 业务模型处理
                 * 参数处理
                 * p：参数值集合，f字段名称集合，s占位参数符号
                 */
                var p = [], f = [], s = [];
                /**
                 * 这是个数组，数组内部是实体对象的字段
                 * 需要双重遍历
                 */
                for (var i in paras.insertion) {
                    var s2 = [];
                    for (var j in paras.insertion[i]) {
                        if (i == 0) {
                            //字段名称集合，大于0就不需要继续了
                            f.push("`" + j + "`");
                        }
                        //参数值
                        p.push(paras.insertion[i][j]);
                        //sql参数化处理符合
                        s2.push('?');
                    }
                    //置入
                    s.push('(' + s2.join(',') + ')');
                }
                //SQL执行
                var sql = "insert into " + paras.command.tableName + "(" + f.join(',') + ") values" + s.join(',') + ";";
                console.log(sql)
                execute(db['WRITER'], sql, p)
                    .then(function (data) {
                        //返回数据
                        resolve(data);
                    })
                    .catch(function (err) {
                        console.log('执行execute查询数据列表出错', err);
                        reject(err);
                    });
            });
        }
    }
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 数据工厂
         */
        var workshop = factory[database];
        if (workshop) {
            workshop()
                .then(function (data) {
                    resolve(data);
                })
                .catch(function (err) {
                    reject(err);
                });
        }
        else {
            reject('非常抱歉，OFranmework目前对' + database + '数据库还未支持！');
        }
    });
}

/**
 * 更新操作，这个更新方法更加灵活
 * 这个方法参数定义比较复杂，了解的实现原理再使用
 * @param paras 新增参数配置
 * author:zack zou
 * create time:2016-08-01
 */
Service.prototype.updateBySenior = function (paras) {
    //参数
    var database = this.database;
    var db = this.db;
    /**
     * 数据工厂
     */
    var factory = {
        "MYSQL": function () {
            return new Promise(function (resolve, reject) {
                /**
                 * 业务模型处理
                 * 特殊参数处理，防止全表更新
                 */
                if (!paras.hasOwnProperty('keyword') || paras.keyword.length == 0) {
                    /**
                     * id等于空值，更新不成功，过滤错误参数导致的全表更新
                     * //paras.keyword = [{"key": "id", "value": "", "logic": "and", "operator": "="}];
                     */
                    reject('paras.keyword更新条件（数组）必须存在条件');
                    return;
                }
                /**
                 * 参数处理
                 */
                var p = oParameters.getUpdateParameters(paras);
                /**
                 * 检测是否存在limit节点
                 * paras.limit=1
                 */
                var _limit = "";
                if (paras.limit && paras.hasOwnProperty('limit')) {
                    /**
                     * 必须两个值
                     */
                    _limit = " limit ?";
                    p.parameters.push(paras.limit);
                }
                /**
                 * SQL执行
                 * 返回变更数据的记录行数
                 */
                var sql = "update " + paras.command.tableName + " set " + p.set.join(',') + " where " + p.where + _limit + ";";
                execute(db['WRITER'], sql, p.parameters)
                    .then(function (data) {
                        //返回数据
                        resolve(data);
                    })
                    .catch(function (err) {
                        console.log('执行execute操作updateBySenior出错', err);
                        reject(err);
                    });
            });
        }
    }
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 数据工厂
         */
        var workshop = factory[database];
        if (workshop) {
            workshop()
                .then(function (data) {
                    resolve(data);
                })
                .catch(function (err) {
                    reject(err);
                });
        }
        else {
            reject('非常抱歉，OFranmework目前对' + database + '数据库还未支持！');
        }
    });
}

/**
 * 物理删除实例对象，改方法慎用，建议采用逻辑删除
 * @param paras 新增参数配置
 * author:zack zou
 * create time:2017-04-06
 */
Service.prototype.deleteEntity = function (paras) {
    //参数
    var database = this.database;
    var db = this.db;
    /**
     * 数据工厂
     */
    var factory = {
        "MYSQL": function () {
            return new Promise(function (resolve, reject) {
                /**
                 * 业务模型处理
                 * 特殊参数处理，防止全表更新
                 */
                if (!paras.hasOwnProperty('keyword') || paras.keyword.length == 0) {
                    /**
                     * id等于空值，更新不成功，过滤错误参数导致的全表更新
                     * //paras.keyword = [{"key": "id", "value": "", "logic": "and", "operator": "="}];
                     */
                    reject('paras.keyword更新条件（数组）必须存在条件');
                    return;
                }
                /**
                 * 参数处理
                 */
                var p = oParameters.getDeleteParameters(paras);

                /**
                 * SQL执行
                 * 返回变更数据的记录行数
                 */
                var sql = "delete from " + paras.command.tableName + " where " + p.where + ";";
                execute(db['WRITER'], sql, p.parameters)
                    .then(function (data) {
                        //返回数据
                        resolve(data);
                    })
                    .catch(function (err) {
                        console.log('执行execute操作deleteEntity出错', err);
                        reject(err);
                    });

            });
        }
    }
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 数据工厂
         */
        var workshop = factory[database];
        if (workshop) {
            workshop()
                .then(function (data) {
                    resolve(data);
                })
                .catch(function (err) {
                    reject(err);
                });
        }
        else {
            reject('非常抱歉，OFranmework目前对' + database + '数据库还未支持！');
        }
    });
}

/**
 * Proc存储过程执行，根据参数返回符合条件的结果
 * @param paras 参数集合，以下为参数示例：
 * {
 *   "proc_name":"usp_biz_getBizInfoList",            //存储过程名称
 *   "keyword":{"_start":0,"_length":10}              //存储过程的输入参数
 * }
 * author:zack zou
 * create time：2017-05-02
 */
Service.prototype.call = function (paras) {
    //参数
    var database = this.database;
    var db = this.db;
    /**
     * 数据工厂
     */
    var factory = {
        "MYSQL": function () {
            return new Promise(function (resolve, reject) {
                /**
                 * 业务模型处理
                 * 参数处理，数据库模式，支持多种数据库，目前写死mysql
                 * 存储过程基于不同类型数据库的架构支持，目前只做支持mysql的示例
                 * 代码预留位置
                 */
                paras.database = database.toUpperCase();          //'mysql';
                paras.symbol = '@';
                /**
                 * 架构中间处理变量
                 */
                var _self = {"call": "", "parameter": [], values: []};
                /**
                 * 输入参数匹配
                 */
                var _error = false;
                var error = [];
                //遍历配置参数
                for (var j in paras.config.parameter) {
                    //参数类型
                    var item = paras.config.parameter[j];
                    //错误标记，临时变量
                    _error = false;
                    /**
                     * 输出参数，查看参数是否完全匹配
                     * 不在可传参数之列的参数，程序自动忽略
                     */
                    for (var i in paras.keyword) {
                        //匹配到字段定义：参数的类型可能会是多种情况 [in|out|inout]
                        if (item.name == i) {
                            if (item.hasOwnProperty('type') && item.type === 'in') {
                                //参数匹配，检测是参数类型
                                _self.values.push(paras.keyword[i]);     //@parameter value
                                _self.parameter.push('?');
                                _error = true;
                            }
                            else {
                                // 输入参数匹配
                                // 输出或者输入类型，对于直接调用的方式out或者inout不好用
                                _self.parameter.push(paras.symbol + item.name);
                                _error = true;
                            }
                        }
                    }

                    //发现错误
                    if (!_error) {
                        if (!item.mandatory) {           //非强制参数，应该存在默认值
                            //检测参数类型，是否为强制性参数
                            //参数类型未直接定义
                            if (item.hasOwnProperty('type') && item.type !== 'in') {
                                //未直接定义的out inout参数，另外一种情况就是存在默认值
                                _self.parameter.push(paras.symbol + item.name);
                            }
                            else {
                                //参数类型是in，但是参数是可选的情况，那么直接调用默认值，假如连这个默认值没有，直接抛错
                                if (item.hasOwnProperty('defaultValue')) {
                                    _self.values.push(item.name);               //使用默认值
                                    _self.parameter.push(item.defaultValue);
                                }
                                else {
                                    error.push(item);
                                }
                            }
                        }
                        else {
                            //强制参束未赋值，也没有默认值
                            error.push(item);
                        }

                    }
                }

                /**
                 * 是否错误抛出，说明未传入该参数，分两种情况，检测是否为强制参数
                 */
                if (error.length > 0) {
                    console.log('error', error);
                    reject("参数不正确");
                    return;
                }
                /**
                 * 参数处理，支持不通类型数据库的业务处理（目前只固定处理mysql）
                 */
                //参数名（防止SQL注入，）
                _self.call = "call " + paras.proc_name + "(" + _self.parameter.join(',') + ");";

                /**
                 * 参数化处理完毕
                 * 执行存储过程
                 */
                execute(db['READER'], _self.call, _self.values)
                    .then(function (data) {
                        //返回数据
                        resolve(data);
                    })
                    .catch(function (err) {
                        console.log('执行execute查询proc出错', err);
                        reject(err);
                    });
            });
        }
    }
    //定义结构
    return new Promise(function (resolve, reject) {
        /**
         * 数据工厂
         */
        var workshop = factory[database];
        if (workshop) {
            workshop()
                .then(function (data) {
                    resolve(data);
                })
                .catch(function (err) {
                    reject(err);
                });
        }
        else {
            reject('非常抱歉，OFranmework目前对' + database + '数据库还未支持！');
        }
    });
}

module.exports = service;


'use strict'

const path = require('path')
const isPlainObj = require('is-plain-obj')
const parseStructure = require('./parseStructure')
const writeStructure = require('./writeStructure')
const binStructure = require('./binStructure')
const cleanup = require('./cleanup')

/**
 * Creates a new instance of fsify. Each instance has its own bin to make testing easier.
 * @public
 * @param {?Object} opts - Options.
 * @returns {Function}
 */
module.exports = function(opts = {}) {

	const bin = require('./bin')()

	/**
	 * Converts an object into a persistent or temporary directory structure.
	 * @param {?Array} structure - Array of objects containing information about a directory or file.
	 * @returns {Promise<Array>} Parsed structure.
	 */
	const main = function(structure = []) {

		return new Promise((resolve, reject) => {

			if (Array.isArray(structure) === false) {
				throw new Error(`'structure' must be an array`)
			}

			parseStructure(structure, opts.cwd)
				.then((parsedStructure) => writeStructure(parsedStructure))
				.then((parsedStructure) => binStructure(parsedStructure, bin, opts.persistent))
				.then(resolve, reject)

		})

	}

	/**
	 * Triggers a cleanup.
	 * @returns {Array} deletedEntries - Deleted directories and files.
	 */
	main.cleanup = function() {

		const entriesToDelete = bin()

		return cleanup(entriesToDelete, opts.force)

	}

	/**
	 * Constants for the structure.
	 * We don't use symbols for the constants as it should still be possible
	 * to copy, paste and use the JSON output of `tree`.
	 */
	main.DIRECTORY = module.exports.DIRECTORY
	main.FILE = module.exports.FILE

	if (isPlainObj(opts) === false) {
		throw new Error(`'opts' must be an object, null or undefined`)
	}

	opts = Object.assign({
		cwd: process.cwd(),
		persistent: true,
		force: false
	}, opts)

	// Support relative and absolute paths
	opts.cwd = path.resolve(opts.cwd)

	// Add cleanup listener when files shouldn't be persistent
	if (opts.persistent === false) process.addListener('exit', main.cleanup)

	return main

}

/**
 * Constants for the structure.
 * We don't use symbols for the constants as it should still be possible
 * to copy, paste and use the JSON output of `tree`.
 * @public
 */
module.exports.DIRECTORY = 'directory'
module.exports.FILE = 'file'


/**
 * Appends WHERE conditions to query builder based on given date parameters
 * @param {Object} queryBuilder - knex querybuilder instance. Mutated during this operation
 * @param {string} column
 * @param {Date} from
 * @param {Date} to
 */
function addAndWhereDate(queryBuilder, column, from, to) {
  if (from && to) {
    queryBuilder.whereBetween(column, [from, to]);
  } else if (from) {
    queryBuilder.andWhere(column, '>=', from);
  } else if (to) {
    queryBuilder.andWhere(column, '<=', to);
  }
}

function addAndWhereGte(knexBuilder, attrName, field) {
  if (field) {
    knexBuilder = knexBuilder.andWhere(() => {
      this.where(attrName, '>', field).orWhere(attrName, field);
    });
  }

  return knexBuilder;
}

function addAndWhereLte(knexBuilder, attrName, field) {
  if (field) {
    knexBuilder = knexBuilder.andWhere(() => {
      this.where(attrName, '<', field).orWhere(attrName, field);
    });
  }

  return knexBuilder;
}

function addAndWhere(knexBuilder, attrName, field) {
  if (field) {
    knexBuilder = _handleMultiValuedParameters(knexBuilder, attrName, field);
  }

  return knexBuilder;
}

function setRange(knexBuilder, rangeStart, rangeEnd) {
  if (rangeStart || rangeStart === 0) {
    knexBuilder = knexBuilder.offset(rangeStart).offset(rangeStart);

    if (rangeEnd || rangeEnd === 0) {
      // rangeStart and rangeEnd are inclusive, add 1
      knexBuilder = knexBuilder.limit(rangeEnd - rangeStart + 1);
    }
  }

  return knexBuilder;
}

/**
 * Use this to add an equation clause for the value which is either an object or an array
 * @param {Object} knexBuilder
 * @param {Object} attrName
 * @param {Object} parameter
 * @returns {Object} knexBuilder with added parameter
 */
function _handleMultiValuedParameters(knexBuilder, attrName, parameter) {
  if (parameter instanceof Set) {
    knexBuilder = knexBuilder.whereIn(attrName, Array.from(parameter));
  } else if (Array.isArray(parameter)) {
    knexBuilder = knexBuilder.whereIn(attrName, parameter);
  } else {
    knexBuilder = knexBuilder.where(attrName, parameter);
  }

  return knexBuilder;
}

module.exports = {
  addAndWhereDate,
  addAndWhere,
  addAndWhereGte,
  addAndWhereLte,
  setRange
};


const validate = require('validation-utils');
const tableCleaner = require('knex-tablecleaner');

/**
 * Clean db
 *
 * @param {Object} knex
 * @param {string[]} tableNames - array of table names
 * @param {object} [logger]
 * @returns {Promise}
 */
function cleanDb(knex, tableNames, logger, verboseLog = false) {
  validate.notNil(tableNames);

  return tableCleaner
    .cleanTables(knex, tableNames, verboseLog)
    .then(() => {
      if (logger) {
        logger.info('Tables cleaned successfully: ', tableNames.join(', '));
      }
    })
    .catch(err => {
      if (logger) {
        logger.error('Error cleaning tables', err);
      }
      throw err;
    });
}

module.exports = {
  cleanDb
};


const validate = require('validation-utils');
const Knex = require('knex');

const _registry = [];

/**
 * Get Knex instance without validating the connection
 * @param {Object} config - Knex config
 * @param [registry]
 * @param [logger]
 * @returns {Object} knex instance. Note that connection is not validated to be correct - make sure to run heartbeat check on it
 */
function getKnexInstance(config, registry = _registry, logger = console) {
  validate.notNil(config, 'Config is null or undefined');
  validate.notNil(config.client, 'DB client is null or undefined');

  const { host, database, user } = config.connection;
  const connectionTimeout = config.acquireConnectionTimeout;

  logger.info(`Init db: ${user}/<Password omitted>@${host} db: ${database}`);
  logger.info(`Timeout: ${connectionTimeout}`);

  const knex = module.exports._initKnexInstance(config);
  module.exports.registerKnexInstance(knex, registry);
  // unfortunately, we can't check heartbeat here and fail-fast, as this initialization is synchronous
  return knex;
}

function _initKnexInstance(config) {
  return Knex(config);
}

/**
 * Attempts to close all registered knex instances
 * @param {Object[]} [registry]
 * @returns {Promise<Object[]>} List of errors that occured during closing operations
 */
function closeAllInstances(registry = _registry) {
  const promises = [];
  const errors = [];
  while (registry.length > 0) {
    const knex = registry.pop();
    const destructionPromise = knex.destroy().catch(e => {
      errors.push({
        knex,
        cause: e
      });
    });
    promises.push(destructionPromise);
  }
  return Promise.all(promises).then(() => {
    return errors;
  });
}

/**
 *
 * @param {Object[]} errors
 * @param [logger]
 */
function logAllClosingErrors(errors, logger = console) {
  errors.forEach(error => {
    const knexConfig = error.knex.client.config.connection;
    const formattedDbDescriptor = `${knexConfig.user}@${knexConfig.host}:${knexConfig.database}`;
    logger.error(`Failed to close DB connection (${formattedDbDescriptor}): `, error.cause);
  });
}

/**
 * @returns {Object[]}
 */
function getRegistry() {
  return _registry;
}

/**
 *
 * @param {Object} knex
 * @param {Object[]} [registry]
 */
function registerKnexInstance(knex, registry = _registry) {
  registry.push(knex);
}

module.exports = {
  closeAllInstances,
  getKnexInstance,
  getRegistry,
  logAllClosingErrors,
  registerKnexInstance,
  _initKnexInstance
};


import remark from 'remark';
import remarkHtml from 'remark-html';
import visit from 'unist-util-visit';
import low from 'lowlight';
import parse5 from 'parse5';
import _ from 'lodash';
import { errorInlineHtml } from '../util';

/* eslint-disable no-console */

const defaultOptions = {
	plugins: [],
	hljs: {
		aliases: {
			yaml: 'python',
			shell: 'bash',
		},
	},
	customTags: {},
};

const remarkHtmlOptions = {
	entities: 'escape',
};

/**
 * Escape double slashes inside custom tags.
 *
 * @param {string} string
 * @return {string}
 */
export function escapeMarkdownInTags(string) {
	return string.replace(/<x-.*?>([\s\S]*?<\/x-)?/gm, m => {
		return m.replace(/\/\//g, '\\/\\/');
	});
}

/**
 * Unescape double slashes in Markdown.
 *
 * @param {string} string
 * @return {string}
 */
export function unescapeMarkdown(string) {
	return string.replace(/\\\/\\\//g, '//');
}

/**
 * Remark plugin for custom tags: <x-foo data-value="42"/>
 *
 * @param {Object} customTags
 * @return {Function}
 */
function remarkCustomTags(customTags) {
	return ast =>
		visit(ast, 'html', node => {
			if (node.value.startsWith('<x-')) {
				// Parse tag’s HTML
				const dom = parse5.parseFragment(unescapeMarkdown(node.value));
				const tagNode = dom.childNodes[0];
				if (!tagNode) {
					throw new Error('Cannot parse custom tag:', node.value);
				}
				let { tagName, attrs } = tagNode;
				const childNode = tagNode.childNodes[0];
				attrs.push({
					name: 'children',
					value: childNode ? childNode.value.trim() : null,
				});
				tagName = tagName.replace(/^x-/, '');

				// Check tag function
				const tagFunction = customTags[tagName];
				if (!tagFunction || !_.isFunction(tagFunction)) {
					throw new Error(
						`Custom tag "${tagName}" is not defined or is not a function.`
					);
				}

				// Unzip attributes
				attrs = attrs.reduce((attrsObj, attr) => {
					attrsObj[attr.name] = attr.value;
					return attrsObj;
				}, {});

				// Render
				let result;
				try {
					result = tagFunction(attrs) || '';
				} catch (exception) {
					result = errorInlineHtml(
						`Error while rendering custom tag <x-${tagName}>: ${
							exception.message
						}`,
						{ block: true }
					);
				}
				node.value = result.toString().trim();
			}
		});
}

/**
 * Remark plugin for Highlight.js.
 *
 * @param {Object} options
 * @return {Function}
 */
function remarkHljs({ aliases }) {
	return ast =>
		visit(ast, 'code', node => {
			if (!node.data) {
				node.data = {};
			}

			const lang = node.lang;
			const highlighted = lang
				? low.highlight(aliases[lang] || lang, node.value).value
				: low.highlightAuto(node.value).value;
			node.data.hChildren = highlighted;
			node.data.hProperties = {
				className: ['hljs', lang && `language-${lang}`],
			};
		});
}

/**
 * Render Markdow using given processor.
 *
 * @param {Object} processor Remark processor.
 * @param {string} source Source Markdown.
 * @return {string}
 */
function render(processor, source) {
	try {
		return processor.processSync(source).contents;
	} catch (exception) {
		const error = `Error while rendering Markdown: ${exception.message}`;
		console.error(error);
		return errorInlineHtml(error).toString();
	}
}

/**
 * Returns function that renders Markdown using Remark.
 *
 * @param {object} options
 * @return {Function}
 */
export default function createMarkdownRenderer(options = {}) {
	options = _.merge({}, defaultOptions, options);

	// Create Remark processor
	const processor = remark();

	// Attach plugins
	const plugins = options.plugins;
	plugins.push(
		[remarkCustomTags, options.customTags],
		[remarkHtml, remarkHtmlOptions]
	);
	if (options.hljs) {
		plugins.push([remarkHljs, options.hljs]);
	}
	plugins.forEach(plugin => {
		if (Array.isArray(plugin)) {
			processor.use(plugin[0], plugin[1]);
		} else {
			processor.use(plugin);
		}
	});

	return source => {
		source = escapeMarkdownInTags(source);
		return render(processor, source);
	};
}

/**
 * Returns function that renders Markdown using Remark (without any extra plugins).
 *
 * @return {Function}
 */
export function createSimpleMarkdownRenderer() {
	const processor = remark().use(remarkHtml, remarkHtmlOptions);
	return source => render(processor, source);
}


"use strict";

const _ 				= require("lodash");
const args 				= require("args");
const chalk 			= require("chalk");
//const fs 				= require("fs");
const path 				= require("path");
const handlebars 		= require("handlebars");
const Promise 			= require("bluebird");
const fs 				= Promise.promisifyAll(require("fs"));

args.option(["t", "template"], "'The template README file path", "./README.md");
const flags = args.parse(process.argv);

if (args.sub.length == 0) {
	console.error(chalk.red.bold("Please set a service filename!"));
	process.exit(1);
}

const file = args.sub[0];

function parseWithJsDoc(file) {
	const jsdoc = require('jsdoc-api');

	console.log(chalk.yellow.bold("Parse service file..."));
	console.log("  File: " + chalk.white.bold(file) + "\n");

	let doc = jsdoc.explainSync({ files: [file] });
	//fs.writeFileSync("./jsdoc.txt", JSON.stringify(doc, null, 4));
	return doc;
}
/*
function parseWithDox(content) {
	const dox = require('dox')

	let doc = dox.parseComments(fs.readFileSync(file, "utf8"));
	//fs.writeFileSync("./dox.txt", JSON.stringify(doc, null, 4));
	return doc;
}*/

function readTemplate(doc) {
	return fs.readFileAsync(flags.template, "utf8").then(template => ({
		doc,
		template
	}));
}

function hasTag(item, tagName) {
	if (item.tags)
		return item.tags.find(tag => tag.title == tagName) != null;

	return false;
}

function getItemType(item) {
	return item.type ? item.type.names.map(s => "`" + s + "`").join(", ") : "any";
}

function resolveParams(item) {
	if (item.params)
		return item.params.map(p => resolveParam(p));

	return [];
}

function resolveParam(p) {
	let defaultValue = p.defaultvalue;
	if (defaultValue === undefined) {
		if (!p.optional && !p.nullable)
			defaultValue = "**required**";
		else
			defaultValue = "-";
	} else
		defaultValue = "`" + JSON.stringify(defaultValue, null, 2) + "`";

	return {
		name: p.name,
		description: p.description,
		type: getItemType(p),
		required: !p.optional && !p.nullable ? "**Yes**": "",
		defaultValue
	}
}

function resolveReturns(item) {
	if (item.returns && item.returns.length > 0) {
		let p = item.returns[0];

		return {
			description: p.description,
			type: getItemType(p)
		}
	}
	return null;
}

function resolveBadges(item) {
	let res = [];
	if (hasTag(item, "cached") || hasTag(item, "cache"))
		res.push("![Cached action](https://img.shields.io/badge/cache-true-blue.svg)");

	if (item.deprecated)
		res.push("![Deprecated action](https://img.shields.io/badge/status-deprecated-orange.svg)");

	return res;
}

function transformReadme({ doc, template }) {
	let module = doc.find(item => item.kind == "module");
	let modulePrefix = module ? module.longname : "";

	const transforms = {
		USAGE(doc) {
			if (module && module.examples) {
				console.log(chalk.yellow.bold("Generating usage...\n"));

				return {
					examples: module.examples,
					hasExamples: module.examples && module.examples.length > 0
				};
			}
		},

		SETTINGS(doc) {
			console.log(chalk.yellow.bold("Generating settings..."));
			let prefix = modulePrefix + ".settings";
			let blocks = doc.filter(item => item.memberof && item.memberof.startsWith(prefix)).map(item => {
				let defaultValue = item.meta && item.meta.code ? item.meta.code.value : undefined;

				if (defaultValue != null) {
					if (!item.optional && !item.nullable)
						defaultValue = "**required**";
					else
						defaultValue = "`null`";
				} else
					defaultValue = "`" + JSON.stringify(defaultValue, null, 2) + "`";

				return {
					name: item.longname.replace(prefix + ".", ""),
					description: item.description,
					type: getItemType(item),
					defaultValue
				};
			});

			blocks.forEach(item => console.log(" - " + chalk.white.bold(item.name) + ": " + trunc(item.description)));
			console.log("");

			return blocks;
		},

		ACTIONS(doc) {
			console.log(chalk.yellow.bold("Generating actions..."));
			let blocks = doc.filter(item => hasTag(item, "actions")).map(item => {
				return {
					name: item.name,
					description: item.description,
					since: item.since,
					examples: item.examples,
					hasExamples: item.examples && item.examples.length > 0,
					deprecated: item.deprecated,
					badges: resolveBadges(item),
					params: resolveParams(item),
					returns: resolveReturns(item)
				};
			});

			blocks.forEach(item => console.log(" - " + chalk.white.bold(item.name) + ": " + trunc(item.description)));
			console.log("");

			return blocks;
		},

		METHODS(doc) {
			console.log(chalk.yellow.bold("Generating methods..."));
			let blocks = doc.filter(item => hasTag(item, "methods")).map(item => {
				return {
					name: item.name,
					description: item.description,
					since: item.since,
					examples: item.examples,
					hasExamples: item.examples && item.examples.length > 0,
					deprecated: item.deprecated,
					badges: resolveBadges(item),
					params: resolveParams(item),
					returns: resolveReturns(item)

				};
			});

			blocks.forEach(item => console.log(" - " + chalk.white.bold(item.name) + ": " + trunc(item.description)));
			console.log("");

			return blocks;
		}
	}

	_.forIn(transforms, (fn, name) => {
		let data = fn(doc);
		template = render(template, name, data);
	});

	return Promise.resolve(template);
}

function trunc(str) {
	return _.truncate(str, { length: 50 });
}

function render(template, name, data) {
	let reTemplate = new RegExp(`\\<\\!--\\s*?AUTO-CONTENT-TEMPLATE:${name}(?:\\r?\\n)+((?:.|\\s)*?)--\\>`, "g");

	let tmpl = reTemplate.exec(template);
	if (tmpl && tmpl.length > 1) {
		let render = handlebars.compile(tmpl[1], {
			noEscape: true,
			preventIndent: true
		});

		let reContent = new RegExp(`(\\<\\!--\\s*?AUTO-CONTENT-START:${name}\\s*?--\\>\\s?)((?:.|\\s)*?)(<!--\\s*?AUTO-CONTENT-END:${name}\\s*?--\\>)`, "g");

		let match = reContent.exec(template);

		let res = template.replace(reContent, "$1" + render(data) + "$3");
		//console.log(res);

		return res;
	}
	return template;
}

function convertTabs(content) {
	return content.replace(/\t/g, "    ");
}

function writeResult(content) {
	console.log(chalk.yellow.bold("Write results..."));
	console.log("  File: " + chalk.white.bold(flags.template));
	//console.log(content);
	return fs.writeFileAsync(flags.template, content, "utf8");
}

Promise.resolve(file)
	//.then(readFileContent)
	.then(parseWithJsDoc)
	.then(readTemplate)
	.then(transformReadme)
	.then(convertTabs)
	.then(writeResult)

	.then(() => {
		console.log(chalk.green.bold("\nDone!"));
	})

	.catch(err => {
		console.error(err);
		process.exit(2);
	});


"use strict";
/**
 * Appends an arrow head marker to the defs element to be used later.
 * @param defElement 'defs' element to append marker elements
 * @param color string representation of a valid color.
 */
function createColorArrow(defElement, color) {
    defElement.append("marker")
        .attr("id", "arrow-" + color)
        .attr("viewBox", "0 -5 10 10")
        .attr("refX", 8)
        .attr("markerWidth", 6)
        .attr("markerHeight", 6)
        .attr("fill", color)
        .attr("orient", "auto")
        .append("path")
        .attr("d", "M0,-5L10,0L0,5")
        .attr("class", "arrowHead");
}
exports.__esModule = true;
exports["default"] = createColorArrow;
//# sourceMappingURL=createColorArrow.js.map