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

import static cn.com.qmth.examcloud.core.questions.service.cache.Constants.DEFAULT_TIME_OUT;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.nlpcn.commons.lang.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import cn.com.qmth.examcloud.api.commons.enums.CallType;
import cn.com.qmth.examcloud.api.commons.security.bean.User;
import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
import cn.com.qmth.examcloud.commons.exception.StatusException;
import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordCloudService;
import cn.com.qmth.examcloud.core.oe.admin.api.request.CheckPaperInExamReq;
import cn.com.qmth.examcloud.core.oe.admin.api.response.CheckPaperInExamResp;
import cn.com.qmth.examcloud.core.oe.student.api.ExamRecordDataCloudService;
import cn.com.qmth.examcloud.core.questions.api.request.SaveRandomPaperExtractConfigReq;
import cn.com.qmth.examcloud.core.questions.base.BeanCopierUtil;
import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
import cn.com.qmth.examcloud.core.questions.base.Model;
import cn.com.qmth.examcloud.core.questions.base.em.enums.ExamType;
import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
import cn.com.qmth.examcloud.core.questions.base.question.PaperDetailDto;
import cn.com.qmth.examcloud.core.questions.base.question.PaperDetailUnitDto;
import cn.com.qmth.examcloud.core.questions.base.question.PaperDto;
import cn.com.qmth.examcloud.core.questions.base.question.QuesOptionDto;
import cn.com.qmth.examcloud.core.questions.base.question.QuestionDto;
import cn.com.qmth.examcloud.core.questions.base.question.SubQuestionDto;
import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
import cn.com.qmth.examcloud.core.questions.dao.AudioTimeConfigRepo;
import cn.com.qmth.examcloud.core.questions.dao.ExtractConfigRepo;
import cn.com.qmth.examcloud.core.questions.dao.PaperDetailRepo;
import cn.com.qmth.examcloud.core.questions.dao.PaperDetailUnitRepo;
import cn.com.qmth.examcloud.core.questions.dao.PaperRepo;
import cn.com.qmth.examcloud.core.questions.dao.RandomPaperRepo;
import cn.com.qmth.examcloud.core.questions.dao.entity.AudioTimeConfig;
import cn.com.qmth.examcloud.core.questions.dao.entity.Course;
import cn.com.qmth.examcloud.core.questions.dao.entity.ExamPaper;
import cn.com.qmth.examcloud.core.questions.dao.entity.ExtractConfig;
import cn.com.qmth.examcloud.core.questions.dao.entity.Paper;
import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetail;
import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnit;
import cn.com.qmth.examcloud.core.questions.dao.entity.QuesOption;
import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
import cn.com.qmth.examcloud.core.questions.dao.entity.QuestionAudio;
import cn.com.qmth.examcloud.core.questions.dao.entity.RandomPaper;
import cn.com.qmth.examcloud.core.questions.service.ExtractConfigService;
import cn.com.qmth.examcloud.core.questions.service.QuesService;
import cn.com.qmth.examcloud.core.questions.service.QuestionAudioService;
import cn.com.qmth.examcloud.core.questions.service.RandomPaperService;
import cn.com.qmth.examcloud.core.questions.service.bean.CouresInfo;
import cn.com.qmth.examcloud.core.questions.service.bean.PaperDetailDtoAssembler;
import cn.com.qmth.examcloud.core.questions.service.bean.PaperDetailUnitDtoAssembler;
import cn.com.qmth.examcloud.core.questions.service.bean.PaperDtoAssembler;
import cn.com.qmth.examcloud.core.questions.service.bean.SubQuestionDtoAssembler;
import cn.com.qmth.examcloud.core.questions.service.bean.extract.SaveRandomPaperExtractConfigForToolDomain;
import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.RandomPaperListVo;
import cn.com.qmth.examcloud.core.questions.service.cache.ExtractConfigCache;
import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
import cn.com.qmth.examcloud.examwork.api.bean.ExamCourseRelationBean;
import cn.com.qmth.examcloud.examwork.api.request.GetExamCourseListReq;
import cn.com.qmth.examcloud.examwork.api.request.GetExamCourseReq;
import cn.com.qmth.examcloud.examwork.api.request.GetExamReq;
import cn.com.qmth.examcloud.examwork.api.response.GetExamCourseListResp;
import cn.com.qmth.examcloud.examwork.api.response.GetExamCourseResp;
import cn.com.qmth.examcloud.examwork.api.response.GetExamResp;
import cn.com.qmth.examcloud.support.CacheConstants;
import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
import cn.com.qmth.examcloud.web.redis.RedisClient;

/**
 * @author chenken
 * @date 2017年4月14日 下午6:07:55
 * @company QMTH
 * @description 调卷规则service
 */
@Service("extractConfigService")
public class ExtractConfigServiceImpl implements ExtractConfigService {

    private static final Logger LOG = LoggerFactory.getLogger(ExtractConfigServiceImpl.class);

    @Autowired
    private ExtractConfigRepo extractConfigRepo;

    @Autowired
    private PaperDetailRepo paperDetailRepo;

    @Autowired
    private PaperDetailUnitRepo paperDetailUnitRepo;

    @Autowired
    private AudioTimeConfigRepo audioTimeConfigRepo;

    @Autowired
    private PaperRepo paperRepo;

    @Autowired
    private QuesService quesService;

    @Autowired
    private QuestionAudioService questionAudioService;

    @Autowired
    private MongoTemplate mongoTemplate;

    @Autowired
    private CourseService courseService;

    @Autowired
    private RandomPaperRepo randomPaperRepo;

    @Autowired
    private PaperDtoAssembler paperDtoAssembler;

    @Autowired
    private PaperDetailDtoAssembler paperDetailDtoAssembler;

    @Autowired
    private PaperDetailUnitDtoAssembler paperDetailUnitDtoAssembler;

    @Autowired
    private SubQuestionDtoAssembler subQuestionDtoAssembler;

    @Autowired
    private RedisClient redisClient;
    @Autowired
    private ExtractConfigCache extractConfigCache;
    /*
     * @Autowired private PaperDetailUnitNativeRepo detailUnitNativeRepo;
     */

    @Autowired
    private ExamCloudService examCloudService;

    @Autowired
    private ExamRecordCloudService adminExamRecordCloudService;

    @Autowired
    private ExamRecordDataCloudService studentExamRecordCloudService;
    
    @Autowired
    private RandomPaperService randomPaperService;
    
    @Override
    public ExtractConfig findConfig(ExtractConfig condition) {
        if (condition.getExamId() == null) {
            return null;
        }
        if (StringUtils.isBlank(condition.getCourseCode())) {
            return null;
        }
        Query query = new Query();
        if (!StringUtils.isBlank(condition.getOrgId())) {
            query.addCriteria(Criteria.where("orgId").is(condition.getOrgId()));
        }
        query.addCriteria(Criteria.where("examId").is(condition.getExamId()));
        query.addCriteria(Criteria.where("courseCode").is(condition.getCourseCode()));
        ExtractConfig tempConfig = this.mongoTemplate.findOne(query, ExtractConfig.class);
        if(tempConfig!=null&&tempConfig.getCallType()==null) {
        	tempConfig.setCallType(CallType.WHOLE_SET);
        }
        return tempConfig;
    }

    @Override
    public Map<String, String> saveExtractConfig2(ExtractConfig extractConfig, User user) throws Exception {
        List<ExamPaper> examPapers = extractConfig.getExamPaperList();
        for (int i = 0; i < examPapers.size(); i++) {
            ExamPaper examPaper = examPapers.get(i);
            Paper paper = examPaper.getPaper();
            paper = Model.of(paperRepo.findById(paper.getId()));
            examPaper.setPaper(paper);
        }
        Course course = courseService.getCourse(Long.valueOf(extractConfig.getOrgId()), extractConfig.getCourseCode());
        extractConfig.setCourse(course);
        extractConfig.setCourseName(course.getName());
        Map<String, String> newFinishedPaperIdMap = makePaperByConfig(extractConfig);
        extractConfig.setFinishedPaperIdMap(newFinishedPaperIdMap);
        extractConfig.setIfFinish((short) 1);
        extractConfigRepo.save(extractConfig);
        return newFinishedPaperIdMap;
    }

    @Override
    public List<ExamPaper> saveExtractConfig(ExtractConfig extractConfig, User user) {
    	Course course = courseService.getCourse(Long.valueOf(extractConfig.getOrgId()), extractConfig.getCourseCode());
        extractConfig.setCourse(course);
        extractConfig.setCourseName(course.getName());
        List<ExamPaper> examPapers = extractConfig.getExamPaperList();
        for (int i = 0; i < examPapers.size(); i++) {
            ExamPaper examPaper = examPapers.get(i);
            Paper paper = examPaper.getPaper();
            paper = Model.of(paperRepo.findById(paper.getId()));
            if(!extractConfig.getCourse().getOrgId().equals(paper.getCourse().getOrgId())
            		||!extractConfig.getCourse().getCode().equals(paper.getCourse().getCode())) {
            	throw new StatusException("试卷的课程和当前课程不匹配");
            }
            examPaper.setPaper(paper);
            if (ExamType.OFFLINE.name().equals(extractConfig.getExamType())) {
                checkOfflinePaper(paper);
            }
        }
        if (StringUtils.isNotBlank(extractConfig.getId())) {
            disposeOldPaper(extractConfig.getId());
        }
        extractConfig.setIfFinish((short) 1);
        extractConfigRepo.save(extractConfig);
        disposeNowPaper(extractConfig.getId());
        return examPapers;
    }
    
    @Transactional
    @Override
    public void saveExtractConfigForRandomPaper(ExtractConfig extractConfig) {
    	GetExamReq examReq=new GetExamReq();
    	examReq.setId(extractConfig.getExamId());
    	GetExamResp examRes=examCloudService.getExam(examReq);
    	if(ExamType.OFFLINE.name().equals(examRes.getExamBean().getExamType())) {
    		throw new StatusException("离线考试不能随机抽题");
    	}
        Course course = courseService.getCourse(Long.valueOf(extractConfig.getOrgId()), extractConfig.getCourseCode());
        extractConfig.setCourse(course);
        extractConfig.setCourseName(course.getName());

        RandomPaper query = new RandomPaper();
        query.setId(extractConfig.getRandomPaperId());
        query.setCourseId(Long.parseLong(course.getId()));
        Example<RandomPaper> queryExample = Example.of(query);
        boolean exists = randomPaperRepo.exists(queryExample);
        if(!exists){
            LOG.warn("随机抽题模板选择错误，与当前课程不匹配！ courseId:{} randomPaperId:{}", course.getId(), extractConfig.getRandomPaperId());
            throw new StatusException("500", "随机抽题模板选择错误，与当前课程不匹配！");
        }

		// 新增，id为空
		if (StringUtils.isBlank(extractConfig.getId())) {
			// 先查询
			ExtractConfig tempConfig = this.findConfig(extractConfig);
			if (tempConfig != null) {
				tempConfig.setRandomPaperId(extractConfig.getRandomPaperId());
				tempConfig.setPlayTime(extractConfig.getPlayTime());
				tempConfig.setCourseName(course.getName());
				tempConfig.setCourse(course);
				tempConfig.setCallType(extractConfig.getCallType());
				extractConfig=tempConfig;
			}
		}else {
			ExtractConfig tempConfig = Model.of(extractConfigRepo.findById(extractConfig.getId()));
			tempConfig.setRandomPaperId(extractConfig.getRandomPaperId());
			tempConfig.setPlayTime(extractConfig.getPlayTime());
			tempConfig.setCourseName(course.getName());
			tempConfig.setCourse(course);
			tempConfig.setCallType(extractConfig.getCallType());
			extractConfig=tempConfig;
		}
		if (StringUtils.isNotBlank(extractConfig.getId())) {
            disposeOldPaper(extractConfig.getId());
        }
		extractConfig.setExamPaperList(null);
        extractConfigRepo.save(extractConfig);
    }

    /**
     * 先处理原来绑定的试卷
     *
     * @param extractConfigId
     */
    private void disposeOldPaper(String extractConfigId) {
        ExtractConfig ec = Model.of(extractConfigRepo.findById(extractConfigId));
        if(CollectionUtils.isEmpty(ec.getExamPaperList())) {
        	return;
        }
        for (ExamPaper paper : ec.getExamPaperList()) {
            if (inExam(paper.getPaper().getId())) {//如果有考试记录
                updatePaperInUse(paper.getPaper().getId(), 1);
            } else {
                if (!inOtherExtractConfig(extractConfigId, paper.getPaper().getId())) {//没有考试记录且不被其他调卷规则引用
                    updatePaperInUse(paper.getPaper().getId(), 0);
                }
            }
        }
    }

    /**
     * 处理当前绑定试卷
     *
     * @param extractConfigId
     */
    private void disposeNowPaper(String extractConfigId) {
        ExtractConfig ec = Model.of(extractConfigRepo.findById(extractConfigId));
        for (ExamPaper paper : ec.getExamPaperList()) {
            updatePaperInUse(paper.getPaper().getId(), 1);
        }
    }

    private void updatePaperInUse(String paperId, int inUse) {
        Query query = null;
        if (paperId.length() == 24) {
            query = Query.query(Criteria.where("_id").is(new ObjectId(paperId)));
        } else {
            query = Query.query(Criteria.where("_id").is(paperId));
        }
        Update update = new Update();
        update.set("inUse", inUse);
        mongoTemplate.updateFirst(query, update, "paper");
    }

    private boolean inExam(String paperId) {
        CheckPaperInExamReq req1 = new CheckPaperInExamReq();
        req1.setBasePaperId(paperId);
        CheckPaperInExamResp res1 = adminExamRecordCloudService.checkPaperInExam(req1);
        if (res1.getInExam()) {
            return res1.getInExam();
        }
        cn.com.qmth.examcloud.core.oe.student.api.request.CheckPaperInExamReq req2 = new cn.com.qmth.examcloud.core.oe.student.api.request.CheckPaperInExamReq();
        req2.setBasePaperId(paperId);
        cn.com.qmth.examcloud.core.oe.student.api.response.CheckPaperInExamResp res2 = studentExamRecordCloudService
                .checkPaperInExam(req2);
        return res2.getInExam();
    }

    private boolean inOtherExtractConfig(String extractConfigId, String paperId) {
        Criteria criteria = new Criteria();
        criteria.and("examPaperList").elemMatch(Criteria.where("paper.$id").is(paperId));
        Query query = Query.query(criteria);
        List<ExtractConfig> list = mongoTemplate.find(query, ExtractConfig.class, "extractConfig");
        if (list == null || list.size() == 0) {
            return false;
        }
        if (list.size() == 1 && list.get(0).getId().equals(extractConfigId)) {
            return false;
        }
        return true;
    }

    private void checkOfflinePaper(Paper paper) {
        List<PaperDetailUnit> pdus = paperDetailUnitRepo.findByPaper(paper);
        if (pdus == null || pdus.size() == 0) {
            return;
        }
        for (PaperDetailUnit pdu : pdus) {
            if (pdu.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
                    || pdu.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION
                    || pdu.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
                throw new StatusException("500", "试卷包含客观题，无法保存规则");
            } else if (pdu.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION
                    && !CollectionUtils.isEmpty(pdu.getQuestion().getSubQuestions())) {
                for (Question q : pdu.getQuestion().getSubQuestions()) {
                    if (q.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
                            || q.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION
                            || q.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
                        throw new StatusException("500", "试卷包含客观题，无法保存规则");
                    }
                }
            }
        }
    }

    @Override
    public ExtractConfig findConfigById(String id) {
        if (StringUtils.isBlank(id)) {
            return null;
        }
        return Model.of(extractConfigRepo.findById(id));
    }

