package cn.com.qmth.examcloud.core.questions.service.export;

import cn.com.qmth.examcloud.api.commons.security.bean.User;
import cn.com.qmth.examcloud.commons.exception.StatusException;
import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
import cn.com.qmth.examcloud.core.questions.base.FileDisposeUtil;
import cn.com.qmth.examcloud.core.questions.base.Model;
import cn.com.qmth.examcloud.core.questions.base.enums.ExamFileType;
import cn.com.qmth.examcloud.core.questions.base.excel.ExcelWriter;
import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
import cn.com.qmth.examcloud.core.questions.base.word.DocxProcessUtil;
import cn.com.qmth.examcloud.core.questions.dao.*;
import cn.com.qmth.examcloud.core.questions.dao.entity.*;
import cn.com.qmth.examcloud.core.questions.dao.entity.computerTestModel.*;
import cn.com.qmth.examcloud.core.questions.service.ExamFileService;
import cn.com.qmth.examcloud.core.questions.service.PaperDetailService;
import cn.com.qmth.examcloud.core.questions.service.PaperService;
import cn.com.qmth.examcloud.core.questions.service.QuestionAudioService;
import cn.com.qmth.examcloud.core.questions.service.bean.dto.*;
import cn.com.qmth.examcloud.core.questions.service.config.UpYunProperty;
import com.google.gson.Gson;
import freemarker.template.Configuration;
import freemarker.template.Template;
import main.java.com.UpYun;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.docx4j.XmlUtils;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.wml.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;

import javax.xml.bind.JAXBElement;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author chenken
 * @date 2017年7月15日 下午4:00:24
 * @company QMTH
 * @description 导出、上传文件service 父类
 */
public abstract class ExportPaperAbstractService {
    private static final Logger log = LoggerFactory.getLogger(ExportPaperAbstractService.class);

    @Autowired
    protected PaperRepo paperRepo;

    @Autowired
    protected PaperService paperService;

    @Autowired
    protected PaperDetailRepo paperDetailRepo;

    @Autowired
    protected PaperDetailUnitRepo paperDetailUnitRepo;

    @Autowired
    protected ExamFileService examFileService;

    @Autowired
    protected PaperDetailService paperDetailService;

    @Autowired
    protected QuestionAudioService questionAudioService;

    @Autowired
    protected BuildComputerTestJsonService buildComputerTestJsonService;

    @Autowired
    protected InitPaperExpService initPaperExpService;

    @Autowired
    protected QuesRepo quesRepo;

    @Autowired
    protected AudioTimeConfigRepo audioTimeConfigRepo;

    @Autowired
    protected QuesPkgPathRepo quesPkgPathRepo;

    @Autowired
    private UpYunProperty upYunProperty;

    public static final String FILL_BLANK_QUESTION_FLAG = "###";

    public static final String TEMP_FILE_EXP = "docxExport/";

    /**
     * Word文件后缀
     */
    public static final String DOCX_SUFFIX = ".docx";
    /**
     * Excel文件后缀
     */
    public static final String EXCEL_SUFFIX = ".xlsx";
    /**
     * ZIP文件后缀
     */
    public static final String ZIP_SUFFIX = ".zip";

    public static final String ENCODING = "UTF-8";
    /**
     * 下划线
     */
    private static final String UNDERSCORE = "_";

    //陕西师范模板
    protected static Template SXSF_TEMPLATE_PAPER;
    protected static Template SXSF_TEMPLATE_ANSWER;
    protected static Template SXSF_OUTLINE_TEMPLATE_PAPER;

    //西南交通大学
    protected static Template XNJD_TEMPLATE_PAPER;
    protected static Template XNJD_TEMPLATE_ANSWER;
    protected static Template XNJD_OUTLINE_TEMPLATE_PAPER;

    //电子科大模板
    protected static Template DZKD_TEMPLATE_PAPER;
    protected static Template DZKD_TEMPLATE_ANSWER;

    //华中科技大学模板
    protected static Template HZKJ_TEMPLATE_PAPER;
    protected static Template HZKJ_TEMPLATE_ANSWER;

    //山东大学
    protected static Template SDDX_TEMPLATE_PAPER;
    protected static Template SDDX_TEMPLATE_ANSWER;

    //天津大学
    protected static Template TJDX_TEMPLATE_PAPER;
    protected static Template TJDX_TEMPLATE_ANSWER;
    protected static Template TJDX_OUTLINE_TEMPLATE_PAPER;

    //石油大学模板
    protected static Template SYDX_TEMPLATE_PAPER;
    protected static Template SYDX_TEMPLATE_ANSWER;

    //西安交大模板
    protected static Template XAJD_TEMPLATE_PAPER;
    protected static Template XAJD_TEMPLATE_ANSWER;

    //武汉软件工程职业学院模板
    protected static Template RJGC_TMPLATE_PAPER;
    protected static Template RJGC_TMPLATE_ANSWER;

    //北京师范模板
    protected static Template BJSF_TMPLATE_PAPER;
    protected static Template BJSF_TMPLATE_ANSWER;

    //重庆大学
    protected static Template CQDX_TMPLATE_PAPER;
    protected static Template CQDX_TMPLATE_ANSWER;

    //福建师范
    protected static Template FJSF_TMPLATE_PAPER;
    protected static Template FJSF_TMPLATE_ANSWER;

    //中国地质大学（北京）
    protected static Template CUGB_TMPLATE_PAPER;
    protected static Template CUGB_TMPLATE_ANSWER;

    //武汉市第二轻工业学校
    protected static Template WHEQX_TMPLATE_PAPER;
    protected static Template WHEQX_TMPLATE_ANSWER;

    //华南师范
    protected static Template HNSF_TMPLATE_PAPER;
    protected static Template HNSF_TMPLATE_ANSWER;

    //中国医科
    protected static Template YKDX_TMPLATE_PAPER;
    protected static Template YKDX_TMPLATE_ANSWER;

    //北京交通大学
    protected static Template BJJT_TMPLATE_PAPER;
    protected static Template BJJT_TMPLATE_ANSWER;

    //中国地质大学（武汉）
    protected static Template ZGDDW_TMPLATE_PAPER;
    protected static Template ZGDDW_TMPLATE_ANSWER;

    //中国地质大学（北京）
    protected static Template ZGDDB_TMPLATE_PAPER;
    protected static Template ZGDDB_TMPLATE_ANSWER;

    //东北师范大学
    protected static Template DBSF_TMPLATE_PAPER;
    protected static Template DBSF_TMPLATE_ANSWER;

    //南开大学
    protected static Template NKDX_TMPLATE_PAPER;
    protected static Template NKDX_TMPLATE_ANSWER;

    //北京航空航天大学
    protected static Template BJHK_TMPLATE_PAPER;
    protected static Template BJHK_TMPLATE_ANSWER;

    //华东师范大学
    protected static Template FDSF_TMPLATE_PAPER;
    protected static Template FDSF_TMPLATE_ANSWER;

    //原卷word A4 模板
    public static Template ORIGINAL_PAPER;

    public static Configuration CONFIGURATION;

