//#import pigeon.js
//#import eventBus.js
//#import Util.js
//#import base64.js
//#import HttpUtil.js
//#import jobs.js
//#import DigestUtil.js
//#import kafkautil.js
//#import @services/dblayer.jsx
//#import @handlers/include/diff.jsx
//#import $owl_task_info:services/modelService.jsx

function trim(s) {
    if (s) {
        return s.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
    }
    return ""
}

var @projectCodeService = (function (pigeon) {
    var objPrefix = '@projectCode'
    var listPrefix = '@projectCode'

    var spec = @spec
    var meta = spec['#meta'];
    var pigeonName = meta["pigeon"];
    if (pigeonName && pigeon.setPigeon) {
        pigeon.setPigeon(pigeonName);
    }

    var formSpecs = @formSpecs

    var flattenedSpecs = @flattenedSpecs

    var idFunc = @idFunc
    var lockFunc = @lockFunc

    var f = {
        /**
         * 添加
         */
        getId: function (data) {
            //默认实现是直接返回一个递增的Id
            //其他实现方式包括从data中获取数据构造出一个Id
            if (data.id) {
                return data.id;
            }
            //@idOrig
            if (idFunc) {
                return objPrefix + '_' + idFunc(data)
            } else {
                var seq = pigeon.getId(objPrefix)
                return objPrefix + '_' + seq
            }
        },

        getSpec: function () {
            return spec;
        },

        getFormSpec: function () {
            return formSpecs;
        },

        getLock: function (data) {
            //@lockOrig
            if (lockFunc) {
                return lockFunc(data)
            } else {
                return data['id']
            }
        },

        lock: function (key) {
            pigeon.lock(key);
        },
        unlock: function (key) {
            pigeon.unlock(key);
        },

        /**
         * 返回一个树状结构
         * 本函数读进所有的数据进内存，在内存中构造一个树状结构
         * 所以对于大的数据不是很合适
         * 适用范围是中小型电商系统的商品分类数，地区管理等
         *
         */
        getTree() {
        },

        getAllListName: function (data) {
            return listPrefix + '_all'
        },

        getValue: function (fullkey, data) {
            var path = fullkey.split('.')
            var curData = data
            for (var i = 0; i < path.length; i++) {
                var curKey = path[i]
                if (typeof (curData) == 'object' && curData != null) {
                    curData = curData[curKey]
                } else {
                    return null
                }
            }
            return curData
        },
        deleteValue: function (fullkey, data) {
            var path = fullkey.split('.')
            var curData = data
            for (var i = 0; i < path.length - 1; i++) {
                var curKey = path[i]
                if (typeof (curData) == 'object' && curData != null) {
                    curData = curData[curKey]
                } else {
                    return null
                }
            }
            if (curData) {
                var lastKey = path[path.length - 1];
                delete curData[lastKey];
            }
        },

        setValue: function (fullkey, value, data) {
            var path = fullkey.split('.')
            var curData = data
            var objPath = [data]
            for (var i = 0; i < path.length; i++) {
                var curKey = path[i]
                if (i == path.length - 1) {
                    curData[curKey] = value
                    return
                }

                var subData = curData[curKey]
                if (!subData) {
                    curData[curKey] = {}
                    subData = curData[curKey]

                }
                curData = subData
            }

        },

        getEnvValue: function (v, env) {
            if (!v) {
                return v
            }
            if (v.indexOf('$') == 0) {
                var fullkey = v.substring(1)
                var newValue = f.getValue(fullkey, env);
                if (newValue) {
                    return newValue;
                }
            }
            return v
        },


        getUniqueObj: function (key, value) {
            var pigeonKey = objPrefix + '_' + key + '_' + DigestUtil.md5(value)
            var obj = pigeon.getObject(pigeonKey);
            if (obj == null) {
                return null;
            }
            var id = obj.id;
            return pigeon.getObject(id);
        },

        isDuplicated: function (id, key, value) {
            var pigeonKey = objPrefix + '_' + key + '_' + DigestUtil.md5(value)
            var obj = pigeon.getObject(pigeonKey);
            if (obj == null) {
                return false
            }
            if (obj.id === id) {
                return false
            }
            //取出 obj.id 对应的最新对象，检查该字段的最新值是否仍等于 value，
            //如果不等说明最新的已经改了，旧的唯一记录已过期，不算重复
            var latestObj = pigeon.getObject(obj.id);
            if (latestObj == null) {
                return false
            }
            var latestValue = f.getValue(key, latestObj)
            if (latestValue !== value) {
                return false
            }
            return true
        },


        saveUniqueValue: function (objId, key, value) {
            var md5 = DigestUtil.md5(value)
            var pigeonKey = objPrefix + '_' + key + '_' + md5
            var obj = {
                id: objId,
                key: key,
                value: value,
                md5: md5,
            }
            pigeon.saveObject(pigeonKey, obj)
        },

        removeUniqueValue: function (id, key, value) {
            var md5 = DigestUtil.md5(value)
            var pigeonKey = objPrefix + '_' + key + '_' + md5
            pigeon.saveObject(pigeonKey, null)
        },

        validate: function (data, env) {
            //TODO:需要实现validate 服务器端
            var fields = flattenedSpecs.mainFields
            for (var i = 0; i < fields.length; i++) {
                var field = fields[i]
                var value = f.getValue(field.key, data)
                if (value == null || typeof (value) == 'undefined') {
                    value = field.defaultValue;
                    f.setValue(field.key, value, data)
                }
                if (typeof (value) == 'string' && value.indexOf('$') == 0) {
                    value = f.getEnvValue(value, env)
                    f.setValue(field.key, value, data)
                }
                if (((!value) || typeof (value) == 'undefined') && field.autoGen == 'true') {
                    value = $.getUUID()
                    f.setValue(field.key, value, data);
                }
                if ((value == null || typeof (value) == 'undefined') && field.required == 'true') {
                    return {'state': 'err', msg: field.fieldLabel + '不能为空。', code: 'required'}
                }

                //对于unique的字段，检查有没有重复
                if (field.unique === 'true' && value) {
                    if (f.isDuplicated(data.id, field.key, value)) {
                        $.log("duplicated,key=" + field.key + ",value=" + value + ",call removeUniqueValue(data.id, field.key, value)");
                        return {'state': 'err', msg: field.fieldLabel + '不能重复。' + value, code: 'duplicated'}
                    }
                }
            }

            var arrFields = flattenedSpecs.details;
            for (var j = 0; j < arrFields.length; j++) {
                var arrFieldInfo = arrFields[j];
                var rows = f.getValue(arrFieldInfo.name, data);
                if (rows) {
                    for (var k = 0; k < rows.length; k++) {
                        var row = rows[k];
                        for (var l = 0; l < arrFieldInfo.fields.length; l++) {
                            var subField = arrFieldInfo.fields[l];
                            var value = row[subField.origKey];
                            if (value == null || typeof value == 'undefined') {
                                value = subField.defaultValue;
                                row[subField.origKey] = value;
                            }
                            if (typeof (value) == 'string' && value.indexOf('$') == 0) {
                                value = f.getEnvValue(value, env)
                                row[subField.origKey] = value;
                            }
                            if (((!value) || typeof (value) == 'undefined') && subField.autoGen == 'true') {
                                value = $.getUUID()
                                row[subField.origKey] = value;
                            }
                            if ((value == null || typeof (value) == 'undefined') && subField.required == 'true') {
                                return {'state': 'err', msg: arrFieldInfo.field.fieldLabel + '.' + subField.fieldLabel + '不能为空。出错的行数为：' + k, code: 'required'}
                            }
                        }
                    }
                }
            }
            return {'state': 'ok'}
        },

        saveUniqueFields: function (data, env) {
            var fields = flattenedSpecs.mainFields
            for (var i = 0; i < fields.length; i++) {
                var field = fields[i]
                //对于unique的字段，检查有没有重复
                if (field.unique === 'true') {
                    var value = f.getValue(field.key, data)
                    if (!value || (typeof (value) == 'string' && value.indexOf('$') == 0)) {
                        value = f.getEnvValue(field.defaultValue, env)
                        if (value) {
                            f.setValue(field.key, value, data)
                        }
                    }
                    if (!value && field.required == 'true') {
                        throw {'state': 'err', msg: field.fieldLabel + '不能为空。', code: 'required'}
                    }

                    if (value && typeof (value) == 'string') {
                        if (f.isDuplicated(data.id, field.key, value)) {
                            throw {'state': 'err', msg: field.fieldLabel + '不能重复。value=' + value, code: 'duplicated'}
                        }

                        //对于没有重复的数据，保存起来
                        f.saveUniqueValue(data.id, field.key, value)
                    }

                }
            }
        },

        removeUniqueFields: function (data, env) {
            var fields = flattenedSpecs.mainFields
            for (var i = 0; i < fields.length; i++) {
                var field = fields[i]
                //对于unique的字段，检查有没有重复
                if (field.unique === 'true') {
                    var value = f.getValue(field.key, data)
                    if (!value || (typeof (value) == 'string' && value.indexOf('$') == 0)) {
                        value = f.getEnvValue(field.defaultValue, env)
                        if (value) {
                            f.setValue(field.key, value, data)
                        }
                    }
                    if (value && typeof (value) == 'string') {
                        f.removeUniqueValue(data.id, field.key, value)
                    }
                }
            }
        },

        addToList: function (data) {
            var key = pigeon.getRKey(data['owl_createTime'], 13)
            pigeon.addToList(f.getAllListName(), key, data.id)

            var t = data['owl_createTime']
            var d = new Date(t)
            var year = d.getFullYear()
            var month = d.getMonth() + 1
            var day = d.getDate()

            var listName = listPrefix + '_' + year + '_' + month + '_' + day

            pigeon.addToList(listName, key, data.id)
            //如果这里有子系统的Id则加入子系统list
            if (data['subplatformId']) {
                var listName = listPrefix + '_' + data['subplatformId']
                pigeon.addToList(listName, key, data.id)
            }

            //如果这里有店铺id,则加入店铺list
            if (data['shopId']) {
                var listName = listPrefix + '_' + data['shopId']
                pigeon.addToList(listName, key, data.id)
            }
        },

        getField: function (fullKey) {
            var matchedField = null;
            f.tranverseFields(formSpecs, function (field, ctx) {
                if (field.key === fullKey) {
                    matchedField = field;
                }
            });
            return matchedField;
        },
        tranverseFields: function (formSpec, callback, ctx) {
            formSpec.fields.forEach(function (field) {
                if (field['_ft'] == 'field') {
                    callback(field, ctx)
                } else if (field['_ft'] == 'subform') {
                    var context = {parentField: field}
                    f.tranverseFields(field, callback, context)
                } else if (field['_ft'] == 'array') {
                    var context = {parentField: field}
                    f.tranverseFields(field, callback, context)
                }
            })
        },

        normalizeValue: function (value, spec) {
            if (value == null) {
                return null
            }
            switch (spec.fieldType) {
                case 'string':
                    if (typeof value == 'number') {
                        value = value + '';
                    }
                    return value + ''
                case 'number':
                    if (isNaN(value)) {
                        return null
                    } else {
                        value = Number(value)
                        return value
                    }
                case 'date':
                    try {
                        if (isNaN(value)) {
                            return value;
                        }

                        return new Date(Number(value)).toISOString();
                    } catch (e) {
                        return null;
                    }

                case 'choice':
                    return value
                default:
                    return value
            }
        },


        getNormalizedDoc: function (data) {
            var meta = spec['#meta'];
            var indexFields = meta.indexFields;


            var obj = JSON.parse(JSON.stringify(data))

            var newObj = {};
            if (indexFields && indexFields.length > 0) {
                for (var i = 0; i < indexFields.length; i++) {
                    var key = indexFields[i];
                    var value = f.getValue(key, obj);
                    if (value) {
                        f.setValue(key, value, newObj);
                    }
                }
                newObj['del'] = obj['del'];
                obj = newObj;
            }


            var fields = formSpecs.fields;
            for (var i = 0; i < fields.length; i++) {
                var field = fields[i];
                if (field['_ft'] === 'subform' || field['_ft'] === 'array') {
                    var meta = field.meta;
                    if (meta.index === 'no') {
                        delete obj[field.key]
                    }
                } else if (field['_ft'] === 'field') {
                    if (field.index === 'no') {
                        delete obj[field.key]
                    }

                }
            }
            f.tranverseFields(formSpecs, function (field, ctx) {
                if (ctx.parentField && ctx.parentField._ft == 'array') {
                    var items = f.getValue(ctx.parentField.key, obj)
                    if (items) {
                        for (var i = 0; i < items.length; i++) {
                            var item = items[i]
                            if (field.index === 'no') {
                                delete item[field.origKey]
                                continue;
                            }
                            var value = item[field.origKey]
                            value = f.normalizeValue(value, field)
                            item[field.origKey] = value
                        }
                    }
                } else {
                    if (indexFields && indexFields.length > 0) {
                        if (indexFields.indexOf(field.key) == -1) {
                            f.deleteValue(field.key, obj);
                        }
                    } else if (field.index === 'no') {
                        f.deleteValue(field.key, obj);
                    } else {
                        var value = f.getValue(field.key, obj)
                        value = f.normalizeValue(value, field)
                        f.setValue(field.key, value, obj)
                    }

                }
            }, {})

            //专门处理一下owl_modifyTime和owl_createTime
            if (obj.owl_modifyTime) {
                obj.owl_modifyTime = new Date(obj.owl_modifyTime).toISOString();
            }
            if (obj.owl_createTime) {
                obj.owl_createTime = new Date(obj.owl_createTime).toISOString();
            }
            return obj
        },

        index: function (doc) {
            var data = f.getNormalizedDoc(doc);
            var m = [];
            m.push(data.m);
            if (data['subplatformId']) {
                m.push(data['subplatformId']);
            }
            if (data['shopId']) {
                m.push(data['shopId'])
            }
            if (data['shop_id']) {
                m.push(data['shop_id'])
            }
            data._m = m;
            var elasticSearchUrl = $.getEnv('elasticSearchUrl')

            var headers = {'Content-Type': 'application/json;charset=utf-8'}
            var elasticSearchUser = $.getEnv('elasticSearchUser')
            var elasticSearchPass = $.getEnv('elasticSearchPass')
            if (elasticSearchUser && elasticSearchPass) {
                var auth = Base64.encode(elasticSearchUser + ':' + elasticSearchPass)
                var basicAuth = 'Basic ' + auth
                headers['Authorization'] = basicAuth
            }
            var searchUrl = elasticSearchUrl + '/@projectCode/_doc/' + data.id
            var sndTxt = JSON.stringify(data)
            var s = HttpUtils.postRaw(searchUrl, sndTxt, headers)
            var r = JSON.parse(s)
            if (!r.result) {
                $.log(data.id + ',index error:' + JSON.stringify(s))
            } else {
                // $.log('index ok...')
            }
        },

        updateParentPath(treeNode) {
            /*工具类，用于更新对应的parentPathIds和pathPathNames*/
            var parentId = treeNode.parent_id;
            var parentPathIds = [];
            var parentPathNames = [];
            var nSteps = 0;
            //获得parentId对应的node
            while (parentId && parentId !== '0') {
                nSteps++;
                if (nSteps > 15) {
                    break;
                }
                var parentNode = f.get(parentId);
                if (parentNode) {
                    parentPathIds.push(parentNode.id);
                    parentPathNames.push(parentNode.name);
                    parentId = parentNode.parent_id;
                } else {
                    break;
                }
            }


            treeNode.parentPathIds = parentPathIds.reverse();
            treeNode.fullPathIds = treeNode.parentPathIds.concat([treeNode.id]);
            treeNode.parentPathNames = parentPathNames.reverse();
            if (treeNode.parentPathNames) {
                var sep = "";
                var meta = spec['#meta'];
                if (meta && meta.pathSep) {
                    sep = meta.pathSep;
                }
                treeNode.fullPathName = treeNode.parentPathNames.join(sep) + sep + treeNode.name;
            }


        },
        add: function (data, env) {
            f.checkData(data);
            env = env || {}
            var ret = f.validate(data, env)
            if (ret.state != 'ok') {
                throw JSON.stringify(ret);
            }

            if (data.__force !== 'T') {
                data['owl_createTime'] = new Date().getTime();
                data['owl_modifyTime'] = new Date().getTime();
            }

            data['_v'] = 0
            data['_t'] = spec['_t']
            try {
                var meta = spec['#meta'];


                EventBusService.fire(spec['_t'] + '_add_before', {data: data, env: env})
                data.id = f.getId(data)
                if (meta.isTree) {
                    f.updateParentPath(data);
                }
                pigeon.lock(f.getLock(data))
                f.saveUniqueFields(data, env)
                pigeon.saveObject(data.id, data)
                f.addToList(data)
                f.index(data)

                if (meta && meta.enableJDBC) {
                    var sqls = insertObject(data);
                    // $.log(sqls.join(";\n"));
                }
                // $.log("add,meta.push=" +meta.push + ",id=" + data.id);
                if (meta && meta.push) {
                    KafkaUtil.send('owlchange', "add", data.id);
                }
                EventBusService.fire(spec['_t'] + '_add_after', {data: data, env: env})
                return data
            } finally {
                pigeon.unlock(f.getLock(data))
            }

        },

        get: function (id, includeDeleted) {
            var meta = spec['#meta'];
            if (meta && meta.push) {
                var cachedObj = appData.getObservableObject(id);
                if (cachedObj) {
                    if (cachedObj.del === 'T' && !includeDeleted) {
                        return null;
                    }
                    return cachedObj;
                }
                var obj = pigeon.getObject(id)
                if (obj) {
                    appData.setObservableObject(id, obj);
                } else {
                    appData.removeObservableObject(id);
                }

                if (obj && obj.del === 'T' && !includeDeleted) {
                    return null;
                }
                return obj;
            } else {
                var obj = pigeon.getObject(id)
                if (obj && obj.del === 'T' && !includeDeleted) {
                    return null;
                }
                return obj;
            }
        },

        getEx: function (id, refresh) {
            if (refresh) {
                var obj = pigeon.getObject(id);

                if (obj) {
                    appData.setObservableObject(id, obj);
                } else {
                    appData.removeObservableObject(id);
                }
                if (obj && obj.del === 'T') {
                    return null;
                }
                return obj;
            } else {
                return f.get(id);
            }
        },

        saveTempData: function (key, data) {
            return pigeon.saveObject(objPrefix + key, data)

        },

        getTempData: function (key) {
            return pigeon.getObject(objPrefix + key)
        },

        getObjects: function (ids) {
            return pigeon.getObjects(ids)
        },

        checkData: function (data, env) {
            var meta = spec['#meta'];
            // 检查数据是否合法
            var checkData = meta.checkData;
            if (checkData) {
                if (checkData.maxSize) {
                    var s = JSON.stringify(data);
                    if (s.length > checkData.maxSize) {
                        throw {code: 'dataTooLarge', msg: '数据太大，超过了' + checkData.maxSize + '字节'}
                    }
                }
            }
        },

        update: function (data, env) {
            f.checkData(data);
            env = env || {}
            var id = data.id

            try {
                var meta = spec['#meta'];
                if (meta.isTree) {
                    f.updateParentPath(data);
                }
                pigeon.lock(f.getLock(data))
                var obj = f.get(data.id, true)
                if (!obj) {
                    throw {msg: '对象不存在!id=' + data.id, code: 'notFound'}
                }
                if (obj._v != data._v) {
                    throw {code: 'concurrentupdate', msg: '对象已经修改过，本次修改被拒绝。old._v=' + obj._v + ',new._v=' + data._v + ", id=" + obj.id}
                }
                var oldObj = JSON.parse(JSON.stringify(obj))
                //深度合并
                //obj = deepMerge(obj,data);//深度合并会引起 编辑出现bug
                obj = data
                obj['_v'] = Number(obj['_v']) + 1
                f.validate(obj, env)
                data._v = obj._v
                if (obj.__force !== 'T') {
                    //如果__force为T，则不更新时间，这代表了强制更新，可以用于数据迁移的场景，希望更新时间保留为旧系统时间
                    obj.owl_modifyTime = new Date().getTime();
                }

                EventBusService.fire(spec['_t'] + '_update_before', {old: oldObj, data: obj, env: env})
                f.addToList(obj)
                f.removeUniqueFields(oldObj, env)
                pigeon.saveObject(id, obj)
                f.saveUniqueFields(data, env)
                f.index(obj)

                if (formSpecs && formSpecs.meta && formSpecs.meta.changeLogEnabled) {
                    var id = oldObj.id;
                    oldObj.id = id + "vvv" + oldObj._v;
                    if (formSpecs.meta.logPigeon && pigeon.getPigeon) {
                        pigeon.getPigeon(formSpecs.meta.logPigeon).saveObject(oldObj.id, oldObj);
                    } else {
                        pigeon.saveObject(oldObj.id, oldObj);
                    }
                    oldObj.id = id;
                }

                var meta = spec['#meta'];
                if (meta && meta.enableJDBC) {
                    var sqls = updateObject(obj);
                    // $.log(sqls.join(";\n"));
                }
                if (meta) {
                    // $.log("updating meta.push=" + meta.push + ", id=" + obj.id);
                }

                if (meta && meta.push) {
                    // $.log("send owlchange," + obj.id)
                    KafkaUtil.send('owlchange', "update", obj.id);
                }
                EventBusService.fire(spec['_t'] + '_update_after', {old: oldObj, data: obj, env: env})
                return data
            } finally {
                pigeon.unlock(f.getLock(data))
            }
        },

        del: function (id, userId, ip, ua) {
            //只做软删除
            var data = f.get(id, true)
            if (!data) {
                throw '对象不存在!id=' + id
            }
            data.id = id;
            data.owl_lastModifiedUserId = userId;
            data.owl_lastModifiedIp = ip;
            data.owl_lastModifiedUserAgent = ua;
            data.owl_modifyTime = new Date().getTime();

            var key = pigeon.getRKey(data['owl_createTime'], 13)
            pigeon.deleteFromList(f.getAllListName(), key, id)
            if (data['subplatformId']) {
                var listName = listPrefix + '_' + data['subplatformId']
                pigeon.deleteFromList(listName, key, id)
            }

            //如果这里有店铺id,则加入店铺list
            if (data['shopId']) {
                var listName = listPrefix + '_' + data['shopId']
                pigeon.deleteFromList(listName, key, id)
            }

            var t = data['owl_createTime']
            var d = new Date(t)
            var year = d.getFullYear()
            var month = d.getMonth() + 1
            var day = d.getDate()

            var listName = listPrefix + '_' + year + '_' + month + '_' + day

            EventBusService.fire(spec['_t'] + '_delete_before', {data: data})
            pigeon.deleteFromList(listName, key, id)

            var deletedList = listPrefix + '_deleted'
            pigeon.addToList(deletedList, key, id)
            data.del = 'T'

            f.index(data)
            pigeon.saveObject(data.id, data)
            f.removeUniqueFields(data, {})
            var meta = spec['#meta'];

            if (meta && meta.enableJDBC) {
                var sqls = deleteObject(data);
                // $.log(sqls.join(";\n"));
            }

            if (meta) {
                // $.log("del meta.push=" + meta.push + ", id=" + data.id);
            }

            if (meta && meta.push) {
                KafkaUtil.send('owlchange', "del", data.id);
            }
            EventBusService.fire(spec['_t'] + '_delete_after', {data: data})
        },
        getList: function (listName, start, limit) {
            if (!listName) {
                return null
            }
            if (!start) {
                start = 0
            }
            if (!limit) {
                limit = 10
            }
            return pigeon.getListObjects(listName, start, limit)
        },

        getExportRunningList: function () {
            return listPrefix + '_exportRunning'
        },

        getExportFinishedList: function () {
            return listPrefix + '_exportFinished'
        },

        addExportTask: function (query, env) {
            var now = new Date().getTime()
            var taskInfo = {
                submitUserId: env.loginId,
                submitTime: now,
                beginTime: null,
                processState: 'notstart',
                appId: "owl_" + spec['_t'],
                params: JSON.stringify(env.params),
                taskId: null
            }
            taskInfo.taskId = '';

            owl_task_infoService.add(taskInfo);
            //这个key如果不多加一个export，会把原有的单据数据覆盖
            // var taskInfoId = objPrefix + '_export_' + pigeon.getId()
            var taskId = JobsService.submitExportTask('@projectCode', 'tasks/export.jsx', {
                query: query,
                env: env,
                taskInfoId: taskInfo.id
            }, now)


            taskInfo = owl_task_infoService.get(taskInfo.id);
            taskInfo.taskId = "" + taskId;
            owl_task_infoService.update(taskInfo);
            return taskInfo.id
        },

        addDocExportTask: function (docId, env) {
            var now = new Date().getTime()
            var taskInfo = {
                // loginUser: f.getEnvValue('$loginUser', env),
                loginUserId: env.loginUserId,
                submitTime: now,
                startTime: 0,
                processState: 'processing',
                percent: 0,
                total: 1,
                _v: 0,
            }
            //这个key如果不多加一个export，会把原有的单据数据覆盖
            var taskInfoId = objPrefix + '_export_' + pigeon.getId()
            var taskId = JobsService.submitExportTask('@projectCode', 'tasks/exportDoc.jsx', {
                id: docId,
                env: env,
                taskInfoId: taskInfoId,
            }, now)
            taskInfo.taskId = '' + taskId
            taskInfo.id = taskInfoId
            var key = pigeon.getRKey(taskInfo.submitTime, 13)
            pigeon.addToList(f.getExportRunningList(), key, taskInfoId)
            pigeon.saveObject(taskInfoId, taskInfo)

            return taskInfoId
        },

        addZipExportTask: function (env) {
            var now = new Date().getTime()
            var taskInfo = {
                // loginUser: f.getEnvValue('$loginUser', env),
                loginUserId: env.loginUserId,
                submitTime: now,
                startTime: 0,
                processState: 'processing',
                percent: 0,
                total: 0,
                _v: 0,
            }
            //这个key如果不多加一个export，会把原有的单据数据覆盖
            var taskInfoId = objPrefix + '_export_' + pigeon.getId()
            var taskId = JobsService.submitExportTask('@projectCode', 'tasks/exportZip.jsx', {
                env: env,
                taskInfoId: taskInfoId,
            }, now)
            taskInfo.taskId = '' + taskId
            taskInfo.id = taskInfoId
            var key = pigeon.getRKey(taskInfo.submitTime, 13)
            pigeon.addToList(f.getExportRunningList(), key, taskInfoId)
            pigeon.saveObject(taskInfoId, taskInfo)
            return taskInfoId
        },

        getExportTaskInfo: function (taskInfoId) {
            return owl_task_infoService.get(taskInfoId);
        },

        updateExportTaskInfo: function (taskInfoId, taskInfo) {
            return owl_task_infoService.update(taskInfo);
        },


        reindexAll: function () {
            var listName = f.getAllListName()
            var count = pigeon.getListSize(listName)
            $.log("reindexAll " + listName);
            var pos = 0
            while (count > 0) {
                var indexCount = 2000
                if (indexCount > count) {
                    indexCount = count
                }
                var objs = pigeon.getListObjects(listName, pos, indexCount)
                objs.forEach(function (data) {
                    try {
                        f.index(data)
                    } catch (e) {
                        $.log('重建索引异常:' + data.id)
                    }
                })
                pos += indexCount
                count -= indexCount
            }
        },

        getAllSize: function () {
            var listName = f.getAllListName();
            var count = pigeon.getListSize(listName);
            return count;
        },

        getAllObjects: function () {
            var result = [];
            var listName = f.getAllListName()
            var count = pigeon.getListSize(listName)
            var pos = 0
            while (count > 0) {
                var indexCount = 2000
                if (indexCount > count) {
                    indexCount = count
                }
                var objs = pigeon.getListObjects(listName, pos, indexCount)
                result = result.concat(objs);
                pos += indexCount
                count -= indexCount
            }
            return result;
        },

        getAllObjectsEx: function (from, count) {
            var listName = f.getAllListName()
            var allsize = pigeon.getListSize(listName)
            var pos = from
            if (count + pos > allsize) {
                count = allsize - pos;
            }
            var objs = pigeon.getListObjects(listName, pos, count)
            return objs;
        },

        createQuery: function (searchArgs) {
            var filters = [];
            for (var k in searchArgs) {
                var v = searchArgs[k];
                if (typeof v === 'object' && v.type === 'or') {
                    if (Array.isArray(v.args)) {
                        var shouldClauses = [];
                        v.args.forEach(function (q) {
                            var qFilters = f.createQuery(q);
                            if (qFilters.length > 0) {
                                shouldClauses.push({
                                    "bool": {
                                        filters: qFilters
                                    }
                                })
                            }
                        });

                        filters.push({bool: {should: shouldClauses}});
                    } else if (v.args && typeof (v.args) == 'object') {
                        var shouldClauses = [];
                        var qFilters = f.createQuery(v.args);
                        shouldClauses.push({bool: {filters: qFilters}});
                        filters.push({bool: {should: shouldClauses}});
                    }

                } else if (typeof (v) == 'object' && Array.isArray(v)) {
                    var range = {}
                    range[k] = {
                        'gte': v[0],
                        'lte': v[1]
                    }
                    filters.push({range: range});
                } else if (typeof (v) == 'object' && v.type === 'terms') {
                    var terms = {};
                    if (v.values) {
                        terms[k + ".keyword"] = v.values;
                        filters.push({terms: terms});
                    }
                } else {
                    var term = {};
                    if (typeof v == 'string') {
                        if (v) {
                            term[k + ".keyword"] = trim('' + v)
                            filters.push({term: term})
                        }
                    } else if (typeof v == 'number') {
                        term[k] = trim('' + v)
                        filters.push({term: term})
                    }
                }
            }
            return filters;
        },

        // 构建通配查询（将 SQL 风格的 % 转为 ES 的 *）
        buildWildcardQuery: function (k, v) {
            // v 预期包含 % 作为通配符标记，转换为 ES 的 *
            var value = ('' + v).replace(/%/g, '*');
            var field = k + ".keyword";
            var wildcard = {};
            wildcard[field] = {
                value: value,
                // 在 ES 7.10+ 支持不区分大小写
                case_insensitive: true
            };
            return {wildcard: wildcard};
        },

        buildQuery: function (mfilters, searchArgs, keyword, isRecycleBin, meta_fields) {
            if (!meta_fields) {
                var meta = spec["#meta"];
                if (meta) {
                    meta_fields = spec["#meta"]["keyword_fields"];
                }
            }
            delete searchArgs.keyword;
            var filters = [];
            var must_not = [{
                term: {
                    "del.keyword": 'T'
                }
            }];

            if (isRecycleBin) {
                must_not = [];
                filters.push({
                    term: {
                        "del.keyword": 'T'
                    }
                });
            }
            for (var k in searchArgs) {

                var v = searchArgs[k];

                var isNotClause = false;
                if (k.indexOf("!") === 0) {
                    isNotClause = true;
                    k = k.substring(1);
                }
                if (typeof v === 'object' && v.type === 'or') {
                    var shouldClauses = [];
                    if (Array.isArray(v.args)) {
                        v.args.forEach(function (q) {
                            var shouldQueries = f.createQuery(q);
                            if (shouldQueries.length > 0) {
                                shouldClauses.push({bool: {filter: shouldQueries}});
                            }

                        });
                        if (isNotClause) {
                            must_not.push({bool: {should: shouldClauses}});
                        } else {
                            filters.push({bool: {should: shouldClauses}});
                        }

                    } else {
                        //do noting,所以 or 语句不支持 单个对象，必须带一个 args 数组
                    }
                } else if (typeof (v) == 'object' && Array.isArray(v)) {
                    var range = {}
                    if (v.length == 2) {
                        range[k] = {
                            'gte': v[0],
                            'lte': v[1]
                        }
                    } else if (v.length == 1) {
                        range[k] = {
                            'gte': v[0]
                        }
                    } else if (v.length == 3) {
                        range[k] = {
                            'lte': v[0]
                        }
                    } else if (v.length == 4) {
                        range[k] = {
                            'gt': v[0],
                            'lt': v[1]
                        }
                    } else if (v.length == 5) {
                        range[k] = {
                            'gt': v[0]
                        }
                    } else if (v.length == 6) {
                        range[k] = {
                            'lt': v[0]
                        }
                    }

                    if (isNotClause) {
                        must_not.push({range: range});
                    } else {
                        filters.push({range: range});
                    }
                } else if (typeof (v) == 'object' && v.type === 'terms') {
                    var terms = {};
                    if (v.values) {
                        terms[k + ".keyword"] = v.values;
                        if (isNotClause) {
                            must_not.push({terms: terms});
                        } else {
                            filters.push({terms: terms});
                        }
                    }
                } else if (typeof (v) === 'object' && v.type === 'exists') {
                    if (isNotClause) {
                        must_not.push({exists: {field: k}});
                    } else {
                        filters.push({exists: {field: k}});
                    }
                } else {
                    var term = {};
                    if (typeof v == 'string') {
                        if (v) {
                            term[k + ".keyword"] = trim('' + v)
                            if (isNotClause) {
                                if (v.indexOf('%') > -1) {
                                    must_not.push(f.buildWildcardQuery(k, v));
                                } else {
                                    must_not.push({term: term});
                                }
                            } else {
                                if (v.indexOf('%') > -1) {
                                    filters.push(f.buildWildcardQuery(k, v));
                                } else {
                                    filters.push({term: term});
                                }
                            }

                        }
                    } else if (typeof v == 'number') {
                        term[k] = trim('' + v)
                        if (isNotClause) {
                            must_not.push({term: term});
                        } else {
                            filters.push({term: term});
                        }
                    }
                }
            }
            if (mfilters && Array.isArray(mfilters) && mfilters.length > 0) {
                filters = filters.concat(mfilters);
            }

            //getKeyword query
            var keywordQuery = "";
            if (keyword && trim(keyword).length > 0) {
                keywordQuery = "\"" + trim(keyword) + "\""
            } else {
                keywordQuery = "*"
            }
            var keyword_fields = null;
            if (meta_fields && Array.isArray(meta_fields) && meta_fields.length > 0) {
                keyword_fields = meta_fields;
            }
            var queryStringQuery = {
                "query_string": {
                    "query": keywordQuery
                }
            }

            if (keyword_fields && keyword_fields.length > 0) {
                queryStringQuery = {
                    "query_string": {
                        "query": keywordQuery,
                        "fields": keyword_fields,
                    }
                }
            }
            var query = {
                "query": {
                    "bool": {
                        "must": queryStringQuery,
                        "must_not": must_not,
                        "filter": filters
                    }
                }
            }
            return query;
        },
        count: function (mfilters, searchArgs, keyword, dataSource, isRecycleBin, meta_fields) {
            var query = f.buildQuery(mfilters, searchArgs, keyword, isRecycleBin, meta_fields);
            var elasticSearchUrl = $.getEnv("elasticSearchUrl");

            var headers = {"Content-Type": "application/json;charset=utf-8"};
            var elasticSearchUser = $.getEnv("elasticSearchUser");
            var elasticSearchPass = $.getEnv("elasticSearchPass");
            if (elasticSearchUser && elasticSearchPass) {
                var auth = Base64.encode(elasticSearchUser + ":" + elasticSearchPass);
                var basicAuth = "Basic " + auth;
                headers["Authorization"] = basicAuth;
            }
            var searchUrl = elasticSearchUrl + "/@projectCode/_count";
            if (dataSource) {
                searchUrl = elasticSearchUrl + "/" + dataSource + "/_count";
            }

            var sndTxt = JSON.stringify(query);
            var s = HttpUtils.postRaw(searchUrl, sndTxt, headers);
            var result = JSON.parse(s);
            if (result.count) {
                return result.count;
            }
            return 0;
        },
        executeSearchQuery: function (query, dataSource) {
            var elasticSearchUrl = $.getEnv("elasticSearchUrl");

            var headers = {"Content-Type": "application/json;charset=utf-8"};
            var elasticSearchUser = $.getEnv("elasticSearchUser");
            var elasticSearchPass = $.getEnv("elasticSearchPass");
            if (elasticSearchUser && elasticSearchPass) {
                var auth = Base64.encode(elasticSearchUser + ":" + elasticSearchPass);
                var basicAuth = "Basic " + auth;
                headers["Authorization"] = basicAuth;
            }
            var searchUrl = elasticSearchUrl + "/@projectCode/_search";
            if (dataSource) {
                searchUrl = elasticSearchUrl + "/" + dataSource + "/_search";
            }

            var sndTxt = JSON.stringify(query);
            return HttpUtils.postRaw(searchUrl, sndTxt, headers);
        },

        search: function (mfilters, searchArgs, keyword, from, pageSize, sort, dataSource, isRecycleBin, meta_fields) {
            var query = f.buildQuery(mfilters, searchArgs, keyword, isRecycleBin, meta_fields);
            var effectiveSort = [{owl_createTime: {order: "desc"}}];
            if (sort) {
                effectiveSort = sort;
            }
            query.from = from;
            query.size = pageSize;
            query.sort = effectiveSort;

            var elasticSearchUrl = $.getEnv("elasticSearchUrl");

            var headers = {"Content-Type": "application/json;charset=utf-8"};
            var elasticSearchUser = $.getEnv("elasticSearchUser");
            var elasticSearchPass = $.getEnv("elasticSearchPass");
            if (elasticSearchUser && elasticSearchPass) {
                var auth = Base64.encode(elasticSearchUser + ":" + elasticSearchPass);
                var basicAuth = "Basic " + auth;
                headers["Authorization"] = basicAuth;
            }
            var searchUrl = elasticSearchUrl + "/@projectCode/_search";
            if (dataSource) {
                searchUrl = elasticSearchUrl + "/" + dataSource + "/_search";
            }

            var sndTxt = JSON.stringify(query);
            var s = HttpUtils.postRaw(searchUrl, sndTxt, headers);
            var result = JSON.parse(s);

            if (!result.hits || !result.hits.hits) {
                $.log("error search:sndQuery=" + sndTxt + "------\nresponse:\n" + s);
                var ret = {
                    state: 'err',
                    list: [],
                    total: {value: 0}
                }
                return ret;
            }

            var hits = result.hits.hits;
            var total = result.hits.total;

            var objs = hits.map(function (hit) {
                return hit._source
            });


            var ret = {
                state: 'ok',
                list: objs,
                total: total
            }

            return ret;

        },
        getHistory: function (objId, from, size) {
            var objs = [];
            var obj = f.get(objId, true);
            var currentVersion = obj._v;

            var logPigeon = pigeon;
            if (formSpecs.meta.logPigeon && pigeon.getPigeon) {
                logPigeon = pigeon.getPigeon(formSpecs.meta.logPigeon);
            }

            for (var i = 0; i < size; i++) {
                var ver = currentVersion - i - from;
                if (ver < 0) {
                    break;
                }
                var id = objId + "vvv" + ver;
                var vobj = logPigeon.getObject(id)
                if (vobj) {
                    objs.push(vobj);
                }
            }
            return objs;

        }
    }
    return f;
})($S);