import JSPDF from "jspdf";
import html2canvas from "html2canvas";

/*
* 使用说明
* ele:需要导出pdf的容器元素(dom节点 不是id)
* pdfFileName: 导出文件的名字 通过调用outPutPdfFn方法也可传参数改变
* splitOptions: 分割选项，[{type:String, className:String}]，type：table/div, className：避免截断的类名
*               避免分段截断的类名 当pdf有多页时需要传入此参数 , 避免pdf分页时截断元素  如表格<tr class="itemClass"></tr>
* 调用方式 先 let pdf = new PdfLoader(ele, 'pdf' ,'itemClass');
* 若想改变pdf名称 pdf.outPutPdfFn(fileName);  outPutPdfFn方法返回一个promise 可以使用then方法处理pdf生成后的逻辑
*
* 转化过程， element => 避免分割，适当添加空div占位 => html2canvas转换成一整张canvas => 复制其中一部分转换成新的单页canvas => pdf中添加image
*/
class PdfLoader {
    constructor(ele, pdfFileName, splitOptions) {
        this.ele = ele;
        this.pdfFileName = pdfFileName;
        this.splitOptions = splitOptions;
        this.A4_WIDTH = 595;
        this.A4_HEIGHT = 842;
        this.PADDING_TOP_BOTTOM = 0;
        this.PADDING_LEFT_RIGHT = 0;
    };

    async getPDF(resolve) {
        html2canvas(this.ele, {
            allowTaint: true, // 允许绘制
            useCORS: true, // 跨域请求
            taintTest: false, // 绘制测试
            logging: false,
            dpi: 300
        }).then(async canvas => {
            let pdf = new JSPDF("p", "pt", "a4");
            let ctx = canvas.getContext("2d");
            let a4w = this.A4_WIDTH - this.PADDING_LEFT_RIGHT * 2;
            let a4h = this.A4_HEIGHT - this.PADDING_TOP_BOTTOM * 2;
            let imgHeight = canvas.width / a4w * a4h // 按A4显示比例换算一页图像的像素高度。补偿越多，页面内容显示越多

            let renderedHeight = 0;
            while (renderedHeight < canvas.height) {
                let page = document.createElement("canvas")
                page.width = canvas.width
                page.height = Math.min(imgHeight, canvas.height - renderedHeight)// 可能内容不足一页

                // 用getImageData剪裁指定区域，并画到前面创建的canvas对象中
                page.getContext("2d").putImageData(ctx.getImageData(0, renderedHeight, canvas.width, Math.min(imgHeight, canvas.height - renderedHeight)), 0, 0)
                // pdf.addImage(page.toDataURL("image/jpeg", 1.0), "JPEG", this.PADDING_LEFT_RIGHT, this.PADDING_TOP_BOTTOM, a4w, Math.min(a4h, a4w * page.height / page.width)) // 添加图像到页面，保留10mm边距
                pdf.addImage(page.toDataURL("image/jpeg", 1.0), "JPEG", a4w*0.02, Math.min(a4h, a4w * page.height / page.width)*0.02, a4w*0.96, Math.min(a4h, a4w * page.height / page.width)*0.96) // 添加图像到页面，保留10mm边距

                renderedHeight += imgHeight
                if (renderedHeight < canvas.height) {
                    pdf.addPage()
                }
            }

            pdf.save(this.pdfFileName + ".pdf", {returnPromise: true}).then(() => {
                // 还原table
                let tableEmptyRows = document.querySelectorAll('.empty-tr');
                for (let i = tableEmptyRows.length - 1; i >= 0; i--) {
                    this.restoreNode(tableEmptyRows[i]);
                }

                //去除添加的空div 防止页面混乱
                let doms = document.querySelectorAll('.empty-div')
                for (let i = doms.length - 1; i >= 0; i--) {
                    doms[i].remove();
                }
            });
            this.ele.style.height = '';
            resolve();
        })

    };

    /**
     * 获取节点的rowspan属性值
     * @param node
     * @returns {number}
     */
    getRowspan(node) {
        return parseInt(node.getAttribute("rowspan"));
    };

    /**
     * 设置节点的rowspan属性值
     * @param node
     * @param rowspan
     * @returns {number}
     */
    setRowspan(node, rowspan) {
        return node.setAttribute("rowspan", `${rowspan}`);
    };

    /**
     * 清空node的子元素
     * @param node
     */
    clearNode(node) {
        const nodes = node.childNodes;
        for (let i = nodes.length - 1; i >= 0; i--) {
            nodes[i].remove();
        }
    };

    /**
     *
     * @param node 当前节点
     * @returns {*}
     */
    adjustColumnRowspan(node) {
        // 查找带有满列的行
        const fullColumnCount = 8;
        let maxElementCount = node.childElementCount;
        let currentNode = node;
        let index = 0;
        let cloneRow = []
        while (currentNode.childElementCount < fullColumnCount) {
            index++;

            const previousNode = currentNode.previousSibling;
            if (currentNode.childElementCount < previousNode.childElementCount && previousNode.childElementCount >= maxElementCount) {
                const cloneRowFragment = [];
                const adjustColumnCount = previousNode.childElementCount - maxElementCount;
                for (let i = 0; i < adjustColumnCount; i++) {
                    const rowspan = this.getRowspan(previousNode.children[i]);
                    this.setRowspan(previousNode.children[i], index);
                    if (rowspan > 1) {
                        const cloneFirstColumn = previousNode.children[i].cloneNode(true);
                        this.setRowspan(cloneFirstColumn, `${rowspan - index + 1}`)
                        this.clearNode(cloneFirstColumn);
                        cloneRowFragment.push(cloneFirstColumn);
                    }
                }
                maxElementCount = previousNode.childElementCount;
                cloneRow = cloneRowFragment.concat(cloneRow);
            }
            currentNode = currentNode.previousSibling;
        }

        for (let i = cloneRow.length; i < fullColumnCount; i++) {
            cloneRow.push(document.createElement('div'))
        }
        return cloneRow;
    };