    static {
        try {
            CONFIGURATION = new Configuration(Configuration.VERSION_2_3_25);
            // 设置编码
            CONFIGURATION.setDefaultEncoding(ENCODING);
            // 设置ftl模板路径
            CONFIGURATION.setClassForTemplateLoading(DocxProcessUtil.class, "/");

            CONFIGURATION = new Configuration(Configuration.VERSION_2_3_25);
            // 设置编码
            CONFIGURATION.setDefaultEncoding(ENCODING);
            // 设置ftl模板路径
            CONFIGURATION.setClassForTemplateLoading(DocxProcessUtil.class, "/");

            SXSF_TEMPLATE_PAPER = CONFIGURATION.getTemplate("sxsf_paper_template.ftl", ENCODING);
            SXSF_TEMPLATE_ANSWER = CONFIGURATION.getTemplate("sxsf_answer_template.ftl", ENCODING);
            SXSF_OUTLINE_TEMPLATE_PAPER = CONFIGURATION.getTemplate("sxsf_outline_paper_template.ftl", ENCODING);

            DZKD_TEMPLATE_PAPER = CONFIGURATION.getTemplate("dzkd_paper_template.ftl", ENCODING);
            DZKD_TEMPLATE_ANSWER = CONFIGURATION.getTemplate("dzkd_answer_template.ftl", ENCODING);

            HZKJ_TEMPLATE_PAPER = CONFIGURATION.getTemplate("hzkj_paper_template.ftl", ENCODING);
            HZKJ_TEMPLATE_ANSWER = CONFIGURATION.getTemplate("hzkj_answer_template.ftl", ENCODING);

            SDDX_TEMPLATE_PAPER = CONFIGURATION.getTemplate("sddx_paper_template.ftl", ENCODING);
            SDDX_TEMPLATE_ANSWER = CONFIGURATION.getTemplate("sddx_answer_template.ftl", ENCODING);

            TJDX_TEMPLATE_PAPER = CONFIGURATION.getTemplate("tjdx_paper_template.ftl", ENCODING);
            TJDX_TEMPLATE_ANSWER = CONFIGURATION.getTemplate("tjdx_answer_template.ftl", ENCODING);
            TJDX_OUTLINE_TEMPLATE_PAPER = CONFIGURATION.getTemplate("tjdx_outline_paper_template.ftl", ENCODING);

            XNJD_TEMPLATE_PAPER = CONFIGURATION.getTemplate("xnjd_paper_template.ftl", ENCODING);
            XNJD_TEMPLATE_ANSWER = CONFIGURATION.getTemplate("xnjd_answer_template.ftl", ENCODING);
            XNJD_OUTLINE_TEMPLATE_PAPER = CONFIGURATION.getTemplate("xnjd_outline_paper_template.ftl", ENCODING);

            SYDX_TEMPLATE_PAPER = CONFIGURATION.getTemplate("sydx_paper_template.ftl", ENCODING);
            SYDX_TEMPLATE_ANSWER = CONFIGURATION.getTemplate("sydx_answer_template.ftl", ENCODING);

            XAJD_TEMPLATE_PAPER = CONFIGURATION.getTemplate("xajd_paper_template.ftl", ENCODING);
            XAJD_TEMPLATE_ANSWER = CONFIGURATION.getTemplate("xajd_answer_template.ftl", ENCODING);

            RJGC_TMPLATE_PAPER = CONFIGURATION.getTemplate("rjgc_paper_template.ftl", ENCODING);
            RJGC_TMPLATE_ANSWER = CONFIGURATION.getTemplate("rjgc_answer_template.ftl", ENCODING);

            BJSF_TMPLATE_PAPER = CONFIGURATION.getTemplate("bjsf_paper_template.ftl", ENCODING);
            BJSF_TMPLATE_ANSWER = CONFIGURATION.getTemplate("bjsf_answer_template.ftl", ENCODING);

            CQDX_TMPLATE_PAPER = CONFIGURATION.getTemplate("cqdx_paper_template.ftl", ENCODING);
            CQDX_TMPLATE_ANSWER = CONFIGURATION.getTemplate("cqdx_answer_template.ftl", ENCODING);

            FJSF_TMPLATE_PAPER = CONFIGURATION.getTemplate("fjsf_paper_template.ftl", ENCODING);
            FJSF_TMPLATE_ANSWER = CONFIGURATION.getTemplate("fjsf_answer_template.ftl", ENCODING);

            CUGB_TMPLATE_PAPER = CONFIGURATION.getTemplate("cugb_paper_template.ftl", ENCODING);
            CUGB_TMPLATE_ANSWER = CONFIGURATION.getTemplate("cugb_answer_template.ftl", ENCODING);

            WHEQX_TMPLATE_PAPER = CONFIGURATION.getTemplate("wheqx_paper_template.ftl", ENCODING);
            WHEQX_TMPLATE_ANSWER = CONFIGURATION.getTemplate("wheqx_answer_template.ftl", ENCODING);

            HNSF_TMPLATE_PAPER = CONFIGURATION.getTemplate("hnsf_paper_template.ftl", ENCODING);
            HNSF_TMPLATE_ANSWER = CONFIGURATION.getTemplate("hnsf_answer_template.ftl", ENCODING);

            YKDX_TMPLATE_PAPER = CONFIGURATION.getTemplate("ykdx_paper_template.ftl", ENCODING);
            YKDX_TMPLATE_ANSWER = CONFIGURATION.getTemplate("ykdx_answer_template.ftl", ENCODING);

            BJJT_TMPLATE_PAPER = CONFIGURATION.getTemplate("bjjt_paper_template.ftl", ENCODING);
            BJJT_TMPLATE_ANSWER = CONFIGURATION.getTemplate("bjjt_answer_template.ftl", ENCODING);

            ZGDDW_TMPLATE_PAPER = CONFIGURATION.getTemplate("zgddw_paper_template.ftl", ENCODING);
            ZGDDW_TMPLATE_ANSWER = CONFIGURATION.getTemplate("zgddw_answer_template.ftl", ENCODING);

            ZGDDB_TMPLATE_PAPER = CONFIGURATION.getTemplate("zgddb_paper_template.ftl", ENCODING);
            ZGDDB_TMPLATE_ANSWER = CONFIGURATION.getTemplate("zgddb_answer_template.ftl", ENCODING);

            DBSF_TMPLATE_PAPER = CONFIGURATION.getTemplate("dbsf_paper_template.ftl", ENCODING);
            DBSF_TMPLATE_ANSWER = CONFIGURATION.getTemplate("dbsf_answer_template.ftl", ENCODING);

            NKDX_TMPLATE_PAPER = CONFIGURATION.getTemplate("nkdx_paper_template.ftl", ENCODING);
            NKDX_TMPLATE_ANSWER = CONFIGURATION.getTemplate("nkdx_answer_template.ftl", ENCODING);

            BJHK_TMPLATE_PAPER = CONFIGURATION.getTemplate("bjhk_paper_template.ftl", ENCODING);
            BJHK_TMPLATE_ANSWER = CONFIGURATION.getTemplate("bjhk_answer_template.ftl", ENCODING);

            FDSF_TMPLATE_PAPER = CONFIGURATION.getTemplate("fdsf_paper_template.ftl", ENCODING);
            FDSF_TMPLATE_ANSWER = CONFIGURATION.getTemplate("fdsf_answer_template.ftl", ENCODING);

            ORIGINAL_PAPER = CONFIGURATION.getTemplate("original_paper.ftl", ENCODING);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 下载试卷
     *
     * @param paperId
     * @param zipFileName
     * @throws Exception
     */
    public abstract void downloadPaper(String paperId, String zipFileName, String examType) throws Exception;

    /**
     * 下载答案
     *
     * @param paperId
     * @param zipFileName
     * @throws Exception
     */
    public abstract void downloadPaperAnswer(String paperId, String zipFileName) throws Exception;

    /**
     * 上传试卷相关文件
     *
     * @param extractConfig
     * @param paperId
     * @param exportStructure
     * @param accessUser
     * @throws Exception
     */
    public abstract void uploadFile(ExtractConfig extractConfig, String paperId, ExportStructure exportStructure, User accessUser, ExamPaper examPaper) throws Exception;

    /**
     * 设置选项号
     *
     * @param optionWordMl
     * @param num
     * @return
     * @throws Exception
     */
    public String setOptionNum(String optionWordMl, String num) throws Exception {
        String tmpStr = DocxProcessUtil.BODY_HEADER + optionWordMl + DocxProcessUtil.BODY_TAIL;
        Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
        List<Object> pList = body.getContent();
        int index = 0;
        for (Object pObj : pList) {
            if (index > 0) {
                break;
            }
            P p = (P) pObj;
            List<Object> pContent = p.getContent();
            R run = new R();
            Text text = new Text();
            text.setValue(num + ". ");
            run.getContent().add(text);
            pContent.add(0, run);
            index++;
        }
        StringBuffer pWordMl = new StringBuffer();
        for (Object pObj : pList) {
            if (pObj instanceof P) {
                pWordMl.append(DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj)));
            }
        }
        return pWordMl.toString();
    }

    /**
     * 将数字1,2,3,4转化成A,B,C,D
     *
     * @param number
     * @return
     */
    public String getOptionNum(int number) {
        char optionNum = (char) (65 + number);
        return String.valueOf(optionNum);
    }

