let JV = require('./jpc_value_define');
let JpcBand = require('./jpc_band');
let JpcFlowTab = require('./jpc_flow_tab');
let JpcBillTab = require('./jpc_bill_tab');
let JpcCrossTab = require('./jpc_cross_tab');
let JpcField = require('./jpc_field');
let JpcParam = require('./jpc_param');
let JpcFunc = require('./jpc_function');
let JpcData = require('./jpc_data');
let JpcCommonHelper = require('./helper/jpc_helper_common');
let $JE = require('./jpc_rte'); //Important: for self-define function execution purpose

let JpcExSrv = function(){};
JpcExSrv.prototype.createNew = function(){
    function private_buildDftItems(rptTpl, dftCollection, propArray, nodeName) {
        let rst = {};
        if (dftCollection) {
            for (let i = 0; i < dftCollection.length; i++) {
                let item = {};
                for (let j = 0; j < propArray.length; j++) {
                    item[propArray[j]] = dftCollection[i][propArray[j]];
                }
                rst[dftCollection[i][JV.PROP_ID]] = item;
            }
            if (rptTpl && rptTpl[nodeName] && rptTpl[nodeName].length > 0) {
                for (let i = 0; i < rptTpl[nodeName].length; i++) {
                    let rptDftItem = rptTpl[nodeName][i];
                    if (rst[rptDftItem[JV.PROP_ID]] === undefined) {
                        let item = {};
                        for (let j = 0; j < propArray.length; j++) {
                            item[propArray[j]] = rptDftItem[propArray[j]];
                        }
                        rst[rptDftItem[JV.PROP_ID]] = item;
                    }
                }
            }
        }
        return rst;
    }
    function private_buildDftControls(rptTpl, dftControlCollection) {
        return private_buildDftItems(rptTpl,dftControlCollection, JV.CONTROL_PROPS, JV.NODE_CONTROL_COLLECTION);
    }
    function private_buildDftFonts(rptTpl, dftFontCollection) {
        return private_buildDftItems(rptTpl,dftFontCollection, JV.FONT_PROPS, JV.NODE_FONT_COLLECTION);
    }
    function private_buildDftStyles(rptTpl, dftStyleCollection) {
        let rst = {};
        function private_CopyBorder(destItem, srcItem) {
            destItem[JV.PROP_LINE_WEIGHT] = srcItem[JV.PROP_LINE_WEIGHT];
            destItem[JV.PROP_DASH_STYLE] = srcItem[JV.PROP_DASH_STYLE];
            destItem[JV.PROP_COLOR] = srcItem[JV.PROP_COLOR];
        }
        if (dftStyleCollection) {
            for (let i = 0; i < dftStyleCollection.length; i++) {
                let item = {};
                if (dftStyleCollection[i][JV.PROP_BORDER_STYLE] && dftStyleCollection[i][JV.PROP_BORDER_STYLE].length > 0) {
                    for (let j = 0; j < dftStyleCollection[i][JV.PROP_BORDER_STYLE].length; j++) {
                        let borderItem = {};
                        private_CopyBorder(borderItem, dftStyleCollection[i][JV.PROP_BORDER_STYLE][j]);
                        item[dftStyleCollection[i][JV.PROP_BORDER_STYLE][j][JV.PROP_POSITION]] = borderItem;
                    }
                }
                rst[dftStyleCollection[i][JV.PROP_ID]] = item;
            }
            if (rptTpl && rptTpl[JV.NODE_STYLE_COLLECTION] && rptTpl[JV.NODE_STYLE_COLLECTION].length > 0) {
                for (let i = 0; i < rptTpl[JV.NODE_STYLE_COLLECTION].length; i++) {
                    let rptDftItem = rptTpl[JV.NODE_STYLE_COLLECTION][i];
                    if (rst[rptDftItem[JV.PROP_ID]] === undefined) {
                        if (rptDftItem[JV.PROP_ID].indexOf('_AutoHeightMerge_Top') > 0) {
                            let key = rptDftItem[JV.PROP_ID].substr(0, rptDftItem[JV.PROP_ID].indexOf('_AutoHeightMerge_Top'));
                            if (rst[key]) {
                                let item = {};
                                if (rst[key][JV.PROP_LEFT]) {
                                    item[JV.PROP_LEFT] = {};
                                    private_CopyBorder(item[JV.PROP_LEFT], rst[key][JV.PROP_LEFT]);
                                }
                                if (rst[key][JV.PROP_RIGHT]) {
                                    item[JV.PROP_RIGHT] = {};
                                    private_CopyBorder(item[JV.PROP_RIGHT], rst[key][JV.PROP_RIGHT]);
                                }
                                if (rst[key][JV.PROP_TOP]) {
                                    item[JV.PROP_TOP] = {};
                                    private_CopyBorder(item[JV.PROP_TOP], rst[key][JV.PROP_TOP]);
                                }
                                rst[rptDftItem[JV.PROP_ID]] = item;
                            }
                        } else if (rptDftItem[JV.PROP_ID].indexOf('_AutoHeightMerge_Middle') > 0) {
                            let key = rptDftItem[JV.PROP_ID].substr(0, rptDftItem[JV.PROP_ID].indexOf('_AutoHeightMerge_Middle'));
                            if (rst[key]) {
                                let item = {};
                                if (rst[key][JV.PROP_LEFT]) {
                                    item[JV.PROP_LEFT] = {};
                                    private_CopyBorder(item[JV.PROP_LEFT], rst[key][JV.PROP_LEFT]);
                                }
                                if (rst[key][JV.PROP_RIGHT]) {
                                    item[JV.PROP_RIGHT] = {};
                                    private_CopyBorder(item[JV.PROP_RIGHT], rst[key][JV.PROP_RIGHT]);
                                }
                                rst[rptDftItem[JV.PROP_ID]] = item;
                            }
                        } else if (rptDftItem[JV.PROP_ID].indexOf('_AutoHeightMerge_Bottom') > 0) {
                            let key = rptDftItem[JV.PROP_ID].substr(0, rptDftItem[JV.PROP_ID].indexOf('_AutoHeightMerge_Bottom'));
                            if (rst[key]) {
                                let item = {};
                                if (rst[key][JV.PROP_LEFT]) {
                                    item[JV.PROP_LEFT] = {};
                                    private_CopyBorder(item[JV.PROP_LEFT], rst[key][JV.PROP_LEFT]);
                                }
                                if (rst[key][JV.PROP_RIGHT]) {
                                    item[JV.PROP_RIGHT] = {};
                                    private_CopyBorder(item[JV.PROP_RIGHT], rst[key][JV.PROP_RIGHT]);
                                }
                                if (rst[key][JV.PROP_BOTTOM]) {
                                    item[JV.PROP_BOTTOM] = {};
                                    private_CopyBorder(item[JV.PROP_BOTTOM], rst[key][JV.PROP_BOTTOM]);
                                }
                                rst[rptDftItem[JV.PROP_ID]] = item;
                            }
                        } else {
                            let item = {};
                            for (let j = 0; j < rptDftItem[JV.PROP_BORDER_STYLE].length; j++) {
                                let borderItem = {};
                                private_CopyBorder(borderItem, rptDftItem[JV.PROP_BORDER_STYLE][j]);
                                item[rptDftItem[JV.PROP_BORDER_STYLE][j][JV.PROP_POSITION]] = borderItem;
                            }
                            rst[rptDftItem[JV.PROP_ID]] = item;
                        }
                    }
                }
            }
        }
        return rst;
    }
    let JpcResult = {};
    //JpcResult.report_title
    JpcResult.initialize = function(rptTpl) {
        let me = this;
        if (rptTpl[JV.NODE_FLOW_INFO]) {
            me.flowTab = JpcFlowTab.createNew();
            me.flowTab.initialize(false);
            me.isFollowMode = false;
        }
        if (rptTpl[JV.NODE_FLOW_INFO_EX]) {
            me.flowTabEx = JpcFlowTab.createNew();
            me.flowTabEx.initialize(true);
            if (rptTpl[JV.NODE_FLOW_INFO_EX][JV.PROP_FLOW_EX_DISPLAY_MODE] === JV.DISPLAY_MODE_FOLLOW) {
                me.isFollowMode = true;
            }
        }
        if (rptTpl[JV.NODE_BILL_INFO]) {
            me.billTab = JpcBillTab.createNew();
            me.billTab.initialize();
        }
        //let dt1 = new Date();
        if (rptTpl[JV.NODE_CROSS_INFO]) {
            me.crossTab = JpcCrossTab.createNew();
            me.crossTab.initialize();
        }
        me.totalPages = 0;
        me.exTotalPages = 0;
        me.runTimePageData = {};
        me.fields = JpcField.createNew(rptTpl);
        me.params = JpcParam.createNew(rptTpl);
        me.formulas = JpcFunc.createNew(rptTpl);
    };

    JpcResult.analyzeData = function(rptTpl, dataObj, defProperties, option, outputType) {
        let me = this, dftPagingOption = option||JV.PAGING_OPTION_NORMAL;
        //1. data object
        let dataHelper = JpcData.createNew();
        me.executeFormulas(JV.RUN_TYPE_BEFORE_ANALYZING, rptTpl, dataObj, me); //在分析前运行，主要是增加灵活性，比如：重新编排数据的主从关系
        if (me.crossTab) {
            me.executeFormulas(JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me);
            dataHelper.analyzeData(rptTpl, dataObj);
            me.crossTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0), me);
        } else {
            dataHelper.analyzeData(rptTpl, dataObj);
            //2. tab object
            //pre-condition: the data should be sorted in SQL/NoSQL level!
            //let dt1 = new Date();
            if (me.flowTab) {
                me.flowTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0), me);
                if (me.flowTabEx) {
                    me.flowTabEx.sorting(rptTpl, dataObj, dataHelper.exDataSeq.slice(0), me);
                }
            }
            if (me.billTab) {
                me.billTab.sorting(rptTpl, dataObj, dataHelper.dataSeq.slice(0), me);
            }
            //let dt2 = new Date();
            //alert(dt2 - dt1);
            //3. formulas
            me.executeFormulas(JV.RUN_TYPE_BEFORE_PAGING, rptTpl, dataObj, me);
        }
        //4. paging
        me.paging(rptTpl, dataObj, defProperties, dftPagingOption, outputType);
        //alert('analyzeData was completed!');
        //for garbage collection:
        dataHelper = null;
    };
    JpcResult.paging = function(rptTpl, dataObj, defProperties, option, outputType) {
        let me = this, dftPagingOption = option||JV.PAGING_OPTION_NORMAL;
        if (me.flowTab) {
            if (me.isFollowMode) {
                me.totalPages = me.flowTab.preSetupPages(rptTpl, dataObj, defProperties, dftPagingOption, me, me.flowTabEx, outputType);
            } else {
                me.totalPages = me.flowTab.preSetupPages(rptTpl, dataObj, defProperties, dftPagingOption, me, null, outputType);
                if (me.flowTabEx) {
                    me.exTotalPages = me.flowTabEx.preSetupPages(rptTpl, dataObj, defProperties, dftPagingOption, me, null, outputType);
                    //console.log('ad-hoc flow pages: ' + me.exTotalPages);
                }
                me.totalPages += me.exTotalPages;
            }
        } else if (me.crossTab) {
            //me.totalPages = me.crossTab.preSetupPages(rptTpl, defProperties, dftPagingOption);
            me.totalPages = me.crossTab.preSetupPages(rptTpl, defProperties, JV.PAGING_OPTION_NORMAL); //infinity对交叉表来说无意义
        } else if (me.billTab) {
            me.totalPages = me.billTab.paging(rptTpl, dataObj);
        }
    };
    JpcResult.executeFormulas = function(runType, $CURRENT_TEMPLATE, $CURRENT_DATA, $CURRENT_RPT) {
        let execFmlMe = this;
        for (let execFmlIdx = 0; execFmlIdx < execFmlMe.formulas.length; execFmlIdx++) {
            //remark: 搞这么复杂的变量名是为了防止与表达式起冲突(如循环变量i,j,k，容易造成变量冲突且不容易看出问题)
            if (execFmlMe.formulas[execFmlIdx][JV.PROP_RUN_TYPE] === runType) {
                let expression = execFmlMe.formulas[execFmlIdx][JV.PROP_EXPRESSION];
                if (expression) {
                    let $ME = execFmlMe.formulas[execFmlIdx];
                    // console.log("current expression idx: " + execFmlIdx);
                    // console.log(expression);
                    try {
                        eval(expression);
                    } catch (ex) {
                        console.log(ex);
                    }
                }
            }
        }
    };
    JpcResult.outputAsPreviewPage= function(rptTpl, defProperties) {
        let me = this, rst = {};
        rst[JV.NODE_CONTROL_COLLECTION] = private_buildDftControls(rptTpl, (defProperties === null)?null:defProperties.ctrls);
        rst[JV.NODE_STYLE_COLLECTION] = private_buildDftStyles(rptTpl, (defProperties === null)?null:defProperties.styles);
        rst[JV.NODE_FONT_COLLECTION] = private_buildDftFonts(rptTpl, (defProperties === null)?null:defProperties.fonts);
        rst[JV.NODE_PAGE_INFO] = {};
        rst[JV.NODE_PAGE_INFO][JV.NODE_MAIN_INFO_RPT_NAME] = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MAIN_INFO_RPT_NAME];
        rst[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE] = JpcCommonHelper.getPageSize(rptTpl);
        rst[JV.NODE_PAGE_INFO][JV.NODE_MARGINS] = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS];
        rst.items = [];
        let bands = JpcBand.createNew(rptTpl, defProperties);
        try {
            function getPageMergeBorder() {
                let mergeRst = null;
                if (bands[JV.BAND_PROP_MERGE_BAND]) {
                    let mergedBand = bands[JV.BAND_PROP_MERGE_BAND];
                    mergeRst = {};
                    mergeRst[JV.PROP_LEFT] = parseInt(mergedBand[JV.PROP_LEFT].toFixed(0));
                    mergeRst[JV.PROP_RIGHT] = parseInt(mergedBand[JV.PROP_RIGHT].toFixed(0));
                    mergeRst[JV.PROP_TOP] = parseInt(mergedBand[JV.PROP_TOP].toFixed(0));
                    mergeRst[JV.PROP_BOTTOM] = parseInt(mergedBand[JV.PROP_BOTTOM].toFixed(0));
                }
                return mergeRst;
            }
            //1.
            let rstPage = {};
            rstPage[JV.PROP_PAGE_SEQ] = 1;
            if (me.flowTab) {
                rstPage[JV.PROP_CELLS] = me.flowTab.outputAsPreviewPage(rptTpl, bands, rst[JV.NODE_CONTROL_COLLECTION], me);
            } else if (me.crossTab) {
                rstPage[JV.PROP_CELLS] = me.crossTab.outputAsPreviewPage(rptTpl, bands, rst[JV.NODE_CONTROL_COLLECTION], me);
            } else if (me.billTab) {
                rstPage[JV.PROP_CELLS] = me.billTab.outputAsPreviewPage(rptTpl, bands, rst[JV.NODE_CONTROL_COLLECTION], me);
            }
            let pageMergeBorder = getPageMergeBorder();
            if (pageMergeBorder) {
                rstPage[JV.PROP_PAGE_MERGE_BORDER] = pageMergeBorder;
            }
            rst.items.push(rstPage);
            //2.
            if (bands[JV.BAND_PROP_MERGE_BAND]) {
                let mergedBand = {}, band = bands[JV.BAND_PROP_MERGE_BAND];
                mergedBand[JV.PROP_LEFT] = parseInt(band[JV.PROP_LEFT].toFixed(0));
                mergedBand[JV.PROP_RIGHT] = parseInt(band[JV.PROP_RIGHT].toFixed(0));
                mergedBand[JV.PROP_TOP] = parseInt(band[JV.PROP_TOP].toFixed(0));
                mergedBand[JV.PROP_BOTTOM] = parseInt(band[JV.PROP_BOTTOM].toFixed(0));
                mergedBand[JV.BAND_PROP_STYLE] = band[JV.BAND_PROP_STYLE];
                rst[JV.BAND_PROP_MERGE_BAND] = mergedBand;
            }
        } catch(exception) {
            console.log(exception);
        } finally {
            bands = null;
        }
        return rst;
    };
    JpcResult.outputAsSimpleJSONPageArray = function(rptTpl, dataObj, startPage, endPage, defProperties, customizeCfg) {
        let me = this, rst = {};
        if ((startPage > 0) && (startPage <= endPage) && (endPage <= me.totalPages)) {
            rst[JV.NODE_CONTROL_COLLECTION] = private_buildDftControls(rptTpl, (defProperties === null)?null:defProperties.ctrls);
            rst[JV.NODE_STYLE_COLLECTION] = private_buildDftStyles(rptTpl, (defProperties === null)?null:defProperties.styles);
            rst[JV.NODE_FONT_COLLECTION] = private_buildDftFonts(rptTpl, (defProperties === null)?null:defProperties.fonts);
            rst[JV.NODE_PAGE_INFO] = {};
            rst[JV.NODE_PAGE_INFO][JV.NODE_MAIN_INFO_RPT_NAME] = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MAIN_INFO_RPT_NAME];
            rst[JV.NODE_PAGE_INFO][JV.NODE_PAGE_SIZE] = JpcCommonHelper.getPageSize(rptTpl);
            rst[JV.NODE_PAGE_INFO][JV.NODE_MARGINS] = rptTpl[JV.NODE_MAIN_INFO][JV.NODE_MARGINS];
            rst.items = [];
            let bands = JpcBand.createNew(rptTpl, defProperties);
            try {
                for (let page = startPage; page <= endPage; page++) {
                    me.runTimePageData.currentPage = page;
                    me.executeFormulas(JV.RUN_TYPE_BEFORE_OUTPUT, rptTpl, dataObj, me);
                    rst.items.push(me.outputAsSimpleJSONPage(rptTpl, dataObj, bands, page, rst[JV.NODE_CONTROL_COLLECTION], customizeCfg));
                }
                if (bands[JV.BAND_PROP_MERGE_BAND]) {
                    let mergedBand = {}, band = bands[JV.BAND_PROP_MERGE_BAND];
                    mergedBand[JV.PROP_LEFT] = parseInt(band[JV.PROP_LEFT].toFixed(0));
                    mergedBand[JV.PROP_RIGHT] = parseInt(band[JV.PROP_RIGHT].toFixed(0));
                    mergedBand[JV.PROP_TOP] = parseInt(band[JV.PROP_TOP].toFixed(0));
                    mergedBand[JV.PROP_BOTTOM] = parseInt(band[JV.PROP_BOTTOM].toFixed(0));
                    mergedBand[JV.BAND_PROP_STYLE] = band[JV.BAND_PROP_STYLE];
                    rst[JV.BAND_PROP_MERGE_BAND] = mergedBand;
                }
            } catch(exception) {
                console.log(exception);
            } finally {
                bands = null;
            }
        }
        return rst;
    };
    JpcResult.outputAsSimpleJSONPage = function(rptTpl, dataObj, bands, page, controls, customizeCfg) {
        let me = this, rst = null;
        function getPageMergeBorder() {
            let mergeRst = null;
            if (bands[JV.BAND_PROP_MERGE_BAND]) {
                let mergedBand = bands[JV.BAND_PROP_MERGE_BAND];
                mergeRst = {};
                mergeRst[JV.PROP_LEFT] = parseInt(mergedBand[JV.PROP_LEFT].toFixed(0));
                mergeRst[JV.PROP_RIGHT] = parseInt(mergedBand[JV.PROP_RIGHT].toFixed(0));
                mergeRst[JV.PROP_TOP] = parseInt(mergedBand[JV.PROP_TOP].toFixed(0));
                mergeRst[JV.PROP_BOTTOM] = parseInt(mergedBand[JV.PROP_BOTTOM].toFixed(0));
            }
            return mergeRst;
        }
        if (me.totalPages >= page) {
            rst = {};
            rst[JV.PROP_PAGE_SEQ] = page;
            //rst.cells = [];
            let adHocMergePos = null;
            if (me.flowTab) {
                if (me.totalPages - me.exTotalPages >= page) {
                    if (me.flowTab.paging_option === JV.PAGING_OPTION_INFINITY) {
                        adHocMergePos = {};
                    }
                    rst[JV.PROP_CELLS] = me.flowTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, adHocMergePos, me, customizeCfg);
                    if (adHocMergePos) {
                        adHocMergePos[JV.NODE_PAGE_SIZE] = JpcCommonHelper.getPageSize(rptTpl);
                        rst[JV.PAGE_SPECIAL_MERGE_POS] = adHocMergePos;
                    }

                } else {
                    if (!me.isFollowMode) {
                        rst[JV.PROP_CELLS] = me.flowTabEx.outputAsSimpleJSONPage(rptTpl, dataObj, page - (me.totalPages - me.exTotalPages), bands, controls, adHocMergePos, me, customizeCfg);
                    }
                }
            } else if (me.crossTab) {
                rst[JV.PROP_CELLS] = me.crossTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me, customizeCfg);
            } else if (me.billTab) {
                rst[JV.PROP_CELLS] = me.billTab.outputAsSimpleJSONPage(rptTpl, dataObj, page, bands, controls, me, customizeCfg);
            }
            if (!(me.flowTab && me.flowTab.paging_option === JV.PAGING_OPTION_INFINITY)) {
                let pageMergeBorder = getPageMergeBorder();
                if (pageMergeBorder) {
                    rst[JV.PROP_PAGE_MERGE_BORDER] = pageMergeBorder;
                }
            }
        }
        return rst;
    };
    //JpcEx.rte.currentRptObj = JpcResult;
    return JpcResult;
};

module.exports = new JpcExSrv();