package com.yeejoin.amos.knowledgebase.face.util.word;

import com.yeejoin.amos.knowledgebase.face.util.IOConfig;
import lombok.Data;
import lombok.experimental.Accessors;
import org.docx4j.XmlUtils;
import org.docx4j.dml.wordprocessingDrawing.Inline;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
import org.docx4j.openpackaging.parts.relationships.Namespaces;
import org.docx4j.relationships.Relationship;
import org.docx4j.wml.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;

import javax.xml.bind.JAXBException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;

/**
 * word导出工具
 * @author tiantao
 */
public class DocxBuilder {

    private WordprocessingMLPackage wordMLPackage;
    private ObjectFactory factory;
    private int docPrId = 1;
    private int cNvPrId = 2;

    public DocxBuilder(String html) {
        Document document = Jsoup.parseBodyFragment(html, "UTF-8");
        try {
            wordMLPackage = WordprocessingMLPackage.createPackage();
            factory = Context.getWmlObjectFactory();
            wordMLPackage.getMainDocumentPart().addTargetPart(IOConfig.INIT_NUMBERING_DEFINITION);
            addElement(wordMLPackage.getMainDocumentPart(), null, document.body(), new TextStyle(), new ParagraphStyle());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成word字节数据
     *
     * @return 字节数组
     */
    public byte[] build() {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        try {
            wordMLPackage.save(outputStream);
        } catch (Docx4JException e) {
            e.printStackTrace();
        }
        return outputStream.toByteArray();
    }

    /**
     * 递归解析html元素，根据不同的标签名插入不同数据到word中
     *
     * @param parent         word区域
     * @param current        当前操作行
     * @param node           html元素节点
     * @param textStyle      当前文本样式
     * @param paragraphStyle 当前段落样式
     */
    private void addElement(ContentAccessor parent, ContentAccessor current, Node node, TextStyle textStyle, ParagraphStyle paragraphStyle) {
        if (node instanceof TextNode) {
            addText(current, ((TextNode) node).text().trim(), textStyle);
        } else {
            switch (node.nodeName()) {
                case "body":
                    addChildElement(parent, current, node, textStyle, paragraphStyle);
                    break;
                case "div":
                    if (!(parent instanceof Tc)) {
                        paragraphStyle = getDivParagraphStyle(node.attr("data-level"), node.attr("style"));
                    }
                    if (isTableDiv(node)) {
                        addChildElement(parent, current, node, textStyle, paragraphStyle);
                    } else {
                        textStyle = getDivTextStyle(node.attr("data-level"));
                        current = factory.createP();
                        ((P) current).setPPr(paragraphStyle.getPPr());
                        addChildElement(parent, current, node, textStyle, paragraphStyle);
                        parent.getContent().add(current);
                    }
                    break;
                case "h1":
                case "h2":
                case "h3":
                case "h4":
                case "h5":
                case "h6":
                case "h7":
                case "h8":
                    addChildElement(parent, current, node, textStyle, paragraphStyle);
                    break;
                case "em":
                    textStyle = new TextStyle(textStyle).setItalic(true);
                    addChildElement(parent, current, node, textStyle, paragraphStyle);
                    break;
                case "span":
                    textStyle = getSpanTextStyle(textStyle, node.attr("style"));
                    addChildElement(parent, current, node, textStyle, paragraphStyle);
                    break;
                case "strong":
                    textStyle = new TextStyle(textStyle).setBold(true);
                    addChildElement(parent, current, node, textStyle, paragraphStyle);
                    break;
                case "sup":
                    textStyle = new TextStyle(textStyle).setTextScript("superscript");
                    addChildElement(parent, current, node, textStyle, paragraphStyle);
                    break;
                case "sub":
                    textStyle = new TextStyle(textStyle).setTextScript("subscript");
                    addChildElement(parent, current, node, textStyle, paragraphStyle);
                    break;
                case "label":
                    addChildElement(parent, current, node, textStyle, paragraphStyle);
                    break;
                case "ul":
                case "ol":
                    paragraphStyle = getListParagraphStyle(node.nodeName(), node.attr("style"));
                    addChildElement(parent, current, node, textStyle, paragraphStyle);
                    break;
                case "li":
                    current = factory.createP();
                    ((P) current).setPPr(paragraphStyle.getPPr());
                    addChildElement(parent, current, node, textStyle, paragraphStyle);
                    parent.getContent().add(current);
                    break;
                case "table":
                    addChildElement(parent, current, node, textStyle, paragraphStyle);
                    break;
                case "caption":
                    paragraphStyle = getTableCaptionParagraphStyle();
                    textStyle = new TextStyle().setFontSize("14").setAsciiFont("黑体").setBold(true).setUnderscore(true);
                    current = factory.createP();
                    ((P) current).setPPr(paragraphStyle.getPPr());
                    addChildElement(parent, current, node, textStyle, paragraphStyle);
                    parent.getContent().add(current);
                    break;
                case "tbody":
                    addTable(parent, node);
                    break;
                case "img":
                    addImg(parent, current, node.attr("src"), node.attr("alt"));
                    break;
                case "a":
                    addA(current, node.attr("href"), ((Element) node).text());
                    break;
                default:
                    System.err.println(node.nodeName());
            }
        }
    }

    private void addChildElement(ContentAccessor parent, ContentAccessor current, Node node, TextStyle textStyle, ParagraphStyle paragraphStyle) {
        for (Node child : node.childNodes()) {
            addElement(parent, current, child, textStyle, paragraphStyle);
        }
    }

    /**
     * 加入文字
     *
     * @param current   当前行
     * @param text      文本
     * @param textStyle 文字样式
     */
    private void addText(ContentAccessor current, String text, TextStyle textStyle) {
        if (text.length() > 0) {
            R r = getR(text, textStyle);
            if (current instanceof P) {
                P p = (P) current;
                p.getContent().add(r);
            }
        }
    }

    /**
     * 加入表格
     *
     * @param parent word区域
     * @param node   table元素
     */
    private void addTable(ContentAccessor parent, Node node) {
        // 二维元素列表
        List<LinkedList<Element>> elementCollection = new ArrayList<>();
        // 二维Tc列表
        List<List<Tc>> tcCollection = new ArrayList<>();
        // 元素坐标-偏移量
        Map<TcPosition, Integer> positionOffsetMap = new HashMap<>();
        // 待合并单元格
        Map<TcPosition, List<TcPosition>> mergeMap = new HashMap<>();
        // 拆解表格数据并分析表格合并项
        disassembleAndMergeAnalysis(node, elementCollection, positionOffsetMap, mergeMap);
        // TODO-表格样式解析
        Tbl table = factory.createTbl();
        // 采用默认表格样式
        setTableStyle(table, !(parent instanceof Tc));
        parent.getContent().add(table);
        // 解析二维元素列表
        parseTdList2Table(table, elementCollection, tcCollection);
        // 单元格合并
        mergeTc(mergeMap, tcCollection);
    }

    /**
     * 拆解表格数据并分析表格合并项
     *
     * @param node              html表格元素
     * @param elementCollection 二维元素列表
     * @param positionOffsetMap 元素坐标-偏移量
     * @param mergeMap          待合并单元格
     */
    private void disassembleAndMergeAnalysis(Node node, List<LinkedList<Element>> elementCollection, Map<TcPosition, Integer> positionOffsetMap, Map<TcPosition, List<TcPosition>> mergeMap) {
        // 最大列数
        int maxColNum = 0;
        Elements trs = ((Element) node).getElementsByTag("tr");
        List<Element> trList = new ArrayList<>();
        for (Element tr : trs) {
            if (tr.parent() == node) {
                trList.add(tr);
            }
        }
        for (int row = 0; row < trList.size(); row++) {
            LinkedList<Element> nodeList = new LinkedList<>();
            elementCollection.add(nodeList);
            Element tr = trList.get(row);
            Elements tds = tr.getElementsByTag("td");
            List<Element> tdList = new ArrayList<>();
            Iterator<Element> tdIterator = tds.iterator();
            while (tdIterator.hasNext()) {
                Element td = tdIterator.next();
                if (td.parent() == tr) {
                    tdList.add(td);
                }
            }
            maxColNum = Math.max(maxColNum, tdList.size());
            for (int col = 0; col <= maxColNum; col++) {
                TcPosition position = new TcPosition(row, col);
                if (positionOffsetMap.containsKey(position)) {
                    Integer count = positionOffsetMap.get(position);
                    for (int i = 0; i < count; i++) {
                        nodeList.add(null);
                    }
                }
                if (col >= tdList.size()) {
                    continue;
                }
                Element td = tdList.get(col);
                nodeList.add(td);
                int rowspan = 1;
                int colspan = 1;
                if (td.attributes().hasKey("rowspan")) {
                    rowspan = Integer.parseInt(td.attributes().get("rowspan"));
                }
                if (td.attributes().hasKey("colspan")) {
                    colspan = Integer.parseInt(td.attributes().get("colspan"));
                }
                if (rowspan > 1 || colspan > 1) {
                    List<TcPosition> voidTcPositionList = new ArrayList<>();
                    for (int i = 0; i < rowspan; i++) {
                        for (int j = 0; j < colspan; j++) {
                            TcPosition newPosition = new TcPosition(row + i, col + j);
                            if (!newPosition.equals(position)) {
                                voidTcPositionList.add(newPosition);
                            }
                            if (i == 0 && j == 1) {
                                positionOffsetMap.put(newPosition, colspan - 1);
                            }
                            if (j == 0 && i > 0) {
                                positionOffsetMap.put(newPosition, colspan);
                            }
                        }
                    }
                    mergeMap.put(position, voidTcPositionList);
                }
            }
        }
    }

    /**
     * 解析二维td元素列表
     *
     * @param table             word表格对象
     * @param elementCollection 二维td元素列表
     * @param tcCollection      Tc对象存储
     */
    private void parseTdList2Table(Tbl table, List<LinkedList<Element>> elementCollection, List<List<Tc>> tcCollection) {
        for (LinkedList<Element> elementList : elementCollection) {
            Tr tr = factory.createTr();
            table.getContent().add(tr);
            List<Tc> tcList = new ArrayList<>();
            tcCollection.add(tcList);
            for (Element element : elementList) {
                // TODO-单元格样式解析
                Tc tc = factory.createTc();
                // 默认单元格样式
                setTcStyle(tc);
                tr.getContent().add(tc);
                tcList.add(tc);
                ParagraphStyle paragraphStyle = new ParagraphStyle().setLineIndentation(0L).setTcLine(true).setLineAlign("center");
                if (isTableDiv(element)) {
                    addChildElement(tc, null, element, new TextStyle(), paragraphStyle);
                    // 必须增加空行，否则文件打开报错
                    tc.getContent().add(factory.createP());
                } else if (isDivParent(element)) {
                    addChildElement(tc, null, element, new TextStyle(), paragraphStyle);
                } else {
                    P p = factory.createP();
                    p.setPPr(paragraphStyle.getPPr());
                    tc.getContent().add(p);
                    if (null != element) {
                        addChildElement(tc, p, element, new TextStyle(), paragraphStyle);
                    }
                }
            }
        }
    }

    // 表格单元格合并
    private void mergeTc(Map<TcPosition, List<TcPosition>> mergeMap, List<List<Tc>> tcCollection) {
        for (TcPosition startPosition : mergeMap.keySet()) {
            TcPr tcpr = getTcPr(tcCollection.get(startPosition.row).get(startPosition.col));
            TcPrInner.VMerge vMerge = factory.createTcPrInnerVMerge();
            vMerge.setVal("restart");
            tcpr.setVMerge(vMerge);
            TcPrInner.HMerge hMerge = factory.createTcPrInnerHMerge();
            hMerge.setVal("restart");
            tcpr.setHMerge(hMerge);
            List<TcPosition> continuesPositions = mergeMap.get(startPosition);
            for (TcPosition continuePosition : continuesPositions) {
                tcpr = getTcPr(tcCollection.get(continuePosition.row).get(continuePosition.col));
                if (continuePosition.col.equals(startPosition.col)) {
                    vMerge = factory.createTcPrInnerVMerge();
                    vMerge.setVal("continue");
                    tcpr.setVMerge(vMerge);
                    hMerge = factory.createTcPrInnerHMerge();
                    hMerge.setVal("restart");
                    tcpr.setHMerge(hMerge);
                } else {
                    hMerge = factory.createTcPrInnerHMerge();
                    hMerge.setVal("continue");
                    tcpr.setHMerge(hMerge);
                }
            }
        }
    }

    /**
     * 判断元素是否为table父节点
     *
     * @param node 元素
     * @return 是否为table父节点
     */
    private boolean isTableDiv(Node node) {
        if (null == node) {
            return false;
        }
        for (Node child : node.childNodes()) {
            if ("table".equals(child.nodeName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断元素是否为div父节点
     *
     * @param node 元素
     * @return 是否为div父节点
     */
    private boolean isDivParent(Node node) {
        if (null == node) {
            return false;
        }
        for (Node child : node.childNodes()) {
            if ("div".equals(child.nodeName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 加入图片
     *
     * @param parent  word区域
     * @param current 当前块
     * @param src     图片地址
     * @param alt     图片名
     */
    private void addImg(ContentAccessor parent, ContentAccessor current, String src, String alt) {
        if (null != src) {
            try {
                byte[] bytes = getRemotePic2Bytes(src);
                BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes);
                Inline inline = imagePart.createImageInline(alt, alt, docPrId++, cNvPrId++, false, 5000);
                Drawing drawing = factory.createDrawing();
                drawing.getAnchorOrInline().add(inline);
                if (current != null) {
                    if (current instanceof P) {
                        current.getContent().add(drawing);
                    } else {
                        P p = factory.createP();
                        current.getContent().add(p);
                        p.getContent().add(drawing);
                    }
                } else {
                    P p = factory.createP();
                    parent.getContent().add(p);
                    p.getContent().add(drawing);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 加入超链接
     *
     * @param current 当前行
     * @param href    链接地址
     * @param text    文本
     */
    private void addA(ContentAccessor current, String href, String text) {
        if (null == current) {
            return;
        }
        Relationship relationship = new org.docx4j.relationships.ObjectFactory().createRelationship();
        wordMLPackage.getMainDocumentPart().getRelationshipsPart().addRelationship(relationship);
        relationship.setType(Namespaces.HYPERLINK);
        relationship.setTarget(href);
        relationship.setTargetMode("External");
        StringBuilder builder = new StringBuilder();
        builder.append("<w:hyperlink r:id=\"");
        builder.append(relationship.getId());
        builder.append("\" xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" ");
        builder.append("xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" >");
        builder.append("<w:r><w:rPr><w:rStyle w:val=\"Hyperlink\" />");
        builder.append("<w:rFonts  w:ascii=\"");
        builder.append("Times New Roman");
        builder.append("\"  w:hAnsi=\"");
        builder.append("Times New Roman");
        builder.append("\"  w:eastAsia=\"");
        builder.append("宋体");
        builder.append("\" w:hint=\"eastAsia\"/>");
        builder.append("<w:sz w:val=\"");
        builder.append("24");
        builder.append("\"/><w:szCs w:val=\"");
        builder.append("24");
        builder.append("\"/></w:rPr><w:t>");
        builder.append(text);
        builder.append("</w:t></w:r></w:hyperlink>");
        try {
            P.Hyperlink link = (P.Hyperlink) XmlUtils.unmarshalString(builder.toString());
            current.getContent().add(link);
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }

    private R getR(String textVal, TextStyle textStyle) {
        Text text = factory.createText();
        text.setValue(textVal);
        R r = factory.createR();
        r.setRPr(textStyle.getRPr());
        r.getContent().add(text);
        return r;
    }

    /**
     * 获取远程图片转为字节数组
     *
     * @param picUrl 图片链接
     * @return 包含图片字节数据的字节数组
     */
    private static byte[] getRemotePic2Bytes(String picUrl) {
        byte[] picBytes = {};
        try {
            if (picUrl.startsWith(IOConfig.PIC_ROUTER)) {
                picUrl = picUrl.replaceFirst(IOConfig.PIC_ROUTER, IOConfig.PIC_URI);
            }
            URL url = new URL(picUrl);
            URLConnection con = url.openConnection();
            // 设置请求超时为5s
            con.setConnectTimeout(5 * 1000);
            // 输入流
            InputStream is = con.getInputStream();
            // 1K的数据缓冲
            byte[] current = new byte[1024];
            // 读取到的数据长度
            int len;
            while ((len = is.read(current)) != -1) {
                picBytes = Arrays.copyOf(picBytes, picBytes.length + len);
                System.arraycopy(current, 0, picBytes, picBytes.length - len, len);
            }
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return picBytes;
    }

    private ParagraphStyle getDivParagraphStyle(String level, String style) {
        ParagraphStyle paragraphStyle = new ParagraphStyle();
        if (null != level && level.trim().length() > 0) {
            long lv = Long.parseLong(level.trim());
            if (lv == 1L) {
                paragraphStyle.setLineAlign("center");
            } else {
                paragraphStyle.setLineLv(lv - 2L);
            }
            if (lv < 4) {
                paragraphStyle.setLineIndentation(0L);
            }
            return paragraphStyle;
        } else {
            Map<String, String> styleMap = styleStr2Map(style);
            if (styleMap.containsKey("text-align")) {
                paragraphStyle.setLineAlign(styleMap.get("text-align"));
            }
        }
        return paragraphStyle;
    }

    private ParagraphStyle getListParagraphStyle(String listType, String style) {
        String listStyle = styleStr2Map(style).get("list-style-type");
        Long numId =1L;
        if (IOConfig.NUM_MAP.containsKey(listType)) {
            numId = IOConfig.NUM_MAP.get(listType);
        }
        Long ilv1Id = 1L;
        if (IOConfig.NUM_MAP.containsKey(listStyle)) {
            ilv1Id = IOConfig.NUM_MAP.get(listStyle);
        }
        numId = IOConfig.INIT_NUMBERING_DEFINITION.restart(numId == null ? 1L : numId, ilv1Id == null ? 1L : ilv1Id, 1L);
        return new ParagraphStyle()
                .setList(true)
                .setNumId(numId).setIlv1Id(ilv1Id);
    }

    private ParagraphStyle getTableCaptionParagraphStyle() {
        return new ParagraphStyle().setLineAlign("center").setLineIndentation(0L);
    }

    private TextStyle getDivTextStyle(String level) {
        TextStyle textStyle = new TextStyle();
        if (null != level && level.trim().length() > 0) {
            switch (level.trim()) {
                case "1":
                    textStyle.setFontSize("24px").setBold(true).setEastAsiaFont("黑体");
                    break;
                case "2":
                    textStyle.setFontSize("18px").setBold(true).setEastAsiaFont("黑体");
                    break;
                default:

            }
        }
        return textStyle;
    }

    private TextStyle getSpanTextStyle(TextStyle textStyle, String style) {
        textStyle = new TextStyle(textStyle);
        Map<String, String> spanStyleMap = styleStr2Map(style);
        for (String key : spanStyleMap.keySet()) {
            switch (key.trim().toLowerCase()) {
                case "text-decoration":
                    textStyle.setUnderscore(true);
                    break;
                case "color":
                    textStyle.setFontColor(spanStyleMap.get(key));
                    break;
                case "font-size":
                    textStyle.setFontSize(spanStyleMap.get(key));
                    break;
                default:
                    System.err.println(key);
                    break;
            }
        }
        return textStyle;
    }

    /**
     * 设置表格默认样式
     *
     * @param table   表格对象
     * @param needInd 是否缩进
     */
    private void setTableStyle(Tbl table, boolean needInd) {
        TblPr tablePr = factory.createTblPr();
        table.setTblPr(tablePr);
        TblBorders borders = factory.createTblBorders();
        tablePr.setTblBorders(borders);
        CTBorder border = new CTBorder();
        border.setColor("auto");
        border.setSz(new BigInteger("4"));
        border.setSpace(new BigInteger("0"));
        border.setVal(STBorder.SINGLE);
        borders.setBottom(border);
        borders.setLeft(border);
        borders.setRight(border);
        borders.setTop(border);
        borders.setInsideH(border);
        borders.setInsideV(border);
        if (needInd) {
            TblWidth tblInd = factory.createTblWidth();
            tblInd.setW(new BigInteger("600"));
            tablePr.setTblInd(tblInd);
        }
    }

    /**
     * 设置表格默认样式
     *
     * @param tc 单元格格对象
     */
    private void setTcStyle(Tc tc) {
        CTVerticalJc vjc = factory.createCTVerticalJc();
        vjc.setVal(STVerticalJc.CENTER);
        getTcPr(tc).setVAlign(vjc);
    }

    private TcPr getTcPr(Tc tc) {
        TcPr tcpr = tc.getTcPr();
        if (null == tcpr) {
            tcpr = factory.createTcPr();
            tc.setTcPr(tcpr);
        }
        return tcpr;
    }

    /**
     * style属性字符串转map
     *
     * @param style style属性值
     * @return Map
     */
    private static Map<String, String> styleStr2Map(String style) {
        Map<String, String> styleMap = new HashMap<>();
        if (null != style) {
            String[] styleItems = style.split(";");
            for (String styleItem : styleItems) {
                String[] styleEntry = styleItem.split(":");
                if (styleEntry.length == 2) {
                    styleMap.put(styleEntry[0].trim(), styleEntry[1].trim());
                }
            }
        }
        return styleMap;
    }

    /**
     * 文本样式
     */
    @Data
    @Accessors(chain = true)
    class TextStyle {
        // 加粗
        private boolean bold;
        // 斜体
        private boolean italic;
        // 下划线
        private boolean underscore;
        // 角标
        private String textScript;
        // 中文字体
        private String asciiFont = "Times New Roman";
        // 英文字体
        private String eastAsiaFont = "宋体";
        // 字体大小
        private String fontSize = "16px";
        // 字体颜色
        private String fontColor;

        TextStyle() {
        }

        TextStyle(TextStyle old) {
            this.setBold(old.bold)
                    .setFontColor(old.fontColor)
                    .setFontSize(old.fontSize)
                    .setItalic(old.italic)
                    .setUnderscore(old.underscore)
                    .setTextScript(old.textScript);
        }

        RPr getRPr() {
            RPr rpr = factory.createRPr();
            // 字体
            RFonts font = new RFonts();
            rpr.setRFonts(font);
            font.setAscii(asciiFont);
            font.setEastAsia(eastAsiaFont);
            // 字号
            HpsMeasure measure = new HpsMeasure();
            rpr.setSzCs(measure);
            rpr.setSz(measure);
            measure.setVal(BigInteger.valueOf((Long.parseLong(fontSize.toLowerCase().replaceAll("px", "")) - 4) * 2));
            // 加粗/斜体/下划线
            if (this.bold) {
                rpr.setB(new BooleanDefaultTrue());
            }
            if (this.italic) {
                rpr.setI(new BooleanDefaultTrue());
            }
            if (this.underscore) {
                U u = factory.createU();
                u.setVal(UnderlineEnumeration.SINGLE);
                rpr.setU(u);
            }
            // 字体颜色
            Color color = factory.createColor();
            rpr.setColor(color);
            color.setVal(this.fontColor);
            // 设置角标
            if (null != this.textScript) {
                CTVerticalAlignRun ctVerticalAlignRun = factory.createCTVerticalAlignRun();
                STVerticalAlignRun stVerticalAlignRun = STVerticalAlignRun.fromValue(textScript);
                ctVerticalAlignRun.setVal(stVerticalAlignRun);
                rpr.setVertAlign(ctVerticalAlignRun);
            }
            return rpr;
        }
    }

    /**
     * 段落样式
     */
    @Data
    @Accessors(chain = true)
    class ParagraphStyle {
        // 缩进
        private Long lineIndentation = 200L;
        // 对齐方式
        private String lineAlign = "left";
        // 大纲级别
        private Long lineLv;
        private boolean isTcLine;
        // 是否为列表
        private boolean list;
        // 列表样式
        private Long numId;
        private Long ilv1Id;


        PPr getPPr() {
            PPr ppr = factory.createPPr();
            // 列表样式
            if (this.list) {
                PPrBase.NumPr numPr = factory.createPPrBaseNumPr();
                ppr.setNumPr(numPr);
                // The <w:numId> element
                PPrBase.NumPr.NumId numIdElement = factory.createPPrBaseNumPrNumId();
                numPr.setNumId(numIdElement);
                numIdElement.setVal(BigInteger.valueOf(this.numId));
                // The <w:ilv> element
                PPrBase.NumPr.Ilvl ilvlElement = factory.createPPrBaseNumPrIlvl();
                numPr.setIlvl(ilvlElement);
                ilvlElement.setVal(BigInteger.valueOf(this.ilv1Id));
            }
            // 文本对齐
            Jc jc = factory.createJc();
            ppr.setJc(jc);
            switch (lineAlign) {
                case "center":
                    jc.setVal(JcEnumeration.CENTER);
                    break;
                case "right":
                    jc.setVal(JcEnumeration.RIGHT);
                    break;
                default:
                    jc.setVal(JcEnumeration.LEFT);
            }
            // 首行缩进
            PPrBase.Ind ind = factory.createPPrBaseInd();
            ppr.setInd(ind);
            ind.setFirstLineChars(BigInteger.valueOf(lineIndentation));
            // 大纲级别
            if (null != lineLv) {
                PPrBase.OutlineLvl outlineLvl = factory.createPPrBaseOutlineLvl();
                ppr.setOutlineLvl(outlineLvl);
                outlineLvl.setVal(BigInteger.valueOf(this.lineLv));
            }
            PPrBase.Spacing spacing = factory.createPPrBaseSpacing();
            // 行间距 [自动auto] [最小atLeast] [固定exact 1磅=20 1行=100 单倍行距=240]
            spacing.setLineRule(STLineSpacingRule.AUTO);
            if (isTcLine) {
                spacing.setBefore(BigInteger.valueOf(100L));
                spacing.setAfter(BigInteger.valueOf(100L));
            }
            ppr.setSpacing(spacing);
            return ppr;
        }
    }

    /**
     * 表格单元格坐标
     */
    class TcPosition {
        private Integer row;
        private Integer col;

        public TcPosition(Integer row, Integer col) {
            this.row = row;
            this.col = col;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof TcPosition)) {
                return false;
            }
            TcPosition that = (TcPosition) o;
            return Objects.equals(row, that.row) &&
                    Objects.equals(col, that.col);
        }

        @Override
        public int hashCode() {
            return Objects.hash(row, col);
        }
    }
}