    /**
     * 替换填空
     * 将###替换为下划线_______
     *
     * @param wordMl
     * @param num
     * @return
     * @throws Exception
     */
    public String replaceQuesBlank(String wordMl, int num) throws Exception {
        String tmpStr = DocxProcessUtil.BODY_HEADER + wordMl + DocxProcessUtil.BODY_TAIL;
        Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
        List<Object> pList = body.getContent();
        int index = 0;
        int cur = 0;
        Map<Integer, String> curMap = new HashMap<Integer, String>();
        for (Object pObj : pList) {
            if (pObj.getClass().equals(P.class)) {
                List<Object> pContent = ((P) pObj).getContent();
                for (Object rObj : pContent) {
                    if (rObj.getClass().equals(R.class)) {
                        List<Object> rContent = ((R) rObj).getContent();
                        for (Object tObj : rContent) {
                            if (tObj instanceof JAXBElement) {
                                tObj = ((JAXBElement<?>) tObj).getValue();
                            }
                            if (tObj.getClass().equals(Text.class)) {
                                Text text = (Text) tObj;
                                String str = text.getValue();
                                //1
                                if ("##".equals(str.trim())) {
                                    str = "___";
                                    text.setValue(str);
                                }
                                //2
                                str = str.replaceAll("###", "______");
                                text.setValue(str);
                                //3
                                Pattern pattern = Pattern.compile("##\\d{1,}##");
                                Matcher m = pattern.matcher(str);
                                while (m.find()) {
                                    int curNum = num + index;
                                    String a = m.group();
                                    str = str.replaceAll(a, "___" + (curNum) + "___");
                                    text.setValue(str);
                                    index++;
                                }
                                //4
	                               /*if(str.startsWith("#") || str.equals("___")){
	                                   curMap.put(cur,str);
	                                   text.setValue("");
	                               }*/
                                //5
                                if (str.matches("^\\d{1,}$")) {
                                    String preStr = curMap.get(cur - 1);
                                    int curNum = num + index;
                                    if (!StringUtils.isEmpty(preStr) && preStr.startsWith("#")) {
                                        text.setValue("___" + (curNum) + "___");
                                    }
                                    index++;
                                }
                                cur++;
                            }
                        }
                    }
                }
            }
        }
        StringBuffer pWordMl = new StringBuffer();
        for (Object pObj : pList) {
            if (pObj instanceof P) {
                pWordMl.append(DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj)));
            }
        }
        return pWordMl.toString();
    }

    /**
     * 设置题号
     *
     * @param quesBodyWordMl
     * @param num
     * @return
     * @throws Exception
     */
    public String setSubQuesNum(String quesBodyWordMl, int num) throws Exception {
        String tmpStr = DocxProcessUtil.BODY_HEADER + quesBodyWordMl + DocxProcessUtil.BODY_TAIL;
        Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
        List<Object> pList = body.getContent();
        int index = 0;
        for (Object pObj : pList) {
            if (index > 0) {
                break;
            }
            P p = (P) pObj;
            List<Object> pContent = p.getContent();
            R run = new R();
            Text text = new Text();
            text.setValue(num + ". ");
            run.getContent().add(text);
            pContent.add(0, run);
            index++;
        }
        StringBuffer pWordMl = new StringBuffer();
        for (Object pObj : pList) {
            if (pObj instanceof P) {
                pWordMl.append(DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj)));
            }
        }
        return pWordMl.toString();
    }

    /**
     * 处理Word的题干选项
     * 1.设置题号
     * 2.给小题选项进行排序
     * 3.设置选项号
     * 4.替换题干中的##为____
     */
    public void setQuestionNumberForWord(List<PaperDetailExp> details) throws Exception {
        if (CollectionUtils.isEmpty(details)) {
            return;
        }

        for (PaperDetailExp detail : details) {
            List<PaperDetailUnitExp> units = detail.getPaperDetailUnits();
            for (PaperDetailUnitExp unit : units) {
                Question question = unit.getQuestion();

                //定义一个字段，用来判断是否为选择题
                boolean isOption = false;
                List<QuesOption> options = question.getQuesOptions();
                if (CollectionUtils.isNotEmpty(options)) {
                    isOption = true;
                    int index = 0;
                    for (QuesOption option : options) {
                        option.setOptionBodyWord(setOptionNum(option.getOptionBodyWord(), getOptionNum(index)));
                        index++;
                    }
                }

                if (isOption) {
                    //检测选项是否换行
                    int i = optionLine(options);
                    if (i == 4) {
                        optionListFourline(options);
                    }
                    if (i == 2) {
                        optionListTwoline(options);
                    }
                }

                List<Question> subQuestions = question.getSubQuestions();
                if (CollectionUtils.isNotEmpty(subQuestions)) {
                    //套题主题干
                    question.setQuesBodyWord(replaceQuesBlank(question.getQuesBodyWord(), subQuestions.get(0).getNumber()));

                    for (Question subQues : subQuestions) {
                        //处理子题题干，答案
                        subQues.setQuesBodyWord(setSubQuesNum(subQues.getQuesBodyWord(), subQues.getNumber()));
                        subQues.setQuesAnswerWord(setSubQuesNum(subQues.getQuesAnswerWord(), subQues.getNumber()));
                        subQues.setQuesBodyWord(replaceQuesBlank(subQues.getQuesBodyWord(), subQues.getNumber()));

                        List<QuesOption> subOptions = subQues.getQuesOptions();
                        if (CollectionUtils.isNotEmpty(subOptions)) {
                            int subIndex = 0;
                            for (QuesOption subOption : subOptions) {
                                subOption.setOptionBodyWord(setOptionNum(subOption.getOptionBodyWord(), getOptionNum(subIndex)));
                                subIndex++;
                            }

                            //检测选项是否换行
                            int i = optionLine(subOptions);
                            if (i == 4) {
                                optionListFourline(subOptions);
                            }
                            if (i == 2) {
                                optionListTwoline(subOptions);
                            }
                        }
                    }
                } else {
                    //处理非套题题干、答案
                    question.setQuesBodyWord(setSubQuesNum(question.getQuesBodyWord(), unit.getNumber()));
                    question.setQuesAnswerWord(setSubQuesNum(question.getQuesAnswerWord(), unit.getNumber()));

                    question.setQuesBodyWord(replaceQuesBlank(question.getQuesBodyWord(), unit.getNumber()));
                }
            }
        }
    }

    /**
     * 检测同一行可以放多少个选项
     *
     * @param optionList
     * @return
     * @throws Exception
     */
    private int optionLine(List<QuesOption> optionList) throws Exception {
        int four = 0;
        String tmpText = "";
        for (int i = 0; i < optionList.size(); i++) {
            QuesOption quesOption = optionList.get(i);
            List<Object> pList = getPlist(quesOption);
            if (pList == null || pList.size() == 0) {
                four++;
                continue;
            }
            //判断是否为纯文本
            boolean isText = DocxProcessUtil.isText((P) pList.get(0));
            if (isText) {
                tmpText = DocxProcessUtil.getPText((P) pList.get(0));
                //如果四个选项在同一行，长度不能超过64
                if (optionsLength(tmpText) < 18) {
                    four++;
                } else if (optionsLength(tmpText) < 42) {
                    //什么都不做
                } else {
                    return 1;
                }
            } else {
                return 1;
            }
        }
        if (four == optionList.size()) {
            return 4;
        } else {
            return 2;
        }
    }

    /**
     * 根据选项得到选项段落
     *
     * @param quesOption
     * @return
     * @throws Exception
     */
    private List<Object> getPlist(QuesOption quesOption) throws Exception {
        String optionBodyWordMl = quesOption.getOptionBodyWord();
        String tmpStr = DocxProcessUtil.BODY_HEADER + optionBodyWordMl + DocxProcessUtil.BODY_TAIL;
        Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
        List<Object> pList = body.getContent();
        return pList;
    }

    /**
     * 计算选项长度
     *
     * @param tmpText
     * @return
     */
    public int optionsLength(String tmpText) {
        String regEx = "[\\u4e00-\\u9fa5]";
        String term = tmpText.replaceAll(regEx, "aa");
        return term.length();
    }

    /**
     * 两个选项在同一行
     *
     * @param optionList
     * @throws Exception
     */
    public void optionListTwoline(List<QuesOption> optionList) throws Exception {
        for (int i = 0; i < optionList.size(); i++) {
            Boolean isAddTbale = false;
            QuesOption quesOption = optionList.get(i);
            String optionBodyWordMl = quesOption.getOptionBodyWord();
            String tmpStr = DocxProcessUtil.BODY_HEADER + optionBodyWordMl + DocxProcessUtil.BODY_TAIL;
            Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
            List<Object> pList = body.getContent();
            //首选项添加tabs标签
            if (i % 2 == 0) {
                isAddTbale = true;
                if (pList.size() > 0) {
                    P p = (P) pList.get(0);
                    //添加tabs标签
                    addTabs(p, 1);
                    StringBuffer pWordMl = new StringBuffer();
                    for (Object pObj : pList) {
                        if (pObj instanceof P) {
                            pWordMl.append(DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj)));
                        }
                    }
                    quesOption.setOptionBodyWord(pWordMl.toString());
                }
            } else {
                if (pList.size() > 0) {
                    String tmpText = DocxProcessUtil.getPText((P) pList.get(0));
                    optionMerge(optionList.get((i / 2) * 2), tmpText, true);
                    quesOption.setOptionBodyWord("");
                }
            }
        }
    }

    /**
     * 四个选项在同一行
     */
    public void optionListFourline(List<QuesOption> optionList) throws Exception {
        for (int i = 0; i < optionList.size(); i++) {
            Boolean isAddTbale = false;
            QuesOption quesOption = optionList.get(i);
            String optionBodyWordMl = quesOption.getOptionBodyWord();
            String tmpStr = DocxProcessUtil.BODY_HEADER + optionBodyWordMl + DocxProcessUtil.BODY_TAIL;
            Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
            List<Object> pList = body.getContent();
            P p = null;
            if (pList == null || pList.size() == 0) {
                p = new P();
            } else {
                p = (P) pList.get(0);
            }
            //首选项添加tabs标签
            if (i % 4 == 0) {
                isAddTbale = true;
                //添加tabs标签
                addTabs(p, 3);
                StringBuffer pWordMl = new StringBuffer();
                for (Object pObj : pList) {
                    if (pObj instanceof P) {
                        pWordMl.append(DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj)));
                    }
                }
                quesOption.setOptionBodyWord(pWordMl.toString());
            } else {
                String tmpText = DocxProcessUtil.getPText(p);
                if ((i + 1) % 4 == 0) {
                    isAddTbale = true;
                }
                optionMerge(optionList.get((i / 4) * 4), tmpText, isAddTbale);
                quesOption.setOptionBodyWord("");
            }
        }
    }

    /**
     * 在p标签中添加tabs标签
     *
     * @param p
     */
    public void addTabs(P p, int tabNumbers) {
        //添加tabs标签
        PPr pPr = p.getPPr();
        if (pPr == null) {
            pPr = new PPr();
            p.getContent().add(0, pPr);
        }
        Tabs tabs = new Tabs();
        if (tabNumbers == 3) {
            //添加tabs
            CTTabStop tab1 = new CTTabStop();
            tab1.setVal(STTabJc.LEFT);
            tab1.setPos(BigInteger.valueOf(2268));
            CTTabStop tab2 = new CTTabStop();
            tab2.setVal(STTabJc.LEFT);
            tab2.setPos(BigInteger.valueOf(4536));
            CTTabStop tab3 = new CTTabStop();
            tab3.setVal(STTabJc.LEFT);
            tab3.setPos(BigInteger.valueOf(7230));
            tabs.getTab().add(tab1);
            tabs.getTab().add(tab2);
            tabs.getTab().add(tab3);
        } else {
            CTTabStop tab1 = new CTTabStop();
            tab1.setVal(STTabJc.LEFT);
            tab1.setPos(BigInteger.valueOf(4536));
            tabs.getTab().add(tab1);
        }
        pPr.setTabs(tabs);
        List<Object> pContent = p.getContent();
        addTab(pContent);
    }

    /**
     * 每行添加tab标签
     *
     * @param content
     */
    public void addTab(List<Object> content) {
        R run = new R();
        R.Tab tab = new R.Tab();
        run.getContent().add(tab);
        content.add(run);
    }

    /**
     * 选项合并
     *
     * @param tmpText
     * @param isLast
     * @throws Exception
     */
    public void optionMerge(QuesOption option, String tmpText, Boolean isLast) throws Exception {
        String optionBodyWordMl = option.getOptionBodyWord();
        String tmpStr = DocxProcessUtil.BODY_HEADER + optionBodyWordMl + DocxProcessUtil.BODY_TAIL;
        Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
        List<Object> pList = body.getContent();
        if (pList.size() > 0) {
            P p = (P) pList.get(pList.size() - 1);
            List<Object> pContent = p.getContent();
            R run = new R();
            Text text = new Text();
            text.setValue(tmpText);
            run.getContent().add(text);
            pContent.add(run);
            //每个选项添加tab结束标签
            if (!isLast) {
                addTab(pContent);
            }
        }
        StringBuffer pWordMl = new StringBuffer();
        for (Object pObj : pList) {
            if (pObj instanceof P) {
                pWordMl.append(DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj)));
            }
        }
        option.setOptionBodyWord(pWordMl.toString());
    }

    /**
     * 设置大题标题
     */
    public void setExportQuestionTitle(List<PaperDetailExp> details, boolean isWord) {
        if (CollectionUtils.isEmpty(details)) {
            return;
        }

        final String style = "<em class=\"e_num\">%s</em>";

        for (PaperDetailExp detail : details) {
            String perQuesScore = checkPaperDetailUnitScore(detail);
            if (perQuesScore != null) {
                perQuesScore = CommonUtils.clearZeroPoint(perQuesScore);
                if (!isWord) {
                    perQuesScore = String.format(style, perQuesScore);
                }
                perQuesScore = String.format("每小题%s分，", perQuesScore);
            } else {
                perQuesScore = "";//每题分数不一致则不注明
            }

            QuesStructType type = detail.getPaperDetailUnits().get(0).getQuestionType();
            String detailScore = CommonUtils.formatValue(detail.getScore());
            String unitCount = String.valueOf(detail.getUnitCount());

            if (!isWord) {
                detailScore = String.format(style, detailScore);
                unitCount = String.format(style, unitCount);
            }
            String title = String.format("（本大题共%s小题，%s共%s分）", unitCount, perQuesScore, detailScore);

            if (type == QuesStructType.SINGLE_ANSWER_QUESTION) {
                detail.setTitleDetail("在每小题列出的备选项中只有一个符合题目要求的，请将其选出并将“答题卡”的相应代码涂黑，错涂、多涂或未涂均无分");
            } else if (type == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
                detail.setTitleDetail("在每小题列出的备选项中有一个或多个符合题目要求的，请将符合项选出并将“答题卡”的相应代码涂黑");
            } else if (type == QuesStructType.BOOL_ANSWER_QUESTION) {
                detail.setTitleDetail("正确的填涂√、错误填涂×。错涂、多涂或未涂均无分");
            }

            detail.setTitle(title);
        }
    }

    /**
     * 创建机考文件，并打包上传至又拍云
     *
     * @param extractConfig
     * @param accessUser
     * @throws IOException
     */
    protected void uploadComputerTestFile(ExtractConfig extractConfig, User accessUser, ExamPaper examPaper) throws IOException {
        List<ComputerTestPaper> computerTestPaperList = buildComputerTestJsonService.buildComputerTestPapers(extractConfig);
        String currentTimeStr = CommonUtils.getCurNum();
        for (int i = 0; i < computerTestPaperList.size(); i++) {
            ComputerTestPaper computerTestPaper = computerTestPaperList.get(i);
            String jsonDirectoryName = currentTimeStr + UNDERSCORE + ExamFileType.COMPUTERTEST_PACKAGE.name() + UNDERSCORE + i;
            String jsonDirectoryPath = TEMP_FILE_EXP + File.separator + jsonDirectoryName;
            //新建文件夹
            File dirFile = new File(jsonDirectoryPath);
            if (!dirFile.exists()) {
                dirFile.mkdirs();
            }
            //下载试卷中的音频文件到jsonDirectoryPath文件夹中
            downloadQuestionAudio(computerTestPaper, jsonDirectoryPath, extractConfig.getExamId() + "");
            //将computerTestPaper对象生成JSON文件存放在jsonDirectoryPath中
            makeComputerTestPaperToJsonFile(extractConfig, computerTestPaper, jsonDirectoryPath);
            //将文件夹打包成zip压缩包放在docxExport下
            FileDisposeUtil.fileToZip(jsonDirectoryPath, "docxExport", jsonDirectoryName);
            //上传zip压缩包到又拍云
            File zipFile = new File(TEMP_FILE_EXP + jsonDirectoryName + ZIP_SUFFIX);
            String zipUpyunFilePath = upYunProperty.getUploadPath() + extractConfig.getOrgId() + "/" + jsonDirectoryName + ZIP_SUFFIX;
            UpYun upyun = new UpYun(upYunProperty.getBucketName(), upYunProperty.getUserName(), upYunProperty.getPassword());
            upyun.writeFile(zipUpyunFilePath, zipFile, true);
            //保存数据库记录
            ExamFile examFile = new ExamFile(computerTestPaper, extractConfig, zipUpyunFilePath, ExamFileType.COMPUTERTEST_PACKAGE, ZIP_SUFFIX);
            examFile.setGroupCode(examPaper.getGroupCode());
            examFileService.saveExamFile(examFile, accessUser);
            //删除本地生成的文件
            zipFile.delete();
            FileUtils.deleteQuietly(dirFile);
        }
    }

    /**
     * 将computerTestPaper对象生成JSON文件存放在jsonDirectoryPath中
     *
     * @param extractConfig
     * @param computerTestPaper
     * @param jsonDirectoryPath
     */
    private void makeComputerTestPaperToJsonFile(ExtractConfig extractConfig, ComputerTestPaper computerTestPaper, String jsonDirectoryPath) {
        //创建新的JSON文件
        File file = new File(jsonDirectoryPath + File.separator + extractConfig.getCourseCode() + ".json");
        //将对象转成 json对象
        Gson gson = new Gson();
        String strJSON = gson.toJson(computerTestPaper);
        strJSON = CommonUtils.replaceUnicodeStr(strJSON);
        //生成文件流写入JSON文件
        FileOutputStream outputStream;
        try {
            outputStream = new FileOutputStream(file);
            byte b[] = strJSON.getBytes();
            outputStream.write(b);
            outputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 下载题目音频文件
     *
     * @param computerTestPaper
     */
    private void downloadQuestionAudio(ComputerTestPaper computerTestPaper, String jsonDirectoryPath, String examId) {
        List<ComputerTestPaperDetail> details = computerTestPaper.getDetails();
        for (ComputerTestPaperDetail detail : details) {
            List<ComputerTestQuestion> questions = detail.getQuestions();
            for (ComputerTestQuestion computerTestQuestion : questions) {
                getBodyAndOptionAudioFile(computerTestQuestion, computerTestQuestion.getBody(), jsonDirectoryPath, computerTestPaper, examId, detail, null, 1);
                List<ComputerTestOption> options = computerTestQuestion.getOptions();
                for (ComputerTestOption option : options) {
                    getBodyAndOptionAudioFile(computerTestQuestion, option.getBody(), jsonDirectoryPath, computerTestPaper, examId, detail, option, 2);
                }
                //子题音频处理
                //List<ComputerTestQuestion> subQuestions = question.getSubQuestions();
            }
        }
    }

    private void getBodyAndOptionAudioFile(ComputerTestQuestion computerTestQuestion, Sections bodySections, String jsonDirectoryPath,
                                           ComputerTestPaper computerTestPaper, String examId, ComputerTestPaperDetail detail, ComputerTestOption option, int number) {
        List<Section> sectionList = bodySections.getSections();
        int bNum = 1;
        int oNum = 1;
        for (Section section : sectionList) {
            List<Block> blocks = section.getBlocks();
            for (Block block : blocks) {
                if ("audio".equals(block.getType())) {
                    computerTestPaper.setHasVideo(1);
                    PaperDetailUnit paperDetailUnit = Model.of(paperDetailUnitRepo.findById(computerTestQuestion.getId()));
                    QuestionAudio audio = questionAudioService.findAudioById(block.getValue());
                    //block.setValue(block.getValue()+"."+audio.getFileSuffixes());
                    if (audio != null) {
                        AudioTimeConfig audioTime = new AudioTimeConfig();
                        audioTime.setCourseCode(computerTestPaper.getCourseCode());
                        audioTime.setExamId(examId);
                        audioTime.setGroupCode(computerTestPaper.getGroupCode());
                        audioTime.setPaperDetailUnit(paperDetailUnit);
                        AudioTimeConfig audioTimeConfig = Model.of(audioTimeConfigRepo.findOne(Example.of(audioTime)));
                        if (audioTimeConfig != null) {
                            if (audioTimeConfig.getPlayTime() == null) {
                                block.setPlayTime(-1);
                            } else {
                                block.setPlayTime(audioTimeConfig.getPlayTime());
                            }
                        } else {
                            block.setPlayTime(-1);
                        }
                        String audioFileName = "";
                        if (number == 1) {
                            audioFileName = audio.getId() + "_" + detail.getNumber() + "_" + computerTestQuestion.getNumber()
                                    + "_1_" + bNum + "." + audio.getFileSuffixes();
                            bNum++;
                        } else {
                            audioFileName = audio.getId() + "_" + detail.getNumber() + "_" + computerTestQuestion.getNumber()
                                    + "_2_" + option.getNumber() + oNum + "." + audio.getFileSuffixes();
                            oNum++;
                        }
                        FileDisposeUtil.saveUrlAs(upYunProperty.getDownloadUrl() + audio.getFileUrl(), jsonDirectoryPath + File.separator + audioFileName);
                    }
                }
            }
        }
    }

    /**
     * 生成试卷或答案Word，上传至又拍云
     */
    protected void uploadPaperOrAnswerFile(PaperExp paperExp, ExtractConfig extractConfig, User accessUser, String currNum,
                                           Template template, ExamFileType examFileType, ExamPaper examPaper) {
        String paperfileName = currNum + examFileType.name() + DOCX_SUFFIX;
        try {
            DocxProcessUtil.exportWord(paperExp, paperfileName, template);
            DocxProcessUtil.processImage(paperfileName, getPkgList(paperExp.getId()));
            File file = new File(TEMP_FILE_EXP + paperfileName);
            String paperFilePath = upYunProperty.getUploadPath() + extractConfig.getOrgId() + "/" + paperfileName;
            UpYun upyun = new UpYun(upYunProperty.getBucketName(), upYunProperty.getUserName(), upYunProperty.getPassword());
            upyun.writeFile(paperFilePath, file, true);
            ExamFile examFile = new ExamFile(paperExp.getId(), extractConfig, paperFilePath, examFileType, DOCX_SUFFIX);
            examFile.setGroupCode(examPaper.getGroupCode());
            examFile.setFileName(extractConfig.getCourseName() + "_" +
                    extractConfig.getCourseCode() + "_" +
                    examFileType.getName() + "_" +
                    examFile.getGroupCode() + DOCX_SUFFIX);
            examFileService.saveExamFile(examFile, accessUser);
            file.delete();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成试卷结构，上传至又拍云
     */
    protected void uploadPaperStructure(PaperExp paperExp, ExtractConfig extractConfig, User accessUser, String currNum, List<QuestionTypeNum> questionTypeNums, ExamPaper examPaper) {
        exportObjectiveQuestionStructures(paperExp, extractConfig, accessUser, currNum, questionTypeNums, examPaper);
        exportSubjectiveQuestionStructures(paperExp, extractConfig, accessUser, currNum, examPaper);
    }

    /**
     * 获得客观题导出结构，并上传至又拍云
     *
     * @param paperExp
     * @return
     */
    protected void exportObjectiveQuestionStructures(PaperExp paperExp, ExtractConfig extractConfig, User accessUser, String currNum, List<QuestionTypeNum> questionTypeNums, ExamPaper examPaper) {
        String objectiveFilename = currNum + ExamFileType.PAPER_STRUCTURE_OBJECTIVE.name() + EXCEL_SUFFIX;
        //得到试卷中的客观大题
        List<PaperDetailExp> objectiveDetails = getAllObjectiveDetails(paperExp);
        //根据试卷结构导出设置中的数量补齐客观题
        List<PaperDetailExp> paperDetailExps = fillObjectiveQuestions(objectiveDetails, questionTypeNums);
        List<ObjectiveQuestionStructure> objectiveQuestionStructureList = new ArrayList<ObjectiveQuestionStructure>();

        //String paperType = findPaperType(extractConfig,paperExp.getId());
        String paperType = examPaper.getGroupCode();
        for (PaperDetailExp paperDetailExp : paperDetailExps) {
            for (PaperDetailUnitExp unit : paperDetailExp.getPaperDetailUnits()) {
                objectiveQuestionStructureList.add(new ObjectiveQuestionStructure(paperExp, paperDetailExp, unit, paperType));
            }
        }
        ExcelWriter objectiveExcelExporter = new ExcelWriter(ObjectiveQuestionStructure.class);
        try {
            File file = new File(TEMP_FILE_EXP + objectiveFilename);
            FileOutputStream out = new FileOutputStream(file);
            objectiveExcelExporter.write(objectiveFilename, objectiveQuestionStructureList, out);
            String objectiveFilePath = upYunProperty.getUploadPath() + extractConfig.getOrgId() + "/" + objectiveFilename;
            UpYun upyun = new UpYun(upYunProperty.getBucketName(), upYunProperty.getUserName(), upYunProperty.getPassword());
            upyun.writeFile(objectiveFilePath, file, true);
            ExamFile examFile = new ExamFile(paperExp.getId(), extractConfig, objectiveFilePath, ExamFileType.PAPER_STRUCTURE_OBJECTIVE, EXCEL_SUFFIX);
            examFile.setGroupCode(examPaper.getGroupCode());
            examFile.setFileName(extractConfig.getCourseName() + "_" + extractConfig.getCourseCode() + "_" + ExamFileType.PAPER_STRUCTURE_OBJECTIVE + "_" + examPaper.getGroupCode() + EXCEL_SUFFIX);
            examFileService.saveExamFile(examFile, accessUser);
            file.delete();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获得主观题导出结构，并上传至又拍云
     *
     * @param paperExp
     * @return
     */
    protected void exportSubjectiveQuestionStructures(PaperExp paperExp, ExtractConfig extractConfig, User accessUser, String currNum, ExamPaper examPaper) {
        String subjectiveFileName = currNum + ExamFileType.PAPER_STRUCTURE_SUBJECTIVE.name() + EXCEL_SUFFIX;
        List<PaperDetailExp> subjectiveDetails = getAllSubjectiveDetails(paperExp);
        List<SubjectiveQuestionStructure> subjectiveQuestionStructureList = new ArrayList<SubjectiveQuestionStructure>();
        //String paperType = findPaperType(extractConfig,paperExp.getId());
        String paperType = examPaper.getGroupCode();
        for (PaperDetailExp paperDetailExp : subjectiveDetails) {
            for (PaperDetailUnitExp unit : paperDetailExp.getPaperDetailUnits()) {
                subjectiveQuestionStructureList.add(new SubjectiveQuestionStructure(paperExp, paperDetailExp, unit, paperType));
            }
        }
        ExcelWriter subjectiveExcelExporter = new ExcelWriter(SubjectiveQuestionStructure.class);
        try {
            File file = new File(TEMP_FILE_EXP + subjectiveFileName);
            FileOutputStream out = new FileOutputStream(file);
            subjectiveExcelExporter.write(subjectiveFileName, subjectiveQuestionStructureList, out);
            String subjectiveFilePath = upYunProperty.getUploadPath() + extractConfig.getOrgId() + "/" + subjectiveFileName;
            UpYun upyun = new UpYun(upYunProperty.getBucketName(), upYunProperty.getUserName(), upYunProperty.getPassword());
            upyun.writeFile(subjectiveFilePath, file, true);
            ExamFile examFile = new ExamFile(paperExp.getId(), extractConfig, subjectiveFilePath, ExamFileType.PAPER_STRUCTURE_SUBJECTIVE, EXCEL_SUFFIX);
            examFile.setGroupCode(examPaper.getGroupCode());
            examFile.setFileName(extractConfig.getCourseName() + "_" + extractConfig.getCourseCode() + "_" + ExamFileType.PAPER_STRUCTURE_SUBJECTIVE + "_" + examPaper.getGroupCode() + EXCEL_SUFFIX);
            examFileService.saveExamFile(examFile, accessUser);
            file.delete();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据试卷结构导出设置中的数量补齐客观题
     *
     * @param objectiveDetails
     * @param questionTypeNums
     */
    public List<PaperDetailExp> fillObjectiveQuestions(List<PaperDetailExp> objectiveDetails, List<QuestionTypeNum> questionTypeNums) {
        //1.得到所有客观题大题的类型
        Set<QuesStructType> types = new HashSet<QuesStructType>();
        for (PaperDetailExp paperDetailExp : objectiveDetails) {
            types.add(QuesStructType.getQuesStructTypeById(paperDetailExp.getSortNumber()));
        }
        List<PaperDetailExp> paperDetailExps = new ArrayList<PaperDetailExp>();
        //如果没有单选
        if (!types.contains(QuesStructType.SINGLE_ANSWER_QUESTION)) {
            buildObjectiveQuestion(paperDetailExps, questionTypeNums, QuesStructType.SINGLE_ANSWER_QUESTION);
        } else {
            addObjectiveQuestion(paperDetailExps, objectiveDetails, questionTypeNums, QuesStructType.SINGLE_ANSWER_QUESTION);
        }
        //如果没有多选
        if (!types.contains(QuesStructType.MULTIPLE_ANSWER_QUESTION)) {
            buildObjectiveQuestion(paperDetailExps, questionTypeNums, QuesStructType.MULTIPLE_ANSWER_QUESTION);
        } else {
            addObjectiveQuestion(paperDetailExps, objectiveDetails, questionTypeNums, QuesStructType.MULTIPLE_ANSWER_QUESTION);
        }
        //如果没有判断
        if (!types.contains(QuesStructType.BOOL_ANSWER_QUESTION)) {
            buildObjectiveQuestion(paperDetailExps, questionTypeNums, QuesStructType.BOOL_ANSWER_QUESTION);
        } else {
            addObjectiveQuestion(paperDetailExps, objectiveDetails, questionTypeNums, QuesStructType.BOOL_ANSWER_QUESTION);
        }
        return paperDetailExps;
    }

    /**
     * 根据数量和类型构建客观题
     *
     * @param questionTypeNums
     * @param quesStructType
     * @return
     */
    private void buildObjectiveQuestion(List<PaperDetailExp> paperDetailExps, List<QuestionTypeNum> questionTypeNums, QuesStructType quesStructType) {
        //1.得到需要的题量
        int quantity = getQuantity(questionTypeNums, quesStructType);
        //2.构建题目
        PaperDetailExp paperDetailExp = new PaperDetailExp();
        List<PaperDetailUnitExp> paperDetailUnits = new ArrayList<PaperDetailUnitExp>();
        for (int i = 0; i < quantity; i++) {
            paperDetailUnits.add(new PaperDetailUnitExp(quesStructType, i + 1));
        }
        paperDetailExp.setPaperDetailUnits(paperDetailUnits);
        paperDetailExp.setNumber(Integer.parseInt(quesStructType.getId() + ""));
        paperDetailExps.add(paperDetailExp);
    }

    /**
     * 补齐需要的客观题
     *
     * @param objectiveDetails
     * @param questionTypeNums
     */
    private void addObjectiveQuestion(List<PaperDetailExp> paperDetailExps, List<PaperDetailExp> objectiveDetails, List<QuestionTypeNum> questionTypeNums, QuesStructType quesStructType) {
        //得到当前题量
        int currentSize = 0;
        /**
         * 构建新的大题
         * 遍历所有的客观大题，如果是符合的类型，就添加该大题
         * 该大题中，如果有套题，要将该套题下的小题拆出来，添加到大题中，并删除原套题
         */
        for (PaperDetailExp paperDetailExp : objectiveDetails) {
            if (paperDetailExp.getSortNumber() == quesStructType.getId()) {
                PaperDetailExp newPaperDetailExp = new PaperDetailExp();
                List<PaperDetailUnitExp> newPaperDetailUnits = new ArrayList<PaperDetailUnitExp>();
                //得到大题下的所有小题
                List<PaperDetailUnitExp> paperDetailUnits = paperDetailExp.getPaperDetailUnits();
                for (PaperDetailUnitExp paperDetailUnitExp : paperDetailUnits) {
                    if (paperDetailUnitExp.getQuestionType() != QuesStructType.NESTED_ANSWER_QUESTION) {
                        newPaperDetailUnits.add(paperDetailUnitExp);
                        currentSize += 1;
                    } else {
                        List<Question> subQuestions = paperDetailUnitExp.getQuestion().getSubQuestions();
                        if (subQuestions != null && subQuestions.size() > 0) {
                            for (int i = 0; i < subQuestions.size(); i++) {
                                newPaperDetailUnits.add(new PaperDetailUnitExp(quesStructType, subQuestions.get(i)));
                            }
                            currentSize += subQuestions.size();
                        }
                    }
                }
                //设置小题
                newPaperDetailExp.setPaperDetailUnits(newPaperDetailUnits);
                //设置大题题号
                newPaperDetailExp.setNumber(Integer.parseInt(quesStructType.getId() + ""));
                newPaperDetailExp.setSortNumber(paperDetailExp.getSortNumber());
                paperDetailExps.add(newPaperDetailExp);
            }
        }

        //刷一遍number
        int num = 0;
        for (int i = 0; i < paperDetailExps.size(); i++) {
            if (paperDetailExps.get(i).getSortNumber() == quesStructType.getId()) {
                List<PaperDetailUnitExp> exps = paperDetailExps.get(i).getPaperDetailUnits();
                for (int j = 0; j < exps.size(); j++) {
                    num++;
                    exps.get(j).setNumber(num);
                }
            }
        }

        //计算差额、补齐试题
        int quantity = getQuantity(questionTypeNums, quesStructType) - currentSize;
        PaperDetailExp paperDetailExp = new PaperDetailExp();
        List<PaperDetailUnitExp> paperDetailUnits = new ArrayList<PaperDetailUnitExp>();
        for (int i = 0; i < quantity; i++) {
            int number = currentSize + (i + 1);
            paperDetailUnits.add(new PaperDetailUnitExp(quesStructType, number));
        }
        paperDetailExp.setPaperDetailUnits(paperDetailUnits);
        //设置大题题号
        paperDetailExp.setNumber(Integer.parseInt(quesStructType.getId() + ""));
        paperDetailExps.add(paperDetailExp);
    }

    //根据类型得到需要的题量
    private Integer getQuantity(List<QuestionTypeNum> questionTypeNums, QuesStructType quesStructType) {
        int quantity = 0;
        for (QuestionTypeNum typeNum : questionTypeNums) {
            if (typeNum.getQuestionType() == quesStructType) {
                quantity = typeNum.getQuantity();
            }
        }
        return quantity;
    }

    /**
     * 得到试卷中所有客观大题
     * 与试题类型枚举:QuesStructType中的ID对应
     *
     * @param paperExp
     * @return
     */
    public List<PaperDetailExp> getAllObjectiveDetails(PaperExp paperExp) {
        List<PaperDetailExp> objectiveDetails = new ArrayList<PaperDetailExp>();
        for (PaperDetailExp paperDetailExp : paperExp.getPaperDetails()) {
            if (paperDetailExp.getSortNumber() == 1L
                    || paperDetailExp.getSortNumber() == 2L
                    || paperDetailExp.getSortNumber() == 3L) {
                objectiveDetails.add(paperDetailExp);
            }
        }
        return objectiveDetails;
    }

    /**
     * 得到所有主观大题
     * 与试题类型枚举:QuesStructType中的ID对应
     *
     * @param paperExp
     * @return
     */
    public List<PaperDetailExp> getAllSubjectiveDetails(PaperExp paperExp) {
        List<PaperDetailExp> subjectiveDetails = new ArrayList<PaperDetailExp>();
        for (PaperDetailExp paperDetailExp : paperExp.getPaperDetails()) {
            if (paperDetailExp.getSortNumber() == 4L
                    || paperDetailExp.getSortNumber() == 5L
                    || paperDetailExp.getSortNumber() == 6L) {
                subjectiveDetails.add(paperDetailExp);
            }
        }
        return subjectiveDetails;
    }

    /**
     * 检查客观题数量是否小于试卷结构导出设置的数量
     *
     * @param paperExp
     * @param questionTypeNums
     * @return
     */
    protected void checkObjectiveDetailsNum(PaperExp paperExp, List<QuestionTypeNum> questionTypeNums) {
        List<PaperDetailExp> objectiveDetails = getAllObjectiveDetails(paperExp);
        for (QuestionTypeNum typeNum : questionTypeNums) {
            List<PaperDetailUnitExp> paperDetailUnits = new ArrayList<PaperDetailUnitExp>();
            for (PaperDetailExp paperDetailExp : objectiveDetails) {
                if (paperDetailExp.getSortNumber() == typeNum.getQuestionType().getId()) {
                    paperDetailUnits.addAll(paperDetailExp.getPaperDetailUnits());
                }
            }
            int currentSize = 0;
            for (PaperDetailUnitExp paperDetailUnitExp : paperDetailUnits) {
                if (paperDetailUnitExp.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
                    currentSize += paperDetailUnitExp.getQuestion().getSubQuestions().size();
                } else {
                    currentSize++;
                }
            }
            if (currentSize > typeNum.getQuantity()) {
                throw new RuntimeException("试卷中" + typeNum.getQuestionType().getName() + "题题量超出设定范围"
                        + ",当前试卷题量:" + currentSize
                        + ",设定题量:" + typeNum.getQuantity()
                        + ",请核实后重新设置导出规则！");
            }
        }
    }

    /**
     * 获取当前试卷下所有试题WordPkg
     *
     * @param id
     * @return
     */
    protected List<WordprocessingMLPackage> getPkgList(String id) {
        Paper paper = Model.of(paperRepo.findById(id));
        List<WordprocessingMLPackage> wordMLPackages = paperDetailUnitRepo.findByPaperOrderByNumber(paper)
                .stream().map(PaperDetailUnit::getQuestion).collect(Collectors.toList())
                .stream().map(question -> getPkgObj(question)).collect(Collectors.toList());
        return wordMLPackages;
    }

    private WordprocessingMLPackage getPkgObj(Question question) {
        String pkgPathId = question.getQuesPkgPathId();
        QuestionPkgPath quesPkg = quesPkgPathRepo.findFirstById(pkgPathId);
        if (quesPkg == null) {
            byte[] bytes = new byte[0];
            return DocxProcessUtil.getPkg(bytes);
        }
        byte[] pkgByte = quesPkg.getQuesPkg();
        return DocxProcessUtil.getPkg(pkgByte);
    }

    /**
     * 筛选出试卷类型
     *
     * @param extractConfig
     * @param paperId
     * @return
     */
    @SuppressWarnings("unused")
    private String findPaperType(ExtractConfig extractConfig, String paperId) {
        String paperType = "";
        Map<String, String> paperIdMap = extractConfig.getFinishedPaperIdMap();
        Set<Entry<String, String>> entry = paperIdMap.entrySet();
        Iterator<Entry<String, String>> iterator = entry.iterator();
        while (iterator.hasNext()) {
            Entry<String, String> next = iterator.next();
            if (paperId.equals(next.getValue())) {
                paperType = next.getKey();
                break;
            }
        }
        return paperType;
    }


    /**
     * 下载试卷音频
     *
     * @param zipFileName
     */
    public void downloadAudio(PaperExp paperExp, String zipFileName) {
        //先判断是否是音频试卷
        if (paperExp.getHasAudio() != null && paperExp.getHasAudio() == true) {
            UpYun upyun = new UpYun(upYunProperty.getBucketName(), upYunProperty.getUserName(), upYunProperty.getPassword());
            //取到所有大题
            List<PaperDetailExp> paperDetailExps = paperExp.getPaperDetails();
            if (paperDetailExps != null && paperDetailExps.size() > 0) {
                for (PaperDetailExp paperDetailExp : paperDetailExps) {
                    //取到所有小题
                    List<PaperDetailUnitExp> paperDetailUnitExps = paperDetailExp.getPaperDetailUnits();
                    if (paperDetailUnitExps != null && paperDetailUnitExps.size() > 0) {
                        for (PaperDetailUnitExp unit : paperDetailUnitExps) {
                            if (unit.getQuestion().getHasAudio() != null && unit.getQuestion().getHasAudio()) {
                                List<QuestionAudio> questionAudios = questionAudioService.findQuestionAudiosByQuestionId(unit.getQuestion().getId());
                                for (QuestionAudio audio : questionAudios) {
                                    String audioFileName = paperExp.getCourseName()
                                            + "_" + paperExp.getName()
                                            + "_试卷_"
                                            + "_" + getAudioFileName(audio, unit, paperDetailExp);
                                    File file = new File(TEMP_FILE_EXP + File.separator + zipFileName + File.separator + audioFileName);
                                    upyun.readFile(audio.getFileUrl(), file);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 计算取得音频文件名称
     *
     * @param audio
     * @param unit
     * @return
     */
    public String getAudioFileName(QuestionAudio audio, PaperDetailUnitExp unit, PaperDetailExp paperDetailExp) {
        String questionAudioId = audio.getId();
        StringBuffer audioFileName = new StringBuffer(paperDetailExp.getNumber() + "_" + unit.getNumber() + "_");
        Question question = unit.getQuestion();
        List<String> idvaluesBody = CommonUtils.getTagANames(question.getQuesBody());
        if (idvaluesBody.contains(questionAudioId)) {
            audioFileName.append("1");//题干
            audioFileName.append("_");
            int index = idvaluesBody.indexOf(questionAudioId);
            audioFileName.append(index + 1);
        } else {
            List<QuesOption> options = question.getQuesOptions();
            if (options != null && options.size() > 0) {
                for (QuesOption option : options) {
                    List<String> idvaluesOption = CommonUtils.getTagANames(option.getOptionBody());
                    if (idvaluesOption.contains(questionAudioId)) {
                        audioFileName.append("2");//选项
                        audioFileName.append("_");
                        audioFileName.append(CommonUtils.getOptionNum(Integer.parseInt(option.getNumber()) - 1));
                        audioFileName.append("_");
                        int index = idvaluesOption.indexOf(questionAudioId);
                        audioFileName.append(index + 1);
                        break;
                    }
                }
            }
        }
        audioFileName.append(".");
        audioFileName.append(audio.getFileSuffixes());
        return audioFileName.toString();
    }

    /*
     * 用于导出Word试卷和试卷答案
     */
    public PaperExp initPaperExp(String paperId) throws Exception {
        PaperExp exportPaper = initPaperExpService.initPaperExp(paperId);

        //给新的大题下所有小题排序
        sortPaperDetailUnits(exportPaper.getPaperDetails());

        //如果每个小题分数不一样，题干后面添加分数
        setQuestionScore(exportPaper.getPaperDetails(), true);

        //处理Word的题干选项
        setQuestionNumberForWord(exportPaper.getPaperDetails());

        //设置大题标题
        setExportQuestionTitle(exportPaper.getPaperDetails(), true);
        return exportPaper;
    }

    /**
     * 用于页面上预览PDF试卷
     */
    public PaperExp previewPaperForPDF(Paper paper) {
        PaperExp previewPaper = initPaperExpService.initPaperExp(paper);

        //给新的大题下所有小题排序
        sortPaperDetailUnits(previewPaper.getPaperDetails());

        //如果每个小题分数不一致，题干后面添加分数；一致时则不用加
        setQuestionScore(previewPaper.getPaperDetails(), false);

        //处理Html的题干选项
        setQuestionNumberForHtml(previewPaper.getPaperDetails());

        //设置大题标题
        setExportQuestionTitle(previewPaper.getPaperDetails(), false);
        return previewPaper;
    }

    /**
     * 对相同类型的大题分组，生成不同的集合放入小题，再对小题排序
     */
    public void sortPaperDetailUnits(List<PaperDetailExp> details) {
        if (CollectionUtils.isEmpty(details)) {
            return;
        }

        int index = 0;
        Map<Long, List<PaperDetailUnitExp>> map = new HashMap<>();

        //循环所有大题,将同类型的小题放到同一个集合下面
        for (PaperDetailExp detail : details) {
            //单选，多选，判断放在同一集合下排序
            List<PaperDetailUnitExp> units;
            Long sortNumber = detail.getSortNumber();
            if (sortNumber < 4L) {
                units = map.get(sortNumber);
                if (units == null) {
                    units = new ArrayList<>();
                }
                units.addAll(detail.getPaperDetailUnits());
                map.put(sortNumber, units);
            } else {
                units = new ArrayList<>();
                units.addAll(detail.getPaperDetailUnits());
                map.put(sortNumber + index, units);
                index++;
            }
        }

        //循环所有小题，给小题排序
        for (Long sortNumber : map.keySet()) {
            List<PaperDetailUnitExp> utils = map.get(sortNumber);
            if (CollectionUtils.isEmpty(utils)) {
                continue;
            }

            int number = 1;//小题号
            int nestedNumber = 1;//套题号
            for (int i = 0; i < utils.size(); i++) {
                PaperDetailUnitExp unit = utils.get(i);
                if (unit.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
                    unit.setNumber(nestedNumber);
                    List<Question> subQuestions = unit.getQuestion().getSubQuestions();
                    for (int j = 0; j < subQuestions.size(); j++) {
                        subQuestions.get(j).setNumber(number + j);
                    }
                    number += subQuestions.size();
                    nestedNumber++;
                } else {
                    unit.setNumber(number);
                    number++;
                }
            }
        }
    }

    /**
     * 题干上设置分数内容(Word)
     */
    public String appendScoreToQuestionWordBody(String wordStr, double score) {
        String totalScore = BigDecimal.valueOf(score).stripTrailingZeros().toPlainString();
        String scores = CommonUtils.clearZeroPoint(totalScore);

        String tmpStr = DocxProcessUtil.BODY_HEADER + wordStr + DocxProcessUtil.BODY_TAIL;
        Body body;
        try {
            body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw new StatusException("500", "Word解析异常！");
        }

        List<Object> pList = body.getContent();
        int index = 0;
        for (Object pObj : pList) {
            if (index == pList.size() - 1) {
                List<Object> pContent = ((P) pObj).getContent();

                R run = new R();
                Text text = new Text();
                text.setValue("(" + scores + "分)");
                run.getContent().add(text);
                pContent.add(run);
            }
            index++;
        }

        StringBuffer pWord = new StringBuffer();
        for (Object pObj : pList) {
            if (pObj instanceof P) {
                String value;
                try {
                    value = DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj));
                } catch (Exception e) {
                    log.error(e.getMessage());
                    throw new StatusException("500", "Word解析异常！");
                }

                pWord.append(value);
            }
        }

        return pWord.toString();
    }

    /**
     * 题干上设置分数内容(Html)
     */
    public String appendScoreToQuestionHtmlBody(String quesBodyHtml, double score) {
        quesBodyHtml = quesBodyHtml.replaceAll("<p>", "").replaceAll("</p>", "");
        quesBodyHtml = quesBodyHtml.replaceAll("<span>", "").replaceAll("</span>", "");
        return "<p>" + quesBodyHtml + "(" + score + "分)</p>";
    }

    /**
     * 如果每个小题分数不一致，题干后面添加分数；一致时则不用加
     */
    public void setQuestionScore(List<PaperDetailExp> details, boolean isWord) {
        if (CollectionUtils.isEmpty(details)) {
            return;
        }

        for (PaperDetailExp detail : details) {
            if (checkPaperDetailUnitScore(detail) != null) {
                continue;//分数一致
            }

            for (PaperDetailUnitExp unit : detail.getPaperDetailUnits()) {
                //判断小题是否为套题
                if (unit.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
                    List<Question> subQuestions = unit.getQuestion().getSubQuestions();
                    for (int i = 0; i < subQuestions.size(); i++) {
                        Question subQuestion = subQuestions.get(i);
                        double subScore = unit.getSubScoreList().get(i);
                        if (isWord) {
                            String questionBodyWord = appendScoreToQuestionWordBody(subQuestion.getQuesBodyWord(), subScore);
                            subQuestion.setQuesBodyWord(questionBodyWord);
                        } else {
                            String bodyHtml = appendScoreToQuestionHtmlBody(subQuestion.getQuesBody(), subScore);
                            subQuestion.setQuesBody(bodyHtml);
                        }
                    }
                } else {
                    Question question = unit.getQuestion();
                    if (isWord) {
                        String questionBodyWord = appendScoreToQuestionWordBody(question.getQuesBodyWord(), unit.getScore());
                        question.setQuesBodyWord(questionBodyWord);
                    } else {
                        String bodyHtml = appendScoreToQuestionHtmlBody(question.getQuesBody(), unit.getScore());
                        question.setQuesBody(bodyHtml);
                    }
                }
            }
        }
    }

    /**
     * 校验大题下的小题分数是不是一样
     * 1.如果分数一样，返回分数
     * 2.如果分数不一样，返回null
     */
    public String checkPaperDetailUnitScore(PaperDetailExp detail) {
        Set<Double> scoreSet = new HashSet<>();
        List<PaperDetailUnitExp> units = detail.getPaperDetailUnits();
        for (PaperDetailUnitExp unit : units) {
            if (unit.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
                /* List<Question> subQuestions = unit.getQuestion().getSubQuestions();
                for (Question subQuestion : subQuestions) {
                    scoreSet.add(subQuestion.getScore());
                }*/
                List<Double> subScoreList = unit.getSubScoreList();
                for (Double score : subScoreList) {
                    scoreSet.add(score);
                }
            } else {
                scoreSet.add(unit.getScore());
            }
        }
        if (scoreSet.size() == 1) {
            return scoreSet.iterator().next() + "";
        }
        return null;
    }

    /**
     * 处理Html的题干选项
     * 1.设置题号
     * 2.给小题选项进行排序
     * 3.设置选项号
     * 4.替换题干中的##为____
     */
    public void setQuestionNumberForHtml(List<PaperDetailExp> details) {
        if (CollectionUtils.isEmpty(details)) {
            return;
        }

        for (PaperDetailExp detail : details) {
            List<PaperDetailUnitExp> units = detail.getPaperDetailUnits();
            for (PaperDetailUnitExp unit : units) {
                Question question = unit.getQuestion();
                List<QuesOption> options = question.getQuesOptions();
                if (CollectionUtils.isNotEmpty(options)) {
                    for (QuesOption option : options) {
                        option.setOptionBody(replaceHashSign(option.getOptionBody()));
                        option.setOptionBodyWord(null);
                    }
                }

                List<Question> subQuestions = question.getSubQuestions();
                question.setQuesBodyWord(null);
                question.setQuesAnswerWord(null);

                if (CollectionUtils.isNotEmpty(subQuestions)) {
                    for (Question subQues : subQuestions) {
                        Map<String, String> params = new HashMap<>();
                        params.put("number", String.valueOf(subQues.getNumber()));
                        subQues.setQuesParams(params);
                        subQues.setQuesBodyWord(null);
                        subQues.setQuesAnswerWord(null);

                        List<QuesOption> subOptions = subQues.getQuesOptions();
                        if (CollectionUtils.isNotEmpty(subOptions)) {
                            for (QuesOption subOption : subOptions) {
                                subOption.setOptionBody(replaceHashSign(subOption.getOptionBody()));
                                subOption.setOptionBodyWord(null);
                            }
                        }
                    }
                } else {
                    question.setQuesBody(replaceHashSign(question.getQuesBody()));
                    question.setQuesAnswer(replaceHashSign(question.getQuesAnswer()));
                }
            }
        }
    }

    /**
     * 替换#号
     */
    private String replaceHashSign(String str) {
        if (StringUtils.isEmpty(str)) {
            return "";
        }
        str = str.replace("###", "____");
        return CommonUtils.relaceQuestionIdx(str, 0);
    }

}