
/**
 * 遍历映射配置，构建映射关系
 * @param dataMapping   映射配置
 * @param rawData   源数据：&nbsp;&nbsp;&nbsp;&nbsp;若有此输入，将认为当前是更新动作、&nbsp;&nbsp;无此输入则认为当前是插入动作
 */
function mapping(dataMapping, rawData) {
    window.dataSet = {};
    window.dataSet.tempId = 950366;
    debugger;
    if(!rawData) {
        window.dataSet.isInsert = true;
        var rawData = {};
    }else {
        window.dataSet.isInsert = false;
    }
    let mapper = {}, returnData = {};
    delete dataMapping.mapper;
    let entitySet =  Object.entries(dataMapping);
    tryForEach(entitySet,(entity) => {
        let beanName = entity[0], propertyNames = entity[1], entityData = rawData[beanName];
        if(!isExist(rawData, beanName)) { throw {}; }
        if(propertyNames instanceof  Array) {
            // Form 时执行此逻辑
            tryForEach(propertyNames,(item) => {
                if(!entityData) { entityData = {}; }
                if(typeof item === "string") {
                    if(!isExist(entityData, item, beanName)) { throw {}; }
                    associate(entityData, item, item, beanName,mapper, returnData);
                    return;
                }  else if(item instanceof Object) {
                    let entry = Object.entries(item)[0];
                    let property = entry[0], alias = entry[1];
                    if(!isExist(entityData, property, beanName)) { throw {}; }
                    associate(entityData, property, alias, beanName,mapper, returnData)
                } else {
                    console.error('The configuration of "'+item+'" in "'+beanName+'" is not recognized,  and the required,' +
                        ' parameters are ( "propertyName" or "{ propertyName: alias }" ).');
                    throw {};
                }
            });
        } else if(propertyNames instanceof Object) {
            // Grid 时执行此逻辑
            if(window.dataSet.isInsert) {
                rawData[beanName] = {};
                rawData[beanName].raw = {};
                rawData[beanName].compares = {};
                rawData[beanName].ids = [];
                return;
            }
            let listMap = {}, ids = [], rowId = ''; //tryForEach 变量作用域注意
            entityData.forEach(rowData => {
                debugger;
                rowId = rowData[propertyNames.id];
                listMap[rowId] = rowData;
                ids.push(rowId);
                tryForEach(propertyNames.columns, (property) => {
                    if(!mapper[property]) {
                        mapper[property] = {};
                    }
                    let data = {};
                    data['listeningKey'] = rowId;
                    mapper[property][beanName] = data;
                });
            })
            rawData[beanName] = {};
            rawData[beanName].raw = entityData;
            rawData[beanName].compares = listMap;
            rawData[beanName].ids = ids;
            debugger;
            return;
        }
    });
    dataMapping.mapper = mapper;
    // ---------------- 暂不处理无法执行到此处的情况 ----------------
    // 这里写死 映射对象为：‘dataMapping’
    window.dataMapping = dataMapping;
    // 这里写死 数据集：‘dataSet: { rawData: {}, newData: {} }’
    window.dataSet.rawData = rawData;
    window.dataSet.newData = JSON.parse(JSON.stringify(dataSet.rawData));
    return returnData;
}

/**
 *  映射关系 => elementName(alias): { objName: property }
 * @param data  数据源
 * @param property  属性
 * @param alias 别名
 * @param objName   对象名
 * @param mapper    返回值: 映射关系
 * @param returnData    返回值: 页面显示数据集
 */
function associate(data, property, alias, objName, mapper, returnData) {
    mapper[alias] = {};
    mapper[alias][objName] = property;
    returnData[alias] = data[property];
}

function testTryForEach() {
    // 直接修改原型属性， 会导致当前UI grid 组件 input 不可用，未知原因。
    Array.prototype.tryForEach = function (callback) {
        try {
            this.forEach(callback);
        } catch { }
    }
}

const tryForEach = function(arrayObj, callback) {
    try {
        arrayObj.forEach(callback);
    } catch { }
}

/**
 * 检测数据中是否存在对应属性
 * @param obj   需要检测数据
 * @param property  需要检测的属性
 * @param objName   检测的对象名
 * @returns {boolean}
 */
function isExist(obj, property, objName) {
    if(window.dataSet.isInsert) {
        obj[property] = {};
        return true;
    } else {
        if(!objName) { objName = 'rawData'; }
        // 这里注意，后端返回的类名是否与映射配置一致
        if(obj[property] === undefined) {
            console.error('No data for name of ' + property + ' in ' + objName + ' !');
            return false;
        }
    }
    return true;
}

/**
 *  检查是否存在此 Grid 的映射 返回匹配到的映射
 * @param str   Grid Id
 * @returns {{}}
 */
function checkArrayMapper(str) {
    let retrunData = {};
    Object.entries(window.dataMapping).forEach(entry => {
        let beanName = entry[0], property = entry[1];
        if(str.indexOf(beanName) >= 0) {
            retrunData['beanName'] = beanName;
            retrunData['property'] = property;
        }
    });
    return retrunData;
}

/**
 *  若需要监听 Grid 数据，必须在此调用组件初始化，此处获取元素 ID
 * @param elId
 * @param options
 * @returns {*}
 */
function listeningGrid(elId, options) {
    tryForEach(Object.entries(window.dataMapping), entry => {
        let beanName = entry[0], property = entry[1];
        if(elId.indexOf(beanName) >= 0) {
            let gridId = property.id;
            if(!gridId) {
                console.error("若需要监听 GridData ,必须在映射配置指定 ID ！请检查。");
                throw {};
            }
            options.columns.unshift({ name: gridId, hide: true });
        }
    });
    debugger;
    let data = {};
    if(!options.data){
        console.error('listeningGrid 必须传入 Data ！请检查');
        data.Rows = '';
    } else { data.Rows = options.data.raw; }
    options.data = data;
    // ------------------ 编辑框打开前记录当前行 ------------------
    const originalOnBeforeEdit = options.onBeforeEdit;
    options.onBeforeEdit = function(editParm, gg){
        let domIds = editParm.column.__domid.split('|'), recordId = editParm.record.__id, colName = editParm.column.name;
        let parentDomId = domIds[0]+'|'+2+'|'+recordId; // +'|'+domIds[2];
        let rowDOM;
        if(!(rowDOM = document.getElementById(parentDomId))) {
            parentDomId = domIds[0]+'|'+1+'|'+recordId;
            rowDOM = document.getElementById(parentDomId)
        }
        let mapper = window.dataMapping.mapper;
        let rowId = rowDOM.childNodes[0].childNodes[0].innerText;
        let mapperEntry =  mapper[colName];
        let currentMapper = checkArrayMapper(domIds[0]);
        debugger;
        if(currentMapper.hasOwnProperty("beanName")) {
            if(!mapperEntry[currentMapper.beanName]) {
                console.error("dataMapping 中，无法找到该 DOMId 的映射。请检查！");
            } else {
                //mapperEntry[currentMapper.beanName]['rowId'] = parentDomId;
                mapperEntry[currentMapper.beanName]['rowIndex'] = editParm.rowindex;
            }
        }
        originalOnBeforeEdit.call(this, editParm, '11');
    }
    const toolbar = options.toolbar;
    if(toolbar) {
        toolbar.items
        tryForEach(toolbar.items, but => {
            if(but.id === "menuAdd") {
                const originalOnClick = but.click;
                but.click = function() {
                    let mappingEntry = Object.entries(window.dataMapping);
                    tryForEach(mappingEntry, entry => {
                        let beanName = entry[0];
                        if(elId.indexOf(beanName) >= 0) {
                            let idProperty = entry[1].id;
                            if(!idProperty) {
                                console.error("若需要监听 GridData ,必须在映射配置指定 ID ！请检查。");
                                throw {};
                            }
                            let mapper = window.dataMapping.mapper;
                            let rowId = window.dataSet.tempId += 1
                            let data = {}, mapperData = {};

                            // ------------- 设置 mapper -------------
                            debugger;
                            tryForEach(entry[1].columns, property => {
                                mapperData['beanName'] = beanName;
                                if(!mapper[property]) {
                                    mapper[property] = {};
                                }
                                mapper[property][rowId] = mapperData;
                                data[property] = '';
                            });
                            data[idProperty] = rowId;
                            // ------------- 设置 compares -------------
                            if(!window.dataSet.newData[beanName]) {
                                window.dataSet.newData[beanName] = {};
                            }
                            if(!window.dataSet.newData[beanName].compares){
                                window.dataSet.newData[beanName].compares = {};
                            }
                            window.dataSet.newData[beanName].compares[rowId] = data;

                            // ------------- 设置 ids -------------
                            if(!window.dataSet.newData[beanName].ids) {
                                window.dataSet.newData[beanName].ids = [];
                            }
                            window.dataSet.newData[beanName].ids.push(rowId);
                            UICtrl.addGridRow(gridManager, data);
                        }
                    })
                    originalOnClick.call(this, data);
                    debugger;
                }

            }
        })
        debugger;
    }
    addObserver(elId);
    var gridManager = UICtrl.grid(elId,  options);
    return gridManager;

}

/**
 * 监听 input 管理页面显示的 Data。
 */
function inputEventListener() {
    const inputElements = document.querySelectorAll('input');
    inputElements.forEach(item => {
        item.addEventListener('input', (event) => {
            listenerContent(event);
        });
    });
}

/**
 * 监听器动作： 根据 保存的映射关系，将页面元素值填入到 newData
 * @param event
 */
function  listenerContent(event) {
    const property = event.target.getAttribute('name'), editId = event.target.id;
    const beanMapping = window.dataMapping.mapper[property];
    debugger;
    if(!beanMapping) {
        console.error("The configuration was not found in the 'dataMapping' : '"+property+"'")
    }
    let control = getControlType(beanMapping);
    // Grid 时执行此逻辑
    if(control === 'grid') {
        let inputIds = editId.split('_');
        debugger;
        tryForEach(Object.entries(beanMapping), mapperEntry => {
            let mapperVal = mapperEntry[1], mapperKey = mapperEntry[0];
            if(mapperVal.rowIndex === inputIds[2] * 1) {
                window.dataSet.newData[mapperVal.beanName].compares[mapperKey][event.target.getAttribute('name')] = event.target.value;
                console.log('Input value changed:', event.target.value);
            } else {
                console.error('当前输入框 '+ event.target.id+ '无法与数据行'+  mapperKey + '：' + mapperVal.rowIndex + '匹配！');
            }
        })
        return;
    }
    // Form 时执行此逻辑
    if(control === 'form') {
        let mappings = Object.entries(beanMapping);
        tryForEach(mappings, (entry) => {
            let beanName = entry[0];
            let property = entry[1];
            window.dataSet.newData[beanName][property] = event.target.value;
            console.log('Input value changed:', event.target.value);
        })
    }
}

function getControlType(beanMapping) {
    let control;
    //  这里根据 grid 与 form 的 mapper 数据结构差异判定。 需要优化
    if(Object.entries(beanMapping)[0][1]['listeningKey']) {
        control = 'grid';
    } else {
        control = 'form';
    }
    return control;
}

/**
 * 添加 DOM 监听
 * @param eleId 监听的 root 元素 ID
 */
function addObserver(eleId) {
    observer.observe(document.querySelector(eleId), mutationConfig);
}

/**
 * 监听 DOM ，为编辑框添加 Input 监听
 * @type {MutationObserver}
 */
const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
        if(mutation.type === 'childList') {
            let addNodes = mutation.addedNodes;
            if(addNodes.length === 1 && addNodes[0].nodeName === 'DIV') {
                let childNodes = addNodes[0].childNodes;
                if(childNodes.length === 1 && childNodes[0].nodeName === 'INPUT') {
                    let node = childNodes[0];
                    node.addEventListener('input', gridEditor)
                }
            }
            let removeNodes = mutation.removedNodes;
            if(removeNodes.length === 1 && removeNodes[0].nodeName === 'INPUT') {
                let node = removeNodes[0];
                node.removeEventListener('input', gridEditor);
            }
        }
    })
})

const gridEditor = function(event) {
    listenerContent(event);
}
// console.log('属性变化节点 ： ' + mutation.target);
// console.log('变化属性名 ： ' + mutation.attributeName);
// console.log('变化属性值 ： ' + mutation.target.value);
const mutationConfig = { attributes: true, childList: true, subtree: true}

function checkUpdata() {
    let dataSet = window.dataSet;
    let rawData = dataSet.rawData, newData = dataSet.newData;
    window.dataSet.upData = {};
    let rawEntry = Object.entries(rawData);
    tryForEach(rawEntry, entry => {
        let beanName = entry[0];
        let mapping = window.dataMapping[beanName], raw = rawData[beanName], now = newData[beanName];
        if(mapping instanceof Array) {
            // Form 时 执行此逻辑
            if(!(getHashCode(JSON.stringify(raw)) === getHashCode(JSON.stringify(now)))) {
                dataSet.upData[beanName] = newData[beanName];
            }
            return;
        } else if (mapping instanceof Object) {
            // Grid 时 执行此逻辑
            let rawCompares = raw.compares, nowCompares = now.compares;
            if(!(getHashCode(JSON.stringify(rawCompares)) === getHashCode(JSON.stringify(nowCompares)))) {
                let rawIds = raw.ids, nowIds = now.ids;
                dataSet.upData[beanName] = {};
                dataSet.upData[beanName].del = [];
                let delIds = rawIds.filter(x => !nowIds.includes(x));
                tryForEach(delIds, id => {
                    dataSet.upData[beanName].del.push(rawCompares[id]);
                });
                dataSet.upData[beanName].add = []
                let addIds = nowIds.filter(x => !rawIds.includes(x));
                tryForEach(addIds, id => {
                    dataSet.upData[beanName].add.push(nowCompares[id]);
                });
                dataSet.upData[beanName].up = []
                let nowEntry = Object.entries(nowCompares);
                tryForEach(nowEntry, row => {
                    let key = row[0], rowNow = row[1];
                    if(rawCompares[key]) {
                        if(!(getHashCode(JSON.stringify(rawCompares[key])) === getHashCode(JSON.stringify(rowNow)))) {
                            dataSet.upData[beanName].up.push(rowNow);
                        }
                    }
                });
            }
            return ;
        }
    })
    return dataSet.upData;
}

/**
 * 用于判断字符串是否更改
 * @param str
 * @param caseSensitive 是否区分大小写 Default: true
 * @returns {number}
 */
function getHashCode (str, caseSensitive) {
    if(caseSensitive === undefined) { caseSensitive = true;}
    if(!caseSensitive) {
        str = str.toLowerCase();
    }
    if (str.length === 0) return 0;
    var len = str.length;
    var hash = 1315423911, i, ch;
    for (i = len - 1; i >= 0; i--) {
        ch = str.charCodeAt(i);
        hash = (hash << 5) + ch + (hash >> 2);
    }
    var hash2 = hash ^ (hash << 7) ^ (hash >> 6);
    var hash3 = hash2 ^ (hash2 << 1) ^ (hash2 >> 13);
    if (len < 50) {
        var hash4 = this.getHashCode(str + str + str, caseSensitive);
        return (hash ^ hash2 ^ hash3  ^ hash4) & 0x7FFFFFFF;
    } else {
        return (hash ^ hash2 ^ hash3 >> 3 ) & 0x7FFFFFFF;
    }
}