    @Override
    public Map<String, Object> extractExamPaper(Long exam_id, String course_code, String group_code) {
        Map<String, Object> returnMap = new HashMap<>();
        LOG.info("调卷开始...");
        long beginTime = System.currentTimeMillis();
        LOG.info("开始根据examId:" + exam_id + "和courseCode:" + course_code + "获取调卷规则");
        ExtractConfig extractConfig = this.findConfig(new ExtractConfig(exam_id, course_code));
        if (extractConfig == null) {
            LOG.error("该考试和课程下调卷规则未制定,请先制定调卷规则,调卷程序退出");
            returnMap.put("errorMsg", "该考试和课程下调卷规则未制定,请先制定调卷规则");
            return returnMap;
        }
        long configFinishTime = System.currentTimeMillis();
        LOG.info("获取调卷规则共耗时:" + (configFinishTime - beginTime) + "ms");
        LOG.info("根据调卷规则中设置的概率获取类型为" + group_code + "的试卷");
        Map<String, Paper> paperMap = this.getExamPaperByProbability(extractConfig.getExamPaperList());
        if (paperMap.isEmpty()) {
            LOG.error("该考试和课程下调卷规则中试卷不存在,请检查调卷规则,调卷程序退出");
            returnMap.put("errorMsg", "该考试和课程下调卷规则中试卷不存在,请重新制定调卷规则");
            return returnMap;
        }

        long paperMapFinishTime = System.currentTimeMillis();
        LOG.info("获取类型为" + group_code + "的试卷共耗时:" + (paperMapFinishTime - configFinishTime) + "ms");

        Paper basePaper = paperMap.get(group_code);
        if (basePaper == null) {
            LOG.error("该考试和课程下调卷规则中该类型试卷不存在,请检查调卷规则,调卷程序退出");
            returnMap.put("errorMsg", "该考试和课程下调卷规则中该类型试卷不存在,请重新制定调卷规则");
            return returnMap;
        }
        String basePaperId = basePaper.getId();
        LOG.info("将原始试卷:" + basePaperId + "根据规则重新组卷");
        int upSetQuestionOrder = extractConfig.getScrambling_the_question_order();
        int upSetOptionOrder = extractConfig.getScrambling_the_option_order();
        // 不乱序直接调卷
        if (upSetQuestionOrder == 0 && upSetOptionOrder == 0) {
            PaperDto paperDto = getPaperDtoByPaper(basePaper, basePaperId);
            long paperDtoFinishTime = System.currentTimeMillis();
            LOG.info("获取试卷Dto共耗时:" + (paperDtoFinishTime - paperMapFinishTime) + "ms");
            returnMap.put("paperDto", paperDto);
            LOG.info("调卷完成");
            LOG.info("总共耗时:" + (System.currentTimeMillis() - beginTime) + "ms");
        } else {
            // 乱序重新生成试卷
            Paper newPaper = this.recombinationPaper(basePaper, PaperType.STUDENT_EXAM, upSetQuestionOrder,
                    upSetOptionOrder);
            LOG.info("根据新试卷 paperId:" + newPaper.getId() + "组装PaperDto后返回");

            long genPaperFinishTime = System.currentTimeMillis();
            LOG.info("组卷共耗时:" + (genPaperFinishTime - paperMapFinishTime) + "ms");
            PaperDto paperDto = getPaperDtoByPaper(newPaper, basePaperId);

            long paperDtoFinishTime = System.currentTimeMillis();
            LOG.info("获取试卷Dto共耗时:" + (paperDtoFinishTime - genPaperFinishTime) + "ms");

            returnMap.put("paperDto", paperDto);
            LOG.info("调卷完成");
            LOG.info("总共耗时:" + (System.currentTimeMillis() - beginTime) + "ms");
        }
        return returnMap;
    }

    @Override
    public boolean checkIsAllQbjectiveQuestion(String paperId) {
        // 优先从redis中获取缓存dto
        PaperDto cachePaperDto = redisClient.get(CacheConstants.CACHE_Q_TEMP_PAPER + paperId, PaperDto.class, DEFAULT_TIME_OUT);
        if (cachePaperDto != null) {
            return cachePaperDto.isAllQbjectiveQuestion();
        }

        Paper paper = Model.of(paperRepo.findById(paperId));
        List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
        for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
            Question question = paperDetailUnit.getQuestion();
            // 填空或问答
            if (question.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
                    || question.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
                return false;
            }
            if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
                List<Question> subQuestions = question.getSubQuestions();
                for (Question subQuestion : subQuestions) {
                    if (subQuestion.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
                            || subQuestion.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    public boolean checkIsAllQbjectiveByPdu(List<PaperDetailUnit> paperDetailUnits) {
        for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
            Question question = paperDetailUnit.getQuestion();
            // 填空或问答
            if (question.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
                    || question.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
                return false;
            }
            if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
                List<Question> subQuestions = question.getSubQuestions();
                for (Question subQuestion : subQuestions) {
                    if (subQuestion.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
                            || subQuestion.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    @Override
    public Map<String, String> makePaperByConfig(ExtractConfig extractConfig) {
        Map<String, String> finishedPaperIdMap = new HashMap<>();
        if (extractConfig == null) {
            throw new RuntimeException("调卷规则不存在");
        }
        // 获得规则中设置的试卷
        Map<String, Paper> paperMap = this.getExamPaperByProbability(extractConfig.getExamPaperList());
        if (paperMap.isEmpty()) {
            throw new RuntimeException("抽取试卷失败");
        }
        for (Map.Entry<String, Paper> entry : paperMap.entrySet()) {
            String key = entry.getKey();
            // 根据原有试卷重新组卷得到新试卷
            /*
             * Paper newPaper = this.recombinationPaper(entry.getValue(), PaperType.PREVIEW,
             * extractConfig.getScrambling_the_question_order(),
             * extractConfig.getScrambling_the_option_order());
             */
            finishedPaperIdMap.put(key, entry.getValue().getId());
        }
        return finishedPaperIdMap;
    }

    /**
     * 重组试卷，生成新的试卷
     *
     * @param paper              选中的试卷
     * @param upSetQuestionOrder 客观题小题乱序 1:乱序 0：不乱序
     * @param upSetOptionOrder   客观题选项乱序 1:乱序 0：不乱序
     * @return
     */
    public Paper recombinationPaper(Paper paper, PaperType paperType, int upSetQuestionOrder, int upSetOptionOrder) {

        // 将小题全部取出来,只取一次
        List<PaperDetailUnit> allPaperDetailUnits = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
        // 获取大题
        List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);

        // 抽取大题号对应的小题
        Map<String, List<PaperDetailUnit>> pduMap = allPaperDetailUnits.stream()
                .collect(Collectors.groupingBy(PaperDetailUnit::getDetailId));
        // 最终保存的所有小题
        List<PaperDetailUnit> savePaperDetailUnits = new ArrayList<>();
        // 保存试卷信息
        paper.setId(null);
        paper.setPaperType(paperType);
        Paper newPaper = paperRepo.insert(paper);

        for (int i = 0; i < paperDetails.size(); i++) {
            PaperDetail paperDetail = paperDetails.get(i);

            List<PaperDetailUnit> paperDetailUnits = pduMap.get(paperDetail.getId());
            if (paperDetailUnits == null || paperDetailUnits.size() == 0) {
                continue;
            }
            Collections.sort(paperDetailUnits);

            // 将大题中最小的number取出
            PaperDetailUnit topDetailUnit = paperDetailUnits.get(0);
            int minNumber = topDetailUnit.getNumber();

            // 小题乱序
            if (paperDetailUnits != null && paperDetailUnits.size() > 0) {
                if ((topDetailUnit.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
                        || topDetailUnit.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION
                        || topDetailUnit.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION)
                        && upSetQuestionOrder == 1) {
                    Collections.shuffle(paperDetailUnits);// 打乱小题List
                }
            }
            // 设置大题信息
            paperDetail.setId(null);
            paperDetail.setPaper(newPaper);

            for (int j = 0; j < paperDetailUnits.size(); j++) {
                // 重新设置保存PaperDetailUnit
                PaperDetailUnit paperDetailUnit = paperDetailUnits.get(j);
                paperDetailUnit.setPaperType(paperType);
                paperDetailUnit.setPaper(newPaper);
                paperDetailUnit.setPaperDetail(paperDetail);
                paperDetailUnit.setNumber(minNumber + j); // 重新设置序号
                reSavePaperDetailUtilAndQuestion(paperDetailUnit, upSetOptionOrder);
                savePaperDetailUnits.add(paperDetailUnit);
            }

        }
        // 保存大题信息
        paperDetailRepo.insert(paperDetails);
        // 保存小题信息
        paperDetailUnitRepo.insert(savePaperDetailUnits);

        // 清空所有list
        allPaperDetailUnits.clear();
        savePaperDetailUnits.clear();
        paperDetails.clear();

        return newPaper;
    }

    /**
     * 每个试卷类型取出一套试卷 { A:Paper, B:Paper } A是试卷类型 Paper是A类型下选定的试卷
     *
     * @param examPaperList
     * @return
     */
    private Map<String, Paper> getExamPaperByProbability(List<ExamPaper> examPaperList) {
        Map<String, Paper> paperByTypeMap = new HashMap<>();
        if (examPaperList == null || examPaperList.size() == 0) {
            throw new RuntimeException("可供抽取的试卷集合为空,无法抽取试卷");
        }

        Map<String, List<ExamPaper>> examPaperMap = new HashMap<>();
        for (int i = 0; i < examPaperList.size(); i++) {
            ExamPaper examPaper = examPaperList.get(i);
            if (!examPaperMap.containsKey(examPaper.getGroupCode())) {
                if (examPaper.getPaper() != null) {
                    List<ExamPaper> epList = new ArrayList<>();
                    epList.add(examPaper);
                    examPaperMap.put(examPaper.getGroupCode(), epList);
                }
            } else {
                if (examPaper.getPaper() != null) {
                    List<ExamPaper> epList = examPaperMap.get(examPaper.getGroupCode());
                    epList.add(examPaper);
                }
            }
        }

        if (examPaperMap != null) {
            Set<String> keys = examPaperMap.keySet();
            Iterator<String> it = keys.iterator();
            while (it.hasNext()) {
                String key = it.next();
                Paper paper = this.getPaperByProbability(examPaperMap.get(key));

                if (paper == null) {
                    continue;
                }

                // 不能用原来的paper对象，否则examPaperList中的paper对象会被覆盖
                Paper newPaper = Model.of(paperRepo.findById(paper.getId()));
                paperByTypeMap.put(key, newPaper);
            }
        }

        return paperByTypeMap;
    }

    /**
     * 根据设定几率取出一套试卷
     *
     * @param examPaperList
     * @return
     */
    private Paper getPaperByProbability(List<ExamPaper> examPaperList) {
        int sum = 0;
        for (int i = 0; i < examPaperList.size(); i++) {
            sum += examPaperList.get(i).getWeight();
        }

        // 从1开始
        Integer rand = new Random().nextInt(sum) + 1;
        for (int i = 0; i < examPaperList.size(); i++) {
            rand -= examPaperList.get(i).getWeight();
            // 选中
            if (rand <= 0) {
                return examPaperList.get(i).getPaper();
            }
        }

        return null;
    }

    /**
     * 重新设置并保存paperDetailUnit和question
     *
     * @param paperDetailUnit
     * @param upSetOptionOrder
     */
    private void reSavePaperDetailUtilAndQuestion(PaperDetailUnit paperDetailUnit, Integer upSetOptionOrder) {
        Question question = paperDetailUnit.getQuestion();
        // 选项乱序
        if (upSetOptionOrder == 1) {
            // 单选或多选
            if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
                    || question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
                List<String> numberList = new ArrayList<>();
                List<QuesOption> options = question.getQuesOptions();
                for (int k = 0; k < options.size(); k++) {
                    QuesOption quesOption = options.get(k);
                    numberList.add(quesOption.getNumber());
                }
                Collections.shuffle(numberList); // 打乱number顺序
                paperDetailUnit.setOptionOrder(StringUtils.join(numberList.toArray(), ","));// 设置option顺序
            }
            // 套题，套题下小题不乱序，选择题选项乱序
            if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
                List<Question> subQuestions = question.getSubQuestions();
                StringBuffer optionOrder = new StringBuffer();
                for (int m = 0; m < subQuestions.size(); m++) {
                    Question subQuestion = subQuestions.get(m);
                    if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
                            || subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
                        List<String> numberList = new ArrayList<>();
                        List<QuesOption> options = subQuestion.getQuesOptions();
                        for (int n = 0; n < options.size(); n++) {
                            QuesOption quesOption = options.get(n);
                            numberList.add(quesOption.getNumber());
                        }
                        Collections.shuffle(numberList); // 打乱number顺序
                        optionOrder.append(StringUtils.join(numberList.toArray(), ",")).append(";");
                    }
                }
                paperDetailUnit.setOptionOrder(optionOrder.toString()); // 设置option顺序
            }
        }
        paperDetailUnit.setId(null);
    }

    @Override
    public List<String> getExamPaperId(String courseCode, String orgId) {
        Assert.hasLength(courseCode, "courseCode不能为空");
        Assert.hasLength(orgId, "orgId不能为空");
        List<String> paperIdList = new ArrayList<>();
        Query query = new Query();
        query.addCriteria(Criteria.where("courseCode").is(courseCode));
        query.addCriteria(Criteria.where("orgId").is(orgId));
        List<ExtractConfig> extractConfigList = this.mongoTemplate.find(query, ExtractConfig.class);
        for (ExtractConfig extractConfig : extractConfigList) {
            List<ExamPaper> examPaperList = extractConfig.getExamPaperList();
            if (examPaperList != null && examPaperList.size() > 0) {
                for (ExamPaper examPaper : examPaperList) {
                    paperIdList.add(examPaper.getPaper().getId());
                }
            }
        }
        return paperIdList;
    }

    /**
     * 根据paper得到PaperDto
     *
     * @param paper
     * @return
     */
    private PaperDto getPaperDtoByPaper(Paper paper, String basePaperId) {
        long beginTime = System.currentTimeMillis();
        // 没有则重新组装
        PaperDto paperDto = paperDtoAssembler.toDto(paper);
        paperDto.setBasePaperId(basePaperId);
        paperDto.setAllQbjectiveQuestion(checkIsAllQbjectiveQuestion(basePaperId));

        long paperDtoEndTime = System.currentTimeMillis();
        LOG.info("单独组装paperDto耗时:" + (paperDtoEndTime - beginTime) + "ms");

        // 将小题全部取出来,只取一次
        List<PaperDetailUnit> allPaperDetailUnits = paperDetailUnitRepo.findByPaper(paper);
        long pduEndTime = System.currentTimeMillis();
        LOG.info("数据库取小题耗时:" + (pduEndTime - paperDtoEndTime) + "ms");
        Collections.sort(allPaperDetailUnits);
        long pduSortEndTime = System.currentTimeMillis();
        LOG.info("排序小题耗时:" + (pduSortEndTime - pduEndTime) + "ms");

        // 获取大题
        List<PaperDetail> paperDetails = paperDetailRepo.findByPaper(paper);
        long pdEndTime = System.currentTimeMillis();
        LOG.info("数据库取大题耗时:" + (pdEndTime - pduSortEndTime) + "ms");
        Collections.sort(paperDetails);
        long pdSortEndTime = System.currentTimeMillis();
        LOG.info("排序大题耗时:" + (pdSortEndTime - pdEndTime) + "ms");

        // 抽取大题Id对应的小题
        Map<String, List<PaperDetailUnit>> pduMap = allPaperDetailUnits.stream()
                .collect(Collectors.groupingBy(PaperDetailUnit::getDetailId));
        long pduMapEndTime = System.currentTimeMillis();
        LOG.info("获取大题与小题对应关系耗时:" + (pduMapEndTime - pdSortEndTime) + "ms");

        // 获取大题Dto
        List<PaperDetailDto> paperDetailDtos = paperDetailDtoAssembler.toDtoList(paperDetails);
        paperDto.setPaperDetails(paperDetailDtos);
        long paperDetailDtoEndTime = System.currentTimeMillis();
        LOG.info("单独组装paperDetailDto耗时:" + (paperDetailDtoEndTime - pduMapEndTime) + "ms");

        // 封装小题
        for (int i = 0; i < paperDetailDtos.size(); i++) {
            // 根据大题查出大题下面的小题
            PaperDetail paperDetail = paperDetails.get(i);

            List<PaperDetailUnit> paperDetailUnits = pduMap.get(paperDetail.getId());

            List<PaperDetailUnitDto> paperDetailUnitDtos = new ArrayList<>();
            for (int j = 0; j < paperDetailUnits.size(); j++) {
                PaperDetailUnit paperDetailUnit = paperDetailUnits.get(j);
                if (paperDetailUnit == null || paperDetailUnit.getQuestion() == null) {
                    break;
                }
                // 设置答案
                setSelectQuestionAnswerUnit(paperDetailUnit);

                PaperDetailUnitDto unitDto = paperDetailUnitDtoAssembler.toDto(paperDetailUnit);
                /**
                 * 此处不能传questionId，需要传paperDetailUnitId 因为选项乱序在paperDetailUnit里
                 * unitDto.setQuesId(paperDetailUnit.getQuestion().getId());
                 */
                unitDto.setQuesId(paperDetailUnit.getId());
                String answer = paperDetailUnit.getQuestion().getQuesAnswer();
                if (StringUtils.isNotEmpty(answer)) {
                    unitDto.setAnswer(answer);
                }
                if (unitDto.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {// 假如是套题
                    List<Question> subQuesList = paperDetailUnit.getQuestion().getSubQuestions();
                    List<SubQuestionDto> subQuesDtos = subQuestionDtoAssembler.toDtoList(subQuesList);
                    for (int m = 0; m < subQuesList.size(); m++) {
                        List<QuesOptionDto> quesOptionDtos = subQuestionDtoAssembler
                                .toOptionDtoList(subQuesList.get(m).getQuesOptions());
                        subQuesDtos.get(m).setQuesOptions(quesOptionDtos);
                        if (StringUtils.isNotEmpty(subQuesList.get(m).getQuesAnswer())) {
                            subQuesDtos.get(m).setQuesAnswer(subQuesList.get(m).getQuesAnswer());
                        }
                        subQuesDtos.get(m).setNumber(m + 1);
                        // 套题分数从小题类中取值
                        subQuesDtos.get(m).setScore(paperDetailUnit.getSubScoreList().get(m));
                    }
                    unitDto.setSubQuestions(subQuesDtos);
                }
                paperDetailUnitDtos.add(unitDto);
            }
            paperDetailDtos.get(i).setPaperDetailUnits(paperDetailUnitDtos);
            paperDetailDtos.get(i).setCnNum(CommonUtils.toCHNum(paperDetailDtos.get(i).getNumber()));
        }
        long paperDetailUnitDtoEndTime = System.currentTimeMillis();
        LOG.info("单独组装paperDetailUnitDto耗时:" + (paperDetailUnitDtoEndTime - paperDetailDtoEndTime) + "ms");

        // 将重新组装的dto放进缓存
        redisClient.set(CacheConstants.CACHE_Q_TEMP_PAPER + paperDto.getId(), paperDto, DEFAULT_TIME_OUT);
        return paperDto;
    }

    /**
     * 测试获取paperDto
     *
     * @param basePaperId
     * @return
     */
    @Deprecated
    public PaperDto getPaperDtoByPaperNew(String basePaperId) {

        long beginTime = System.currentTimeMillis();
        // 没有则重新组装
        Paper paper = Model.of(paperRepo.findById(basePaperId));
        PaperDto paperDto = paperDtoAssembler.toDto(paper);
        paperDto.setBasePaperId(basePaperId);

        long paperDtoEndTime = System.currentTimeMillis();
        LOG.info("单独组装paperDto耗时:" + (paperDtoEndTime - beginTime) + "ms");

        // 将小题全部取出来,只取一次
        // List<PaperDetailUnit> allPaperDetailUnits =
        // detailUnitNativeRepo.findByPaperId(paper.getId());
        List<PaperDetailUnit> allPaperDetailUnits = new ArrayList<>();

        long pduEndTime = System.currentTimeMillis();
        LOG.info("数据库取小题耗时:" + (pduEndTime - paperDtoEndTime) + "ms");
        Collections.sort(allPaperDetailUnits);
        long pduSortEndTime = System.currentTimeMillis();
        LOG.info("排序小题耗时:" + (pduSortEndTime - pduEndTime) + "ms");

        paperDto.setAllQbjectiveQuestion(checkIsAllQbjectiveByPdu(allPaperDetailUnits));
        long isAllObjEndtime = System.currentTimeMillis();
        LOG.info("设置客观题耗时:" + (isAllObjEndtime - pduSortEndTime) + "ms");

        // 获取大题
        List<PaperDetail> paperDetails = paperDetailRepo.findByPaper(paper);
        long pdEndTime = System.currentTimeMillis();
        LOG.info("数据库取大题耗时:" + (pdEndTime - isAllObjEndtime) + "ms");
        Collections.sort(paperDetails);
        long pdSortEndTime = System.currentTimeMillis();
        LOG.info("排序大题耗时:" + (pdSortEndTime - pdEndTime) + "ms");

        // 抽取大题Id对应的小题
        Map<String, List<PaperDetailUnit>> pduMap = allPaperDetailUnits.stream()
                .collect(Collectors.groupingBy(PaperDetailUnit::getDetailId));
        long pduMapEndTime = System.currentTimeMillis();
        LOG.info("获取大题与小题对应关系耗时:" + (pduMapEndTime - pdSortEndTime) + "ms");

        // 获取大题Dto
        List<PaperDetailDto> paperDetailDtos = paperDetailDtoAssembler.toDtoList(paperDetails);
        paperDto.setPaperDetails(paperDetailDtos);
        long paperDetailDtoEndTime = System.currentTimeMillis();
        LOG.info("单独组装paperDetailDto耗时:" + (paperDetailDtoEndTime - pduMapEndTime) + "ms");

        // 封装小题
        for (int i = 0; i < paperDetailDtos.size(); i++) {
            // 根据大题查出大题下面的小题
            PaperDetail paperDetail = paperDetails.get(i);

            List<PaperDetailUnit> paperDetailUnits = pduMap.get(paperDetail.getId());

            List<PaperDetailUnitDto> paperDetailUnitDtos = new ArrayList<>();
            for (int j = 0; j < paperDetailUnits.size(); j++) {
                PaperDetailUnit paperDetailUnit = paperDetailUnits.get(j);
                if (paperDetailUnit == null || paperDetailUnit.getQuestion() == null) {
                    break;
                }
                // 设置答案
                setSelectQuestionAnswerUnit(paperDetailUnit);

                PaperDetailUnitDto unitDto = paperDetailUnitDtoAssembler.toDto(paperDetailUnit);
                /**
                 * 此处不能传questionId，需要传paperDetailUnitId 因为选项乱序在paperDetailUnit里
                 * unitDto.setQuesId(paperDetailUnit.getQuestion().getId());
                 */
                unitDto.setQuesId(paperDetailUnit.getId());
                String answer = paperDetailUnit.getQuestion().getQuesAnswer();
                if (StringUtils.isNotEmpty(answer)) {
                    unitDto.setAnswer(answer);
                }
                if (unitDto.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {// 假如是套题
                    List<Question> subQuesList = paperDetailUnit.getQuestion().getSubQuestions();
                    List<SubQuestionDto> subQuesDtos = subQuestionDtoAssembler.toDtoList(subQuesList);
                    for (int m = 0; m < subQuesList.size(); m++) {
                        List<QuesOptionDto> quesOptionDtos = subQuestionDtoAssembler
                                .toOptionDtoList(subQuesList.get(m).getQuesOptions());
                        subQuesDtos.get(m).setQuesOptions(quesOptionDtos);
                        if (StringUtils.isNotEmpty(subQuesList.get(m).getQuesAnswer())) {
                            subQuesDtos.get(m).setQuesAnswer(subQuesList.get(m).getQuesAnswer());
                        }
                        subQuesDtos.get(m).setNumber(m + 1);
                        // 套题分数从小题类中取值
                        subQuesDtos.get(m).setScore(paperDetailUnit.getSubScoreList().get(m));
                    }
                    unitDto.setSubQuestions(subQuesDtos);
                }
                paperDetailUnitDtos.add(unitDto);
            }
            paperDetailDtos.get(i).setPaperDetailUnits(paperDetailUnitDtos);
            paperDetailDtos.get(i).setCnNum(CommonUtils.toCHNum(paperDetailDtos.get(i).getNumber()));
        }
        long paperDetailUnitDtoEndTime = System.currentTimeMillis();
        LOG.info("单独组装paperDetailUnitDto耗时:" + (paperDetailUnitDtoEndTime - paperDetailDtoEndTime) + "ms");
        return paperDto;
    }

    private void setSelectQuestoionAnswer(List<PaperDetailUnit> paperDetailUnits) {
        for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
            if (paperDetailUnit == null || paperDetailUnit.getQuestion() == null) {
                break;
            }
            String optionOrder = paperDetailUnit.getOptionOrder();
            Question question = paperDetailUnit.getQuestion();
            quesService.setSelectQuestionAnswer(question, optionOrder);
        }
    }

    private void setSelectQuestionAnswerUnit(PaperDetailUnit paperDetailUnit) {
        Question question = paperDetailUnit.getQuestion();
        if (paperDetailUnit == null || question == null) {
            return;
        }
        String optionOrder = paperDetailUnit.getOptionOrder();
        quesService.setSelectQuestionAnswer(question, optionOrder);
    }

    /**
     * 根据paperDetailUnitId抽取单个试题 根据paperDetailUnitId中设置的option顺序对option排序
     */
    @Override
    public QuestionDto extractExamQuestion(String examId, String courseCode, String groupCode,
                                           String paperDetailUnitId) {
        PaperDetailUnit paperDetailUnit = Model.of(paperDetailUnitRepo.findById(paperDetailUnitId));
        List<PaperDetailUnit> paperDetailUnits = new ArrayList<>();
        paperDetailUnits.add(paperDetailUnit);
        // 设置答案
        setSelectQuestoionAnswer(paperDetailUnits);
        // 重新对选择题option进行排序(多选、单选、套题下选择题)
        reorderChoicequestionOption(paperDetailUnit);
        Question ques = paperDetailUnit.getQuestion();
        quesService.formatQues(ques);
        QuestionDto dto = BeanCopierUtil.copyProperties(ques, QuestionDto.class);
        dto.setScore(paperDetailUnit.getScore());
        dto.setQuesOptions(buildQuestionOptionDto(ques.getQuesOptions()));
        if (ques.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
            List<Question> subQuesList = ques.getSubQuestions();
            List<SubQuestionDto> subQuesDtos = new ArrayList<>();
            for (Question question : subQuesList) {
                SubQuestionDto subQuestionDto = new SubQuestionDto();
                BeanUtils.copyProperties(question, subQuestionDto);
                subQuesDtos.add(subQuestionDto);
            }
            for (int m = 0; m < subQuesList.size(); m++) {
                List<QuesOptionDto> quesOptionDtos = BeanCopierUtil
                        .copyPropertiesOfList(subQuesList.get(m).getQuesOptions(), QuesOptionDto.class);
                subQuesDtos.get(m).setQuesOptions(quesOptionDtos);
                subQuesDtos.get(m).setQuesAnswer(subQuesList.get(m).getQuesAnswer());
                subQuesDtos.get(m).setNumber(m + 1);
                // 套题分数从小题类中取值
                subQuesDtos.get(m).setScore(paperDetailUnit.getSubScoreList().get(m));
                dto.setSubQuestions(subQuesDtos);
            }
        }
        if (ques.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
                || ques.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
            dto.setQuesAnswer(ques.getQuesAnswer());
        }
        appendAudioFlag(dto, examId, courseCode, groupCode);
        return dto;
    }

    private List<QuesOptionDto> buildQuestionOptionDto(List<QuesOption> quesOptions) {
        if (quesOptions == null) {
            return null;
        }
        List<QuesOptionDto> optionDtos = new ArrayList<>();
        for (QuesOption option : quesOptions) {
            QuesOptionDto optionDto = new QuesOptionDto();
            optionDto.setNumber(option.getNumber());
            optionDto.setOptionBody(option.getOptionBody());
            optionDtos.add(optionDto);
        }
        return optionDtos;
    }

    /**
     * 1.给QuestionDto添加音频播放次数 2.给试题音频中有a标签添加url
     *
     * @param questionDto
     */
    private void appendAudioFlag(QuestionDto questionDto, String examId, String courseCode, String groupCode) {
        if (questionDto.getHasAudio() != null && questionDto.getHasAudio() == true) {
            // 1.判断questionDto是否含有音频，如果有添加音频播放次数
            AudioTimeConfig audioTimeConfig = Model.of(audioTimeConfigRepo
                    .findOne(Example.of(new AudioTimeConfig(examId, courseCode, groupCode, questionDto.getId()))));
            questionDto.setPlayTime(audioTimeConfig != null ? audioTimeConfig.getPlayTime() : null);
            // 2.1 取到题干，给a标签添加url
            String quesBody = questionDto.getQuesBody();
            questionDto.setQuesBody(buildBody(quesBody, questionDto));
            // 2.2取到选项，给a标签添加url
            List<QuesOptionDto> quesOptionDtoList = questionDto.getQuesOptions();
            if (quesOptionDtoList != null && quesOptionDtoList.size() > 0) {
                for (QuesOptionDto quesOptionDto : quesOptionDtoList) {
                    quesOptionDto.setOptionBody(buildBody(quesOptionDto.getOptionBody(), questionDto));
                }
            }
        } else {
            questionDto.setPlayTime(null);
        }
    }

    // 给题目和选项添加url
    public String buildBody(String body, QuestionDto questionDto) {
        String[] bodyStrings = body.split("></a>");
        if (bodyStrings.length > 1) {
            String resultBody = "";
            for (int i = 0; i < bodyStrings.length; i++) {
                String containAStr = bodyStrings[i];
                if (containAStr.indexOf("<a") > -1) {
                    String questionAudioId = matchAudioName(containAStr, "a", "id");
                    QuestionAudio questionAudio = questionAudioService.findAudioById(questionAudioId);
                    if (questionAudio != null) {
                        //                        String url = sysProperty.getDomain() + questionAudio.getFileUrl();
                        // 通用存储
                        String url = FileStorageUtil.realPath(questionAudio.getFileUrl());
                        if (questionDto.getPlayTime() != null) {
                            containAStr += " question-audio url=\"" + url + "\" playTime=\"" + questionDto.getPlayTime()
                                    + "\"" + "></a>";
                        } else {
                            containAStr += " question-audio url=\"" + url + "\"" + "></a>";
                        }
                    }
                }
                resultBody += containAStr;
            }
            return resultBody;
        } else {
            return body;
        }
    }

    private String matchAudioName(String source, String element, String attr) {
        String reg = "<" + element + "[^<>]*?\\s" + attr + "=['\"]?(.*?)['\"]?(\\s.*?)";
        Matcher m = Pattern.compile(reg).matcher(source);
        if (m.find()) {
            return m.group(1);
        }
        return "";
    }

    /**
     * 重新对选择题option进行排序(多选、单选、套题下选择题)
     */
    private void reorderChoicequestionOption(PaperDetailUnit paperDetailUnit) {
        String optionOrder = paperDetailUnit.getOptionOrder();
        if (StringUtil.isNotBlank(optionOrder)) {
            Question question = paperDetailUnit.getQuestion();
            if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
                    || question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
                question.setQuesOptions(reorderOptionCore(question.getQuesOptions(), optionOrder));
            }
            if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
                List<Question> subQuestions = question.getSubQuestions();
                int index = 0;
                for (int k = 0; k < subQuestions.size(); k++) {
                    Question subQuestion = subQuestions.get(k);
                    if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
                            || subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
                        subQuestion.setQuesOptions(
                                reorderOptionCore(subQuestion.getQuesOptions(), optionOrder.split(";")[index]));
                        index++;
                    }
                }
            }
        }
    }

    /**
     * 对option排序
     *
     * @param quesOptions
     * @param optionOrder
     * @return
     */
    private List<QuesOption> reorderOptionCore(List<QuesOption> quesOptions, String optionOrder) {
        List<QuesOption> newQuesOptions = new ArrayList<>();
        if (StringUtil.isBlank(optionOrder) || quesOptions.isEmpty()) {
            return null;
        }
        String[] optionOrderArr = optionOrder.split(",");
        for (int j = 0; j < optionOrderArr.length; j++) {
            for (int k = 0; k < quesOptions.size(); k++) {
                if (optionOrderArr[j].equals(quesOptions.get(k).getNumber())) {
                    newQuesOptions.add(quesOptions.get(k));
                }
            }
        }
        quesOptions = null;
        return newQuesOptions;
    }

    @Override
    public Page<ExtractConfig> findPageExtractConfig(int currentPage, int pageSize, Long examId, String courseCode,
                                                     String orgId, UserDataRule ud) {
        if (ud.assertEmptyQueryResult()) {
            return Page.empty();
        }
        if (examId == null) {
            throw new StatusException("Q-", "examId is null");
        }
        Query query = new Query();
        query.addCriteria(Criteria.where("orgId").is(orgId));
        if (ud.assertNeedQueryRefIds()) {
            query.addCriteria(Criteria.where("course.id").in(ud.stringRefIds()));
        }
        query.addCriteria(Criteria.where("examId").is(examId));
        query.addCriteria(Criteria.where("course.enable").is("true"));
        if (!StringUtils.isBlank(courseCode)) {
            query.addCriteria(Criteria.where("course.code").is(courseCode));
        }

        long count = this.mongoTemplate.count(query, ExtractConfig.class);
        query.limit(pageSize);
        query.skip((currentPage - 1L) * pageSize);

        List<ExtractConfig> extractConfigList = this.mongoTemplate.find(query, ExtractConfig.class);
        if (extractConfigList != null && extractConfigList.size() > 0) {
            // 调用考务rmi，获取考试信息
            GetExamReq req = new GetExamReq();
            req.setId(examId);
            req.setRootOrgId(Long.valueOf(orgId));
            GetExamResp resp = examCloudService.getExam(req);
            ExamBean bean = resp.getExamBean();
            for (ExtractConfig extractConfig : extractConfigList) {
                extractConfig.setExamType(bean.getExamType());
            }
        }

        return new PageImpl<>(extractConfigList, PageRequest.of(currentPage - 1, pageSize), count);
    }

    @Override
    public Map<String, Object> extractPaper(String paperId) {
        Map<String, Object> returnMap = new HashMap<>();
        Paper paper = Model.of(paperRepo.findById(paperId));
        if (paper == null) {
            returnMap.put("errorMsg", "该试卷不存在");
            return returnMap;
        }
        PaperDto paperDto = getPaperDtoByPaper(paper, paperId);
        returnMap.put("paperDto", paperDto);
        return returnMap;
    }

    @Override
    public String getAnswerHtml(String paperId) {
        // 1.根据id查询试卷
        Paper paper = Model.of(paperRepo.findById(paperId));
        // 2.定义html字符串
        StringBuffer answerHtml = new StringBuffer("<p style=\"text-align:center;font-size:20px;font-weight:bold\">"
                + paper.getCourse().getName() + "(" + paper.getCourse().getCode() + ")" + "</p>");
        // 添加考试说明
        if (paper.getExamRemark() == null) {
            answerHtml.append("<p>考试说明：</p>");
        } else {
            answerHtml.append("<p>考试说明：</p>" + paper.getExamRemark());
        }
        // 3.通过试卷获取考试端试卷结构
        PaperDto paperDto = getPaperDtoByPaper(paper, paper.getId());
        // 4.获取试卷所有大题,并且循环
        List<PaperDetailDto> paperDetailDtos = paperDto.getPaperDetails();
        for (PaperDetailDto paperDetailDto : paperDetailDtos) {
            String title = "<p>" + paperDetailDto.getCnNum() + "、" + paperDetailDto.getName() + "(共"
                    + paperDetailDto.getScore() + "分)" + "</p>";
            answerHtml.append(title);
            // 5.获取大题下面的小题，并循环
            List<PaperDetailUnitDto> paperDetailUnitDtos = paperDetailDto.getPaperDetailUnits();
            for (PaperDetailUnitDto paperDetailUnitDto : paperDetailUnitDtos) {
                // 定义题干
                String body = paperDetailUnitDto.getQuesBody().replaceAll("<span>", "").replaceAll("</span>", "")
                        .replaceAll("###", "___");
                ;
                // 定义选项
                String option = "";
                // 定义答案
                String answer = "";
                // 6.如果为单选和多选
                if (paperDetailUnitDto.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
                        || paperDetailUnitDto.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
                    // 获取选项
                    List<QuesOptionDto> quesOptionDtos = paperDetailUnitDto.getQuesOptions();
                    // 添加题干
                    body = startWithP(body, paperDetailUnitDto.getNumber(), false);
                    answerHtml.append(body);
                    // 添加选项
                    for (QuesOptionDto quesOptionDto : quesOptionDtos) {
                        option = startWithP(quesOptionDto.getOptionBody(), Integer.parseInt(quesOptionDto.getNumber()),
                                true);
                        answerHtml.append(option);
                    }
                    answer = startWithP(paperDetailUnitDto.getAnswer(), -1, false);
                    answerHtml.append(answer);
                }
                // 7.如果为套题
                else if (paperDetailUnitDto.getSubQuestions() != null
                        && paperDetailUnitDto.getSubQuestions().size() > 0) {
                    // 添加主题干
                    String bigBody = CommonUtils.relaceQuestionIdx(paperDetailUnitDto.getQuesBody(), 0);
                    bigBody = bigBody.replaceAll("<span>", "").replaceAll("</span>", "").replaceAll("###", "___");
                    bigBody = startWithP(bigBody, -2, false);
                    answerHtml.append(bigBody);
                    List<SubQuestionDto> subQuestionDtos = paperDetailUnitDto.getSubQuestions();
                    for (SubQuestionDto subQuestionDto : subQuestionDtos) {
                        body = subQuestionDto.getQuesBody().replaceAll("<span>", "").replaceAll("</span>", "")
                                .replaceAll("###", "___");
                        // 如果子题为单选或者多选
                        if (subQuestionDto.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
                                || subQuestionDto.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
                            // 获取选项
                            List<QuesOptionDto> quesOptionDtos = subQuestionDto.getQuesOptions();
                            // 添加题干
                            body = startWithP(body, subQuestionDto.getNumber(), false);
                            answerHtml.append(body);
                            // 添加选项
                            for (QuesOptionDto quesOptionDto : quesOptionDtos) {
                                option = startWithP(quesOptionDto.getOptionBody(),
                                        Integer.parseInt(quesOptionDto.getNumber()), true);
                                answerHtml.append(option);
                            }
                            answer = startWithP(subQuestionDto.getQuesAnswer(), -1, false);
                            answerHtml.append(answer);
                        } else {
                            // 添加题干
                            body = startWithP(body, subQuestionDto.getNumber(), false);
                            answerHtml.append(body);
                            // 添加答案
                            answer = startWithP(subQuestionDto.getQuesAnswer(), -1, false);
                            answerHtml.append(answer);
                        }
                    }
                } else {
                    // 添加题干
                    body = startWithP(body, paperDetailUnitDto.getNumber(), false);
                    answerHtml.append(body);
                    // 添加答案
                    answer = startWithP(paperDetailUnitDto.getAnswer(), -1, false);
                    answerHtml.append(answer);
                }
            }
        }
        return answerHtml.toString();
    }

    public String startWithP(String s, Integer number, boolean option) {
        if (s == null) {
            s = "<p></p>";
        }
        if (number == -2) {
            if (s.startsWith("<p>")) {
                s = s.replaceAll("<p>", "<p style=\"margin-left:40px;\">").replaceAll("<p style=\"margin-left:40px;\">",
                        "<p style=\"margin-left:20px;\">");
            } else {
                s = "<p style=\"margin-left:20px;color:red;\">" + s + "</p>";
            }
        } else if (number == -1) {
            if (s.startsWith("<p>")) {
                s = s.replaceAll("<p>", "<p style=\"margin-left:65px;\">")
                        .replaceFirst("<p style=\"margin-left:65px;\">",
                                "<p style=\"margin-left:20px;color:red;\">" + "答案：")
                        .replace("<p style=\"margin-left:65px;\">", "<p style=\"margin-left:65px;color:red;\">");
            } else {
                s = "<p style=\"margin-left:20px;color:red;\">" + "答案：" + s + "</p>";
            }
        } else {
            if (option) {
                if (s.startsWith("<p>")) {
                    s = s.replaceAll("<p>", "<p style=\"margin-left:40px;\">").replaceFirst(
                            "<p style=\"margin-left:40px;\">",
                            "<p style=\"margin-left:20px;\">" + CommonUtils.getOptionNum(number - 1) + ".");
                } else {
                    s = "<p style=\"margin-left:20px;\">" + CommonUtils.getOptionNum(number - 1) + "." + s + "</p>";
                }
            } else {
                if (s.startsWith("<p>")) {
                    s = s.replaceAll("<p>", "<p style=\"margin-left:40px;\">").replaceFirst(
                            "<p style=\"margin-left:40px;\">", "<p style=\"margin-left:20px;\">" + number + ".");
                } else {
                    s = "<p style=\"margin-left:20px;\">" + number + "." + s + "</p>";
                }
            }
        }
        return s;
    }

    @Override
    public List<CouresInfo> findCourseByExtractConfig(Long examId, String orgId, UserDataRule ud) {
        if (ud.assertEmptyQueryResult()) {
            return new ArrayList<>();
        }
        if (examId == null) {
            throw new StatusException("500", "examId is null");
        }
        // 从考务查询改考试下的所有开启课程
        List<CouresInfo> courseInfoList = new ArrayList<>();
        GetExamCourseListReq req = new GetExamCourseListReq();
        req.setExamId(examId);
        req.setCourseEnable(true);
        Long start = 1l;
        int count = 0;
        while (true) {
            req.setStart(start);
            GetExamCourseListResp resp = examCloudService.getExamCourseList(req);
            for (ExamCourseRelationBean bean : resp.getRelationList()) {
                CouresInfo info = new CouresInfo(bean);
                courseInfoList.add(info);
            }
            if (start.equals(resp.getNext())) {
                break;
            } else {
                start = resp.getNext();
            }
            count++;
            if (count > 1000) {
                throw new StatusException("Q-", "考试下课程的数据量过大");
            }
        }
        if (CollectionUtils.isEmpty(courseInfoList)) {
            return null;
        }
        // 查询已经改考试下已经制定的课程的调卷规则
        Query query = new Query();
        query.addCriteria(Criteria.where("orgId").is(orgId));
        query.addCriteria(Criteria.where("examId").is(examId));
        List<ExtractConfig> extractConfigList = this.mongoTemplate.find(query, ExtractConfig.class);
        if (extractConfigList != null && extractConfigList.size() > 0) {
            for (ExtractConfig extractConfig : extractConfigList) {
                CouresInfo info = new CouresInfo();
                info.setCourseCode(extractConfig.getCourseCode());
                if (courseInfoList.contains(info)) {
                    courseInfoList.remove(info);
                }
            }
        }
        if (ud.assertNeedQueryRefIds() && courseInfoList.size() > 0) {
            List<CouresInfo> ret = new ArrayList<>();
            for (CouresInfo info : courseInfoList) {
                if (ud.getRefIds().contains(info.getCourseId())) {
                    ret.add(info);
                }
            }
            return ret;
        }
        return courseInfoList;
    }

    @Override
    public List<ExtractConfig> findExtractConfig(Long examId) {
        if (examId == null) {
            throw new StatusException("1001", "examId is null");
        }
        Query query = new Query();
        query.addCriteria(Criteria.where("examId").is(examId));
        query.addCriteria(Criteria.where("course.enable").is("true"));

        List<ExtractConfig> extractConfigList = this.mongoTemplate.find(query, ExtractConfig.class);

        return extractConfigList;
    }
    
    @Transactional
    @Override
    public void saveExtractConfigForRandomPaperByOuter(SaveRandomPaperExtractConfigReq param) {
    	if(param.getRootOrgId()==null) {
    		throw new StatusException("rootOrgId不能为空");
    	}
    	GetExamReq examReq=new GetExamReq();
    	examReq.setRootOrgId(param.getRootOrgId());
    	examReq.setName(param.getExamName());
    	examReq.setCode(param.getExamCode());
    	GetExamResp examRes=examCloudService.getExam(examReq);
    	if(ExamType.OFFLINE.name().equals(examRes.getExamBean().getExamType())) {
    		throw new StatusException("不能对离线考试创建调卷规则");
    	}
    	// 从考务查询改考试下的所有课程
        List<ExamCourseRelationBean> courseInfoList = new ArrayList<>();
        GetExamCourseListReq req = new GetExamCourseListReq();
        req.setExamId(examRes.getExamBean().getId());
        Long start = 1l;
        for(;;) {
            req.setStart(start);
            GetExamCourseListResp resp = examCloudService.getExamCourseList(req);
            courseInfoList.addAll(resp.getRelationList());
            if (start.equals(resp.getNext())) {
                break;
            } else {
                start = resp.getNext();
            }
        }
        if (CollectionUtils.isEmpty(courseInfoList)) {
        	throw new StatusException("请先同步考生再创建调卷规则");
        }
        for(ExamCourseRelationBean course:courseInfoList) {
        	createByCourse(param.getRootOrgId(), examRes.getExamBean(), course);
        }
    }
    
    private void createByCourse(Long rootOrgId,ExamBean examBean,ExamCourseRelationBean course) {
    	List<RandomPaperListVo> rps=randomPaperService.getList(rootOrgId,course.getCourseId());
    	if(CollectionUtils.isEmpty(rps)) {
    		throw new StatusException(course.getCourseName()+",没有启用的随机抽题模板");
    	}
    	if(rps.size()>1) {
    		throw new StatusException("存在多个启用的随机抽题模板");
    	}
    	ExtractConfig extractConfig=findConfig(examBean.getId(), course.getCourseCode());
    	if(extractConfig==null) {
	    	extractConfig=new ExtractConfig();
	    	extractConfig.setExamId(examBean.getId());
	    	extractConfig.setOrgId(rootOrgId.toString());
	    	extractConfig.setExamName(examBean.getName());
	    	extractConfig.setExamType(examBean.getExamType());
	    	extractConfig.setCourseCode(course.getCourseCode());
	        extractConfig.setCourse(of(course,rootOrgId));
	        extractConfig.setCourseName(course.getCourseName());
    	}
    	extractConfig.setRandomPaperId(rps.get(0).getId());
    	extractConfig.setCallType(CallType.RANDOM_PAPER);
		extractConfig.setPlayTime(1);
        extractConfigRepo.save(extractConfig);
     // 清除缓存
 		extractConfigCache.remove(extractConfig.getExamId(), extractConfig.getCourseCode());
    }
    private Course of(ExamCourseRelationBean course,Long rootOrgId) {
    	Course c=new Course();
    	c.setCode(course.getCourseCode());
    	c.setId(course.getCourseId().toString());
    	c.setEnable(course.getCourseEnable()==null?"false":course.getCourseEnable().toString());
    	c.setName(course.getCourseName());
    	c.setLevel(course.getCourseLevel());
    	c.setOrgId(rootOrgId.toString());
    	return c;
    }
    private ExtractConfig findConfig(Long examId,String courseCode) {
        if (examId == null) {
            return null;
        }
        if (StringUtils.isBlank(courseCode)) {
            return null;
        }
        Query query = new Query();
        query.addCriteria(Criteria.where("examId").is(examId));
        query.addCriteria(Criteria.where("courseCode").is(courseCode));
        ExtractConfig tempConfig = this.mongoTemplate.findOne(query, ExtractConfig.class);
        return tempConfig;
    }

    @Transactional
	@Override
	public void saveExtractConfigForRandomPaper(SaveRandomPaperExtractConfigForToolDomain req) {
    	if(req.getExamId()==null) {
    		throw new StatusException("ExamId不能为空");
    	}
    	GetExamReq examReq=new GetExamReq();
    	examReq.setId(req.getExamId());
    	GetExamResp examRes=examCloudService.getExam(examReq);
    	if(ExamType.OFFLINE.name().equals(examRes.getExamBean().getExamType())) {
    		throw new StatusException("不能对离线考试创建调卷规则");
    	}
    	Long rootOrgId=examRes.getExamBean().getRootOrgId();
    	Course course = courseService.getCourse(rootOrgId, req.getCourseCode());
    	GetExamCourseReq settingReq = new GetExamCourseReq();
        settingReq.setExamId(examRes.getExamBean().getId());
        settingReq.setCourseId(Long.valueOf(course.getId()));
        GetExamCourseResp settingResp = examCloudService.getExamCourseSetting(settingReq);
        if(settingResp.getBean()==null) {
        	throw new StatusException("请先同步考生后再创建相应课程的调卷规则");
        }
    	List<RandomPaperListVo> rps=randomPaperService.getList(rootOrgId, Long.valueOf(course.getId()));
    	if(CollectionUtils.isEmpty(rps)) {
    		throw new StatusException("没有启用的随机抽题模板");
    	}
    	if(rps.size()>1) {
    		throw new StatusException("存在多个启用的随机抽题模板");
    	}
    	ExtractConfig extractConfig=findConfig(examRes.getExamBean().getId(), req.getCourseCode());
    	if(extractConfig==null) {
	    	extractConfig=new ExtractConfig();
	    	extractConfig.setExamId(examRes.getExamBean().getId());
	    	extractConfig.setOrgId(rootOrgId.toString());
	    	extractConfig.setExamName(examRes.getExamBean().getName());
	    	extractConfig.setExamType(examRes.getExamBean().getExamType());
	    	extractConfig.setCourseCode(course.getCode());
	        extractConfig.setCourse(course);
	        extractConfig.setCourseName(course.getName());
    	}
    	extractConfig.setCallType(CallType.RANDOM_PAPER);
    	extractConfig.setRandomPaperId(rps.get(0).getId());
		extractConfig.setPlayTime(1);
        extractConfigRepo.save(extractConfig);
     // 清除缓存
 		extractConfigCache.remove(extractConfig.getExamId(), extractConfig.getCourseCode());
	}
}