    addElementTableRow(node, newNodeChildren, height) {
        let divParent = node.parentNode; // 获取该div的父节点
        let newNode = document.createElement('tr');
        newNode.className = 'empty-tr el-table__cell';
        newNode.style.background = 'white';
        newNode.style.height = height + 'px';
        newNode.style.width = '100%';
        newNodeChildren.forEach(item => newNode.appendChild(item)); // 添加补充列
        let next = node.nextSibling; // 获取div的下一个兄弟节点
        // 判断兄弟节点是否存在
        if (next) {
            // 存在则将新节点插入到div的下一个兄弟节点之前，即div之后
            divParent.insertBefore(newNode, node);
        } else {
            // 不存在则直接添加到最后,appendChild默认添加到divParent的最后
            divParent.appendChild(newNode);
        }
        return newNode;
    }

    /**
     * 还原节点
     * @param node
     */
    restoreNode(node) {
        const fullColumnCount = 8;

        if (node.getElementsByTagName("div").length < fullColumnCount) {
            let currentNode = node.previousSibling;
            let maxElementCount = 0;

            while (currentNode) {
                if (currentNode.childElementCount > maxElementCount) {
                    const adjustColumnCount = node.childElementCount - currentNode.childElementCount;

                    for (let j = adjustColumnCount; j < currentNode.childElementCount && j < fullColumnCount - maxElementCount; j++) {
                        if (this.getRowspan(node.children[j])) {
                            this.setRowspan(currentNode.children[j - adjustColumnCount], this.getRowspan(currentNode.children[j - adjustColumnCount]) + this.getRowspan(node.children[j]) - 1);
                        }
                    }

                    maxElementCount = currentNode.childElementCount;
                }

                if (currentNode.children.length === fullColumnCount) {
                    break;
                }

                currentNode = currentNode.previousSibling;
            }
        }
        node.remove();
    };

    async outPutPdfFn(pdfFileName) {
        return new Promise((resolve, reject) => {
            this.ele.style.height = 'initial';
            pdfFileName ? this.pdfFileName = pdfFileName : null;
            let target = this.ele;
            let a4w = this.A4_WIDTH - this.PADDING_LEFT_RIGHT * 2;
            let a4h = this.A4_HEIGHT - this.PADDING_TOP_BOTTOM * 2;
            let pageHeight = target.scrollWidth / a4w * a4h;
            let pageNum = 1; //pdf页数

            // 获取分割dom，此处为class类名为item的dom
            let domList = [];
            this.splitOptions.forEach((splitOption) => {
                const list = document.getElementsByClassName(splitOption.className);
                for (let i = 0; i < list.length; i++) {
                    domList.push(list[i]);
                }
            })

            domList.sort((nodeA, nodeB) => {
                return nodeA.getBoundingClientRect().top - nodeB.getBoundingClientRect().top;
            })

            // 进行分割操作，当dom内容已超出a4的高度，则将该dom前插入一个空dom，把他挤下去，分割
            let eleBounding = this.ele.getBoundingClientRect();
            for (let i = 0; i < domList.length; i++) {
                let node = domList[i];
                let currentPage = Math.ceil((node.getBoundingClientRect().bottom - eleBounding.top) / pageHeight); //当前元素应该在哪一页
                if (currentPage > pageNum) {
                    const nodeType = this.splitOptions.find(item => node.classList.contains(item.className)).type;
                    console.log("nodeType", nodeType, pageNum);
                    if (nodeType === 'table') {
                        do {
                            // 调整
                            let newNodeChildren = this.adjustColumnRowspan(node);
                            let height = pageHeight * pageNum - (node.getBoundingClientRect().top - eleBounding.top);
                            let newNode = this.addElementTableRow(node, newNodeChildren, height);

                            // 检查
                            if (height < 0) {
                                this.restoreNode(newNode);
                                node = node.previousSibling;
                                if (!node) {
                                    break;
                                }
                            } else {
                                break;
                            }
                        } while (true)
                    } else if (nodeType === 'div') {
                        let divParent = node.parentNode; // 获取该div的父节点
                        let newNode = document.createElement('div');
                        newNode.className = 'empty-div';
                        newNode.style.background = 'white';
                        newNode.style.height = (pageHeight * pageNum - (node.getBoundingClientRect().top - eleBounding.top)) + 'px';
                        newNode.style.width = '100%';
                        let next = node.nextSibling; // 获取div的下一个兄弟节点
                        // 判断兄弟节点是否存在
                        if (next) {
                            // 存在则将新节点插入到div的下一个兄弟节点之前，即div之后
                            divParent.insertBefore(newNode, node);
                        } else {
                            // 不存在则直接添加到最后,appendChild默认添加到divParent的最后
                            divParent.appendChild(newNode);
                        }
                    }

                    pageNum++;
                }
            }
            // 异步函数，导出成功后处理交互
            this.getPDF(resolve, reject);
            // resolve();
        })
    };
}

export default PdfLoader;