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

import cn.com.qmth.examcloud.api.commons.security.bean.User;
import cn.com.qmth.examcloud.commons.exception.StatusException;
import cn.com.qmth.examcloud.core.basic.api.UserCloudService;
import cn.com.qmth.examcloud.core.basic.api.bean.UserBean;
import cn.com.qmth.examcloud.core.basic.api.request.GetUserListByIdsReq;
import cn.com.qmth.examcloud.core.basic.api.request.GetUserReq;
import cn.com.qmth.examcloud.core.basic.api.response.GetUserListByIdsResp;
import cn.com.qmth.examcloud.core.basic.api.response.GetUserResp;
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.enums.PaperStructType;
import cn.com.qmth.examcloud.core.questions.base.enums.QuestionDifficulty;
import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
import cn.com.qmth.examcloud.core.questions.dao.PaperStructRepo;
import cn.com.qmth.examcloud.core.questions.dao.PropertyRepo;
import cn.com.qmth.examcloud.core.questions.dao.RandomPaperQuestionRepo;
import cn.com.qmth.examcloud.core.questions.dao.RandomPaperRepo;
import cn.com.qmth.examcloud.core.questions.dao.entity.*;
import cn.com.qmth.examcloud.core.questions.dao.entity.dto.CoursePropertyNumberDto;
import cn.com.qmth.examcloud.core.questions.dao.entity.dto.PaperDetailUnitStructDto;
import cn.com.qmth.examcloud.core.questions.service.PaperStructService;
import cn.com.qmth.examcloud.core.questions.service.RandomPaperService;
import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.*;
import cn.com.qmth.examcloud.core.questions.service.cache.PaperDetailStructCache;
import cn.com.qmth.examcloud.core.questions.service.cache.PaperStructCache;
import cn.com.qmth.examcloud.core.questions.service.cache.RandomPaperCache;
import cn.com.qmth.examcloud.core.questions.service.util.BatchGetDataUtil;
import cn.com.qmth.examcloud.core.questions.service.util.BatchSetDataUtil;
import cn.com.qmth.examcloud.core.questions.service.util.PaperUtil;
import cn.com.qmth.examcloud.question.commons.core.paper.DefaultPaper;
import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionGroup;
import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionStructureWrapper;
import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionUnitWrapper;
import cn.com.qmth.examcloud.question.commons.core.question.QuestionType;
import cn.com.qmth.examcloud.support.CacheConstants;
import cn.com.qmth.examcloud.support.cache.CacheHelper;
import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
import cn.com.qmth.examcloud.web.redis.RedisClient;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class RandomPaperServiceImpl implements RandomPaperService {

	private static final Logger log = LoggerFactory.getLogger(RandomPaperServiceImpl.class);

	private static Cache<String, RandomPaperCache> localRandomPaperCache = CacheBuilder.newBuilder()
			.expireAfterWrite(3, TimeUnit.MINUTES).build();

	private static int cacheTimeOut = 2 * 60 * 60;

	@Autowired
	private MongoTemplate mongoTemplate;

	@Resource(name = "mongoTemplate2")
	private MongoTemplate mongoTemplate2;

	@Autowired
	private PaperStructService paperStructService;

	@Autowired
	private PaperStructRepo paperStructRepo;

	@Autowired
	private RedisClient redisClient;

	@Autowired
	private PropertyRepo propertyRepo;

	@Autowired
	private RandomPaperRepo randomPaperRepo;

	@Autowired
	private RandomPaperQuestionRepo randomPaperQuestionRepo;

	@Autowired
	private UserCloudService userCloudService;

	@Override
	public Page<RandomPaperListVo> getPage(RandomPaperQuery req) {
		if (req.getUd().assertEmptyQueryResult()) {
			return Page.empty();
		}
		Query query;
		List<Criteria> cs = new ArrayList<>();
		cs.add(Criteria.where("rootOrgId").is(req.getRootOrgId()));

		if (req.getUd().assertNeedQueryRefIds()) {
			cs.add(Criteria.where("courseId").in(req.getUd().getRefIds()));
		}

		if (req.getEnable() != null) {
			cs.add(Criteria.where("enable").is(req.getEnable()));
		}

		if (req.getCourseId() != null) {
			cs.add(Criteria.where("courseId").is(req.getCourseId()));
		}
		if (StringUtils.isNotBlank(req.getName())) {
			String paperName = CommonUtils.escapeExprSpecialWord(req.getName());
			cs.add(Criteria.where("name").regex(".*?\\.*" + paperName + ".*"));
		}
		Criteria and = new Criteria();
		Criteria[] cas = new Criteria[cs.size()];
		if (StringUtils.isNotBlank(req.getId())) {
			and.andOperator(cs.toArray(cas));
			query = Query.query(new Criteria().orOperator(and, Criteria.where("id").is(req.getId())));
		} else {
			and.andOperator(cs.toArray(cas));
			query = Query.query(and);
		}

		long total = this.mongoTemplate.count(query, RandomPaper.class);
		if (total == 0) {
			return Page.empty();
		}

		PageRequest pageable = PageRequest.of(req.getPageNumber() - 1, req.getPageSize());
		query.with(Sort.by(Sort.Order.desc("creationDate")));
		query.skip(pageable.getOffset());
		query.limit(pageable.getPageSize());

		List<RandomPaperListVo> paperList = this.mongoTemplate.find(query, RandomPaperListVo.class, "randomPaper");
		if (CollectionUtils.isEmpty(paperList)) {
			return Page.empty();
		}

		for (RandomPaperListVo vo : paperList) {
			CourseCacheBean course = CacheHelper.getCourse(vo.getCourseId());
			vo.setCourseCode(course.getCode());
			vo.setCourseName(course.getName());
			vo.setPaperStructTypeStr(vo.getPaperStructType().getName());
			PaperStruct paperStruct = Model.of(paperStructRepo.findById(vo.getPaperStructId()));
			vo.setPaperStructName(paperStruct.getName());
			vo.setEnableStr(vo.getEnable() ? "启用" : "禁用");
		}
		fillUserName(paperList, req.getRootOrgId());

		return new PageImpl<>(paperList, pageable, total);
	}

	private void fillUserName(List<RandomPaperListVo> dtos, Long rootOrgId) {
		if (dtos != null && dtos.size() > 0) {
			List<Long> ids = dtos.stream().map(dto -> dto.getUpdateBy()).distinct().collect(Collectors.toList());
			List<UserBean> userList = new ArrayList<UserBean>();
			GetUserListByIdsReq req = new GetUserListByIdsReq();
			BatchGetDataUtil<UserBean, Long> tool = new BatchGetDataUtil<UserBean, Long>() {
				@Override
				public List<UserBean> getData(List<Long> paramList) {
					req.setRootOrgId(rootOrgId);
					req.setUserIdList(paramList);
					GetUserListByIdsResp resp = userCloudService.getUserListByIds(req);
					return resp.getUserBeanList();
				}

			};
			tool.getDataForBatch(userList, ids, 100);
			Map<Long, UserBean> map = userList.stream()
					.collect(Collectors.toMap(UserBean::getUserId, account -> account, (key1, key2) -> key2));
			for (RandomPaperListVo markerBean : dtos) {
				UserBean userBean = map.get(markerBean.getUpdateBy());
				markerBean.setUpdateByName(userBean.getDisplayName());
			}
		}
	}

	@Transactional
	@Override
	public void toggle(String id, Boolean enable, User user) {
		RandomPaper paperStruct = Model.of(randomPaperRepo.findById(id));
		if (paperStruct == null) {
			throw new StatusException("未找到模板");
		}
		if (!paperStruct.getRootOrgId().equals(user.getRootOrgId())) {
			throw new StatusException("非法操作");
		}
		paperStruct.setEnable(enable);
		randomPaperRepo.save(paperStruct);
	}

	@Override
	public StructInfo getStructQuestionInfo(String structId) {
		StructInfo ret = new StructInfo();
		PaperStruct ps = Model.of(paperStructRepo.findById(structId));
		ret.setTotalScore(ps.getTotalScore());
		if (PaperStructType.BLUEPRINT.equals(ps.getPaperStrucType())) {
			ret.setDifficultyDegree(ps.getDifficulty());
			if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
				List<StructQuestionInfo> sqinfos = new ArrayList<>();
				ret.setStructQuestionInfo(sqinfos);
				for (PaperDetailStruct paperDetailStruct : ps.getPaperDetailStructs()) {
					StructQuestionInfo sqinfo = new StructQuestionInfo();
					sqinfos.add(sqinfo);
					sqinfo.setDetailName(paperDetailStruct.getName());
					sqinfo.setTotalCount(paperDetailStruct.getDetailCount());
					sqinfo.setTotalScore(paperDetailStruct.getTotalScore());
					Integer simpleCount = 0;
					Integer mediumCount = 0;
					Integer difficultyCount = 0;
					simpleCount = paperDetailStruct.getPublicSimpleCount() + paperDetailStruct.getNoPublicSimpleCount();
					mediumCount = paperDetailStruct.getPublicMediumCount() + paperDetailStruct.getNoPublicMediumCount();
					difficultyCount = paperDetailStruct.getPublicDifficultyCount()
							+ paperDetailStruct.getNoPublicDifficultyCount();
					sqinfo.setHardInfo(new StructQuestionCountInfo(difficultyCount, true));
					sqinfo.setMediumInfo(new StructQuestionCountInfo(mediumCount, true));
					sqinfo.setEasyInfo(new StructQuestionCountInfo(simpleCount, true));
				}
			}
		} else if (PaperStructType.EXACT.equals(ps.getPaperStrucType())) {
			ret.setDifficultyDegree(getExactDifficulty(ps));
			if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
				List<StructQuestionInfo> sqinfos = new ArrayList<>();
				ret.setStructQuestionInfo(sqinfos);
				for (PaperDetailStruct paperDetailStruct : ps.getPaperDetailStructs()) {
					StructQuestionInfo sqinfo = new StructQuestionInfo();
					sqinfos.add(sqinfo);
					sqinfo.setDetailName(paperDetailStruct.getName());
					sqinfo.setTotalCount(paperDetailStruct.getDetailCount());
					sqinfo.setTotalScore(paperDetailStruct.getTotalScore());
					Integer simpleCount = 0;
					Integer mediumCount = 0;
					Integer difficultyCount = 0;
					Integer anyCount = 0;
					if (CollectionUtils.isNotEmpty(paperDetailStruct.getUnitStructs())) {
						for (PaperDetailUnitStructDto unitStruct : paperDetailStruct.getUnitStructs()) {
							simpleCount = simpleCount + unitStruct.getPublicSimple() + unitStruct.getNoPublicSimple();
							mediumCount = mediumCount + unitStruct.getPublicMedium() + unitStruct.getNoPublicMedium();
							difficultyCount = difficultyCount + unitStruct.getPublicDifficulty()
									+ unitStruct.getNoPublicDifficulty();
							anyCount=anyCount+getIntVal(unitStruct.getPublicAnyDifficulty())
							+getIntVal(unitStruct.getNoPublicAnyDifficulty());
						}
					}
					sqinfo.setHardInfo(new StructQuestionCountInfo(difficultyCount, true));
					sqinfo.setMediumInfo(new StructQuestionCountInfo(mediumCount, true));
					sqinfo.setEasyInfo(new StructQuestionCountInfo(simpleCount, true));
					sqinfo.setAnyInfo(new StructQuestionCountInfo(anyCount, true));
				}
			}
		}
		return ret;
	}
	
	private int getIntVal(Integer val) {
		if(val==null) {
			return 0;
		}
		return val;
	}

	private Double getExactDifficulty(PaperStruct ps) {
		Double sum = 0.0;
		Double totalScore = ps.getTotalScore();
		if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
			Integer simpleCount = 0;
			Integer mediumCount = 0;
			Integer difficultyCount = 0;
			for (PaperDetailStruct paperDetailStruct : ps.getPaperDetailStructs()) {
				if (CollectionUtils.isNotEmpty(paperDetailStruct.getUnitStructs())) {
					for (PaperDetailUnitStructDto unitStruct : paperDetailStruct.getUnitStructs()) {
						if((unitStruct.getNoPublicAnyDifficulty()!=null&&unitStruct.getNoPublicAnyDifficulty()>0)
								||(unitStruct.getPublicAnyDifficulty()!=null&&unitStruct.getPublicAnyDifficulty()>0)) {
							return null;
						}
						simpleCount = unitStruct.getPublicSimple() + unitStruct.getNoPublicSimple();
						mediumCount = unitStruct.getPublicMedium() + unitStruct.getNoPublicMedium();
						difficultyCount = unitStruct.getPublicDifficulty() + unitStruct.getNoPublicDifficulty();
						sum = simpleCount * unitStruct.getScore() * 0.8 + mediumCount * unitStruct.getScore() * 0.5
								+ difficultyCount * unitStruct.getScore() * 0.2 + sum;
					}
				}
			}

			Double dif = sum / totalScore;
			BigDecimal b = BigDecimal.valueOf(dif);
			Double difficulty = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
			return difficulty;
		}
		return (double) 0;
	}

	@Override
	public StructInfo getPaperQuestionViewInfo(PaperQuestionViewQuery query) {
		String structId = query.getStructId();
		List<String> paperIds = query.getPaperIds();
		if (StringUtils.isBlank(structId)) {
			throw new StatusException("structId不能为空");
		}
		if (CollectionUtils.isEmpty(paperIds)) {
			throw new StatusException("paperIds不能为空");
		}
		StructInfo ret = getPaperQuestionInfo(structId, paperIds);
		clearQuestionIds(ret);
		return ret;
	}

	private StructInfo getPaperQuestionInfo(String structId, List<String> paperIds) {
		StructInfo ret = new StructInfo();
		ret.setValid(true);
		PaperStruct ps = Model.of(paperStructRepo.findById(structId));
		List<QuestionDto> questionList = new ArrayList<>();
		List<PaperDetailUnitDto> unitList = findUnitByPaperIds(paperIds);
		fillQuestionAndDetail(unitList);
		StructQuestionCheckDto cd = new StructQuestionCheckDto();
		cd.setQuestionList(questionList);
		if (PaperStructType.BLUEPRINT.equals(ps.getPaperStrucType())) {
			for (PaperDetailUnitDto unit : unitList) {
				unit.getQuestion().addQuesName(unit.getPaperDetail().getName());
				unit.getQuestion().setPropertyGroup(bulidPropertyGroup(unit.getQuestion()));
				questionList.add(unit.getQuestion());
			}
			if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
				List<StructQuestionInfo> sqinfos = new ArrayList<>();
				ret.setStructQuestionInfo(sqinfos);
				int detailNumber = 0;
				for (PaperDetailStruct ds : ps.getPaperDetailStructs()) {
					detailNumber++;
					StructQuestionInfo sqinfo = new StructQuestionInfo();
					sqinfos.add(sqinfo);
					sqinfo.setDetailName(ds.getName());
					cd.setDetailNumber(detailNumber);
					cd.setDs(ds);
					cd.setSqinfo(sqinfo);
					cd.setUnitScore(ds.getScore());
					for (CoursePropertyNumberDto cp : ds.getCoursePropertyNumberDtos()) {
						if (!cp.getDisable()) {
							cd.setCp(cp);
							setQuestionInfoByBlue(cd);
						}
					}
					sqinfo.setTotalCount(sqinfo.getHardInfo().getCount() + sqinfo.getMediumInfo().getCount()
							+ sqinfo.getEasyInfo().getCount());
				}
			}
		} else if (PaperStructType.EXACT.equals(ps.getPaperStrucType())) {
			for (PaperDetailUnitDto unit : unitList) {
				unit.getQuestion().addQuesName(unit.getPaperDetail().getName());
				questionList.add(unit.getQuestion());
			}
			if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
				List<StructQuestionInfo> sqinfos = new ArrayList<>();
				ret.setStructQuestionInfo(sqinfos);
				int detailNumber = 0;
				for (PaperDetailStruct ds : ps.getPaperDetailStructs()) {
					detailNumber++;
					StructQuestionInfo sqinfo = new StructQuestionInfo();
					sqinfos.add(sqinfo);
					sqinfo.setDetailName(ds.getName());
					cd.setDetailNumber(detailNumber);
					cd.setSqinfo(sqinfo);
					int index = 0;
					for (PaperDetailUnitStructDto us : ds.getUnitStructs()) {
						index++;
						cd.setUnitScore(us.getScore());
						cd.setIndex(index);
						cd.setUs(us);
						setQuestionInfoByExact(cd);
					}
					sqinfo.setTotalCount(sqinfo.getHardInfo().getCount() + sqinfo.getMediumInfo().getCount()
							+ sqinfo.getEasyInfo().getCount()+sqinfo.getAnyInfo().getCount());
				}
			}
		}
		fillValid(ret);
		return ret;
	}

	private void fillQuestionAndDetail(List<PaperDetailUnitDto> units) {
		if (CollectionUtils.isNotEmpty(units)) {
			new BatchSetDataUtil<PaperDetailUnitDto>() {

				@Override
				protected void setData(List<PaperDetailUnitDto> dataList) {
					List<String> ids = dataList.stream().map(p -> p.getQuestion().getId()).collect(Collectors.toList());
					List<QuestionDto> temList = findQuestionByIds(ids);
					if (CollectionUtils.isNotEmpty(temList)) {
						Map<String, QuestionDto> map = new HashMap<>();
						for (QuestionDto vo : temList) {
							map.put(vo.getId(), vo);
						}
						for (PaperDetailUnitDto dto : dataList) {
							dto.setQuestion(map.get(dto.getQuestion().getId()));
						}
					}
					ids = dataList.stream().map(p -> p.getPaperDetail().getId()).collect(Collectors.toList());
					List<PaperDetailDto> details = findDetailByIds(ids);
					if (CollectionUtils.isNotEmpty(temList)) {
						Map<String, PaperDetailDto> map = new HashMap<>();
						for (PaperDetailDto vo : details) {
							map.put(vo.getId(), vo);
						}
						for (PaperDetailUnitDto dto : dataList) {
							dto.setPaperDetail(map.get(dto.getPaperDetail().getId()));
						}
					}
				}
			}.setDataForBatch(units, 1000);
		}
	}

	private List<QuestionDto> findQuestionByIds(List<String> questionIds) {
		List<Object> ids = new ArrayList<>();
		for (String pid : questionIds) {
			if (pid.length() > 24) {
				ids.add(pid);
			} else {
				ids.add(new ObjectId(pid));
			}
		}
		Query query = new Query();
		query.addCriteria(Criteria.where("id").in(ids));
		List<QuestionDto> units = this.mongoTemplate.find(query, QuestionDto.class, "question");
		return units;
	}

	private List<PaperDetailDto> findDetailByIds(List<String> detailIds) {
		List<Object> ids = new ArrayList<>();
		for (String pid : detailIds) {
			if (pid.length() > 24) {
				ids.add(pid);
			} else {
				ids.add(new ObjectId(pid));
			}
		}
		Query query = new Query();
		query.addCriteria(Criteria.where("id").in(ids));
		List<PaperDetailDto> units = this.mongoTemplate.find(query, PaperDetailDto.class, "paperDetail");
		return units;
	}

	private void fillValid(StructInfo ret) {
		if (CollectionUtils.isEmpty(ret.getStructQuestionInfo())) {
			ret.setValid(false);
			return;
		}
		for (StructQuestionInfo si : ret.getStructQuestionInfo()) {
			if (!si.getHardInfo().getValid()) {
				ret.setValid(false);
			}
			if (!si.getMediumInfo().getValid()) {
				ret.setValid(false);
			}
			if (!si.getEasyInfo().getValid()) {
				ret.setValid(false);
			}
			if (!si.getAnyInfo().getValid()) {
				ret.setValid(false);
			}
		}
	}

	private void clearQuestionIds(StructInfo ret) {
		if (CollectionUtils.isEmpty(ret.getStructQuestionInfo())) {
			ret.setValid(false);
			return;
		}
		for (StructQuestionInfo si : ret.getStructQuestionInfo()) {
			if (!si.getHardInfo().getValid()) {
				ret.setValid(false);
			}
			if (!si.getMediumInfo().getValid()) {
				ret.setValid(false);
			}
			if (!si.getEasyInfo().getValid()) {
				ret.setValid(false);
			}
			if (!si.getAnyInfo().getValid()) {
				ret.setValid(false);
			}
			for (RandomPaperQuestionDto dto : si.getHardInfo().getQuestionInfo()) {
				dto.setQuestionDtos(new ArrayList<>());
			}
			for (RandomPaperQuestionDto dto : si.getMediumInfo().getQuestionInfo()) {
				dto.setQuestionDtos(new ArrayList<>());
			}
			for (RandomPaperQuestionDto dto : si.getEasyInfo().getQuestionInfo()) {
				dto.setQuestionDtos(new ArrayList<>());
			}
			for (RandomPaperQuestionDto dto : si.getAnyInfo().getQuestionInfo()) {
				dto.setQuestionDtos(new ArrayList<>());
			}
		}
	}

	private void setQuestionInfoByExact(StructQuestionCheckDto cd) {
		PaperDetailUnitStructDto us = cd.getUs();
		StructQuestionInfo sqinfo = cd.getSqinfo();
		if (us.getNoPublicDifficulty() > 0) {
			cd.setSi(sqinfo.getHardInfo());
			cd.setPub(false);
			cd.setDifficulty(QuestionDifficulty.HARD.getName());
			cd.setNeedCount(us.getNoPublicDifficulty());
			setQuestionInfoByExactItem(cd);
		}
		if (us.getPublicDifficulty() > 0) {
			cd.setSi(sqinfo.getHardInfo());
			cd.setPub(true);
			cd.setDifficulty(QuestionDifficulty.HARD.getName());
			cd.setNeedCount(us.getPublicDifficulty());
			setQuestionInfoByExactItem(cd);
		}

		if (us.getNoPublicMedium() > 0) {
			cd.setSi(sqinfo.getMediumInfo());
			cd.setPub(false);
			cd.setDifficulty(QuestionDifficulty.MEDIUM.getName());
			cd.setNeedCount(us.getNoPublicMedium());
			setQuestionInfoByExactItem(cd);
		}
		if (us.getPublicMedium() > 0) {
			cd.setSi(sqinfo.getMediumInfo());
			cd.setPub(true);
			cd.setDifficulty(QuestionDifficulty.MEDIUM.getName());
			cd.setNeedCount(us.getPublicMedium());
			setQuestionInfoByExactItem(cd);
		}
		if (us.getNoPublicSimple() > 0) {
			cd.setSi(sqinfo.getEasyInfo());
			cd.setPub(false);
			cd.setDifficulty(QuestionDifficulty.EASY.getName());
			cd.setNeedCount(us.getNoPublicSimple());
			setQuestionInfoByExactItem(cd);
		}
		if (us.getPublicSimple() > 0) {
			cd.setSi(sqinfo.getEasyInfo());
			cd.setPub(true);
			cd.setDifficulty(QuestionDifficulty.EASY.getName());
			cd.setNeedCount(us.getPublicSimple());
			setQuestionInfoByExactItem(cd);
		}
		//不限难度的一定要在最后抽题
		if (getIntVal(us.getPublicAnyDifficulty()) > 0) {
			cd.setSi(sqinfo.getAnyInfo());
			cd.setPub(true);
			cd.setDifficulty(QuestionDifficulty.ANY.getName());
			cd.setNeedCount(us.getPublicAnyDifficulty());
			setQuestionInfoByExactItem(cd);
		}
		if (getIntVal(us.getNoPublicAnyDifficulty()) > 0) {
			cd.setSi(sqinfo.getAnyInfo());
			cd.setPub(false);
			cd.setDifficulty(QuestionDifficulty.ANY.getName());
			cd.setNeedCount(us.getNoPublicAnyDifficulty());
			setQuestionInfoByExactItem(cd);
		}
	}

	private void setQuestionInfoByExactItem(StructQuestionCheckDto cd) {
		StructQuestionCountInfo si = cd.getSi();
		RandomPaperQuestionDto rq = new RandomPaperQuestionDto();
		rq.setUnitScore(cd.getUnitScore());
		rq.setDetailNumber(cd.getDetailNumber());
		rq.setKey(cd.getIndex() + "-" + cd.getPub() + "-" + cd.getDifficulty());
		si.getQuestionInfo().add(rq);
		if (CollectionUtils.isNotEmpty(cd.getQuestionList())) {
			Iterator<QuestionDto> it = cd.getQuestionList().iterator();
			while (it.hasNext()) {
				QuestionDto q = it.next();
				if (cd.getUsedQuesIds().contains(q.getId())) {
					it.remove();
				} else {
					if (checkExactQuesType(cd.getUs().getQuesNames(), cd.getUs().getQuestionType(), cd.getPub(),
							cd.getDifficulty(), q)) {
						rq.getQuestionDtos().add(q);
						cd.getUsedQuesIds().add(q.getId());
						it.remove();
					}
				}
			}
		}
		si.setCount(si.getCount() + rq.getQuestionDtos().size());
		if (si.getValid() && cd.getNeedCount() > rq.getQuestionDtos().size()) {
			si.setValid(false);
			si.setInvalidMsg(getExactErrmsg(cd.getIndex(), cd.getDetailNumber(), cd.getPub(), cd.getDifficulty()));
		}
	}

	private void setQuestionInfoByBlue(StructQuestionCheckDto cd) {
		StructQuestionInfo sqinfo = cd.getSqinfo();
		CoursePropertyNumberDto cp = cd.getCp();
		if (cp.getNoPublicDifficulty() > 0) {
			cd.setSi(sqinfo.getHardInfo());
			cd.setPub(false);
			cd.setDifficulty(QuestionDifficulty.HARD.getName());
			cd.setNeedCount(cp.getNoPublicDifficulty());
			setQuestionInfoByBlueProp(cd);
		}
		if (cp.getPublicDifficulty() > 0) {
			cd.setSi(sqinfo.getHardInfo());
			cd.setPub(true);
			cd.setDifficulty(QuestionDifficulty.HARD.getName());
			cd.setNeedCount(cp.getPublicDifficulty());
			setQuestionInfoByBlueProp(cd);
		}

		if (cp.getNoPublicMedium() > 0) {
			cd.setSi(sqinfo.getMediumInfo());
			cd.setPub(false);
			cd.setDifficulty(QuestionDifficulty.MEDIUM.getName());
			cd.setNeedCount(cp.getNoPublicMedium());
			setQuestionInfoByBlueProp(cd);
		}
		if (cp.getPublicMedium() > 0) {
			cd.setSi(sqinfo.getMediumInfo());
			cd.setPub(true);
			cd.setDifficulty(QuestionDifficulty.MEDIUM.getName());
			cd.setNeedCount(cp.getPublicMedium());
			setQuestionInfoByBlueProp(cd);
		}
		if (cp.getNoPublicSimple() > 0) {
			cd.setSi(sqinfo.getEasyInfo());
			cd.setPub(false);
			cd.setDifficulty(QuestionDifficulty.EASY.getName());
			cd.setNeedCount(cp.getNoPublicSimple());
			setQuestionInfoByBlueProp(cd);
		}
		if (cp.getPublicSimple() > 0) {
			cd.setSi(sqinfo.getEasyInfo());
			cd.setPub(true);
			cd.setDifficulty(QuestionDifficulty.EASY.getName());
			cd.setNeedCount(cp.getPublicSimple());
			setQuestionInfoByBlueProp(cd);
		}
	}

	private void setQuestionInfoByBlueProp(StructQuestionCheckDto cd) {
		StructQuestionCountInfo si = cd.getSi();
		RandomPaperQuestionDto rq = new RandomPaperQuestionDto();
		rq.setUnitScore(cd.getUnitScore());
		rq.setDetailNumber(cd.getDetailNumber());
		rq.setKey(bulidPropertyGroupByBlueStruct(cd.getCp().getPropertyParentId(), cd.getCp().getPropertyId(),
				cd.getPub(), cd.getDifficulty()));
		si.getQuestionInfo().add(rq);
		if (CollectionUtils.isNotEmpty(cd.getQuestionList())) {
			Iterator<QuestionDto> it = cd.getQuestionList().iterator();
			while (it.hasNext()) {
				QuestionDto q = it.next();
				if (cd.getUsedQuesIds().contains(q.getId())) {
					it.remove();
				} else {
					if (checkBlueQuesType(cd.getDs().getQuesNames(), cd.getDs().getQuestionType(), rq.getKey(), q)) {
						rq.getQuestionDtos().add(q);
						cd.getUsedQuesIds().add(q.getId());
						it.remove();
					}
				}
			}
		}
		si.setCount(si.getCount() + rq.getQuestionDtos().size());
		if (si.getValid() && cd.getNeedCount() > rq.getQuestionDtos().size()) {
			si.setValid(false);
			si.setInvalidMsg(getBlueErrmsg(cd.getDetailNumber(), cd.getCp().getPropertyParentId(),
					cd.getCp().getPropertyId(), cd.getPub(), cd.getDifficulty()));
		}
	}

	private String getExactErrmsg(Integer index, Integer detailNumber, Boolean pub, String difficulty) {
		String pubstr;
		if (pub) {
			pubstr = "公开";
		} else {
			pubstr = "非公开";
		}
		return "第" + detailNumber + "大题 " + "第" + index + "题型结构 " + pubstr + "-" + difficulty + "题源数量不满足";
	}

	private String getBlueErrmsg(Integer detailNumber, String pproid, String proid, Boolean pub, String difficulty) {
		String pubstr;
		if (pub) {
			pubstr = "公开";
		} else {
			pubstr = "非公开";
		}
		if (StringUtils.isNotBlank(pproid) && !"0".equals(pproid)) {
			// 有一级 和 二级
			Property fp = Model.of(propertyRepo.findById(pproid));
			Property sp = Model.of(propertyRepo.findById(proid));
			return "第" + detailNumber + "大题 " + fp.getName() + "-" + sp.getName() + "-" + pubstr + "-" + difficulty
					+ "题源数量不满足";
		} else {
			// 有一级 无 二级
			Property fp = Model.of(propertyRepo.findById(proid));
			return "第" + detailNumber + "大题 " + fp.getName() + "-" + pubstr + "-" + difficulty + "题源数量不满足";
		}
	}
	
	private boolean checkHas(List<String> list,Set<String> set) {
		if(list==null||set==null) {
			return false;
		}
		if(list.size()==0||set.size()==0) {
			return false;
		}
		for(String s:list) {
			if(set.contains(s)) {
				return true;
			}
		}
		return false;
	}

	private boolean checkExactQuesType(List<String> quesNames, QuesStructType st, Boolean pub, String difficulty,
			QuestionDto question) {
		if (CollectionUtils.isNotEmpty(quesNames)) {
			if (checkHas(quesNames, question.getQuesName())&& st.equals(question.getQuestionType())) {
				if(QuestionDifficulty.ANY.getName().equals(difficulty)) {
					if (question.getPublicity().equals(pub)) {
						return true;
					}
				}else {
					if (question.getPublicity().equals(pub) && question.getDifficulty().equals(difficulty)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	private boolean checkBlueQuesType(List<String> quesNames, QuesStructType st, String propertyGroup,
			QuestionDto question) {
		if (CollectionUtils.isNotEmpty(quesNames)) {
			if (checkHas(quesNames, question.getQuesName()) && st.equals(question.getQuestionType())) {
				if (question.getPropertyGroup() != null) {
					if (question.getPropertyGroup().contains(propertyGroup)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	private List<PaperDetailUnitDto> findUnitByPaperIds(List<String> paperIds) {
		List<Object> ids = new ArrayList<>();
		for (String pid : paperIds) {
			if (pid.length() > 24) {
				ids.add(pid);
			} else {
				ids.add(new ObjectId(pid));
			}
		}
		Query query = new Query();
		query.addCriteria(Criteria.where("paper.$id").in(ids));
		List<PaperDetailUnitDto> units = this.mongoTemplate2.find(query, PaperDetailUnitDto.class, "paperDetailUnit");
		return units;
	}

	private String bulidPropertyGroupByBlueStruct(String pproid, String proid, Boolean pub, String difficulty) {
		String propertyGroup = null;
		// 获取试题关联的多组属性
		if (StringUtils.isNotBlank(pproid) && !"0".equals(pproid)) {
			// 有一级 和 二级
			propertyGroup = pproid + "-" + proid + "-" + pub + "-" + difficulty;
		} else {
			// 有一级 无 二级
			propertyGroup = proid + "-" + pub + "-" + difficulty;
		}
		return propertyGroup;
	}

	private List<String> bulidPropertyGroup(QuestionDto question) {
		String propertyGroup = null;
		List<String> propertyGroups = new ArrayList<>();
		// 获取试题关联的多组属性
		List<QuesProperty> quesProperties = question.getQuesProperties();
		if (quesProperties != null && quesProperties.size() > 0) {
			for (QuesProperty quesProperty : quesProperties) {
				if (quesProperty.getSecondProperty() != null) {
					// 有一级 和 二级
					if (quesProperty.getSecondProperty().getId() == null
							|| StringUtils.isBlank(String.valueOf(quesProperty.getSecondProperty().getId()))) {
						propertyGroup = String.valueOf(quesProperty.getFirstProperty().getId()) + "-"
								+ String.valueOf(question.getPublicity()) + "-" + question.getDifficulty();
					} else {
						propertyGroup = String.valueOf(quesProperty.getFirstProperty().getId()) + "-"
								+ String.valueOf(quesProperty.getSecondProperty().getId()) + "-"
								+ String.valueOf(question.getPublicity()) + "-" + question.getDifficulty();
					}
					propertyGroups.add(propertyGroup);
				} else {
					// 有一级 无 二级
					propertyGroup = String.valueOf(quesProperty.getFirstProperty().getId()) + "-"
							+ String.valueOf(question.getPublicity()) + "-" + question.getDifficulty();
					propertyGroups.add(propertyGroup);
				}
			}
			return propertyGroups;
		}
		return null;
	}

	@Transactional
	@Override
	public StructInfo saveRandomPaper(RandomPaperDomain domain) {
		if (domain.getCourseId() == null) {
			throw new StatusException("课程id不能为空");
		}
		if (StringUtils.isBlank(domain.getName())) {
			throw new StatusException("模板名称不能为空");
		}
		if (domain.getPaperStructType() == null) {
			throw new StatusException("组卷模式不能为空");
		}
		if (domain.getPaperStructId() == null) {
			throw new StatusException("组卷结构不能为空");
		}
		if (domain.getPaperType() == null) {
			throw new StatusException("题源范围不能为空");
		}
		if (CollectionUtils.isEmpty(domain.getPaperIds())) {
			throw new StatusException("试卷id不能为空");
		}
		RandomPaper rp = randomPaperRepo.findByRootOrgIdAndName(domain.getRootOrgId(), domain.getName());
		if (rp != null && !rp.getId().equals(domain.getId())) {
			throw new StatusException("模板名称已存在");
		}
		StructInfo ret = getPaperQuestionInfo(domain.getPaperStructId(), domain.getPaperIds());
		if (ret.getValid()) {
			RandomPaper e;
			if (StringUtils.isNotBlank(domain.getId())) {
				e = Model.of(randomPaperRepo.findById(domain.getId()));
			} else {
				e = new RandomPaper();
				e.setCourseId(domain.getCourseId());
				e.setEnable(true);
				e.setRootOrgId(domain.getRootOrgId());
			}
			e.setName(domain.getName());
			e.setPaperIds(domain.getPaperIds());
			e.setPaperStructType(domain.getPaperStructType());
			e.setPaperStructId(domain.getPaperStructId());
			e.setPaperType(domain.getPaperType());
			randomPaperRepo.save(e);
			randomPaperQuestionRepo.deleteByRandomPaperId(e.getId());
			List<RandomPaperQuestion> rqs = new ArrayList<>();
			for (StructQuestionInfo si : ret.getStructQuestionInfo()) {
				for (RandomPaperQuestionDto dto : si.getHardInfo().getQuestionInfo()) {
					addRqs(rqs, dto, e);
				}
				for (RandomPaperQuestionDto dto : si.getMediumInfo().getQuestionInfo()) {
					addRqs(rqs, dto, e);
				}
				for (RandomPaperQuestionDto dto : si.getEasyInfo().getQuestionInfo()) {
					addRqs(rqs, dto, e);
				}
				for (RandomPaperQuestionDto dto : si.getAnyInfo().getQuestionInfo()) {
					addRqs(rqs, dto, e);
				}
			}
			randomPaperQuestionRepo.saveAll(rqs);
			String key = CacheConstants.CACHE_Q_RANDOM_PAPER + e.getId();
			redisClient.delete(key);
		}
		clearQuestionIds(ret);
		return ret;
	}

	private void addRqs(List<RandomPaperQuestion> rqs, RandomPaperQuestionDto dto, RandomPaper e) {
		if (CollectionUtils.isNotEmpty(dto.getQuestionDtos())) {
			for (QuestionDto qdto : dto.getQuestionDtos()) {
				RandomPaperQuestion rq = new RandomPaperQuestion();
				rqs.add(rq);
				rq.setCourseId(e.getCourseId());
				rq.setKey(dto.getDetailNumber() + "-" + dto.getKey());
				rq.setQuestionId(qdto.getId());
				rq.setRandomPaperId(e.getId());
				rq.setRootOrgId(e.getRootOrgId());
				rq.setScore(dto.getUnitScore());
				rq.setQuestionType(qdto.getQuestionType());
				rq.setAnswerType(qdto.getAnswerType());
				if (CollectionUtils.isNotEmpty(qdto.getQuesOptions())) {
					rq.setOptionCount(qdto.getQuesOptions().size());
				}
				if (QuesStructType.NESTED_ANSWER_QUESTION.equals(rq.getQuestionType())
						&& CollectionUtils.isNotEmpty(qdto.getSubQuestions())) {
					List<RandomPaperQuestion> subQuestion = new ArrayList<>();
					rq.setSubQuestions(subQuestion);
					List<Double> subScores = getSubScoreList(rq.getScore(), qdto.getSubQuestions().size());
					int i = 0;
					for (QuestionDto subQd : qdto.getSubQuestions()) {
						RandomPaperQuestion subrq = new RandomPaperQuestion();
						subQuestion.add(subrq);
						subrq.setCourseId(e.getCourseId());
						subrq.setScore(subScores.get(i));
						i++;
						subrq.setQuestionType(subQd.getQuestionType());
						subrq.setAnswerType(subQd.getAnswerType());
						if (CollectionUtils.isNotEmpty(subQd.getQuesOptions())) {
							subrq.setOptionCount(subQd.getQuesOptions().size());
						}
					}
				}
			}
		}
	}

	private List<Double> getSubScoreList(double totalScore, int count) {
		List<Double> scoreList = new ArrayList<>();
		if (count > 0) {
			int baseScore = (int) (totalScore / count);
			double leftScore = totalScore;
			for (int i = 0; i < count; i++) {
				scoreList.add((double) baseScore);
				leftScore -= baseScore;
			}
			if (leftScore > 0) {
				scoreList.set(count - 1, baseScore + leftScore);
			}
			return scoreList;
		}
		return null;
	}

	@Override
	public RandomPaperListVo getInfo(String id) {
		RandomPaperListVo vo = this.mongoTemplate.findById(id, RandomPaperListVo.class, "randomPaper");
		CourseCacheBean course = CacheHelper.getCourse(vo.getCourseId());
		vo.setCourseCode(course.getCode());
		vo.setCourseName(course.getName());
		vo.setPaperStructTypeStr(vo.getPaperStructType().getName());
		PaperStruct paperStruct = Model.of(paperStructRepo.findById(vo.getPaperStructId()));
		vo.setPaperStructName(paperStruct.getName());
		vo.setEnableStr(vo.getEnable() ? "启用" : "禁用");
		GetUserReq ureq = new GetUserReq();
		ureq.setUserId(vo.getUpdateBy());
		GetUserResp ures = userCloudService.getUser(ureq);
		vo.setUpdateByName(ures.getUserBean().getDisplayName());
		Query query = new Query();
		List<Object> ids = new ArrayList<>();
		for (String pid : vo.getPaperIds()) {
			if (pid.length() > 24) {
				ids.add(pid);
			} else {
				ids.add(new ObjectId(pid));
			}
		}
		query.addCriteria(Criteria.where("id").in(ids));
		List<PaperVo> papers = this.mongoTemplate.find(query, PaperVo.class, "paper");
		vo.setPapers(papers);
		return vo;
	}

	@Override
	public DefaultPaper getRandomPaper(String randomPaperId, Integer playTime) {
		long start = System.currentTimeMillis();
		RandomPaperCache rp = this.getRandomPaperTemplateCacheById(randomPaperId);
		long start2 = System.currentTimeMillis();
		log.warn("获取抽卷模板！ 耗时：{}ms ID:{} 题数量：{}", start2 - start, randomPaperId, rp.getQuestionMap().size());

		PaperStructCache ps = paperStructService.getPaperStructCacheById(rp.getPaperStructId());
		log.warn("获取组卷结构！ 耗时：{}ms 结构类型：{} ID：{}", System.currentTimeMillis() - start2, ps.getPaperStrucType(),
				rp.getPaperStructId());

		CreateDefaultPaperParam param = new CreateDefaultPaperParam();
		param.setFullyObjective(true);
		param.setRp(rp);
		param.setPlayTime(playTime);
		DefaultPaper paper = new DefaultPaper();
		paper.setName(rp.getName());
		List<DefaultQuestionGroup> details = new ArrayList<>();
		paper.setQuestionGroupList(details);

		if (PaperStructType.BLUEPRINT.equals(ps.getPaperStrucType())) {
			if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
				int detailNumber = 0;
				for (PaperDetailStructCache ds : ps.getPaperDetailStructs()) {
					DefaultQuestionGroup detail = new DefaultQuestionGroup();
					details.add(detail);
					detail.setGroupName(ds.getName());
					detail.setGroupScore(ds.getTotalScore());
					List<DefaultQuestionStructureWrapper> units = new ArrayList<>();
					detail.setQuestionWrapperList(units);
					detailNumber++;
					param.setUnits(units);
					param.setDetailNumber(detailNumber);
					for (CoursePropertyNumberDto cp : ds.getCoursePropertyNumberDtos()) {
						if (!cp.getDisable()) {
							param.setCp(cp);
							createUnitByBlueProp(param);
						}
					}
				}
			}
		} else if (PaperStructType.EXACT.equals(ps.getPaperStrucType())) {
			if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
				int detailNumber = 0;
				for (PaperDetailStructCache ds : ps.getPaperDetailStructs()) {
					DefaultQuestionGroup detail = new DefaultQuestionGroup();
					details.add(detail);
					detail.setGroupName(ds.getName());
					detail.setGroupScore(ds.getTotalScore());
					List<DefaultQuestionStructureWrapper> units = new ArrayList<>();
					detail.setQuestionWrapperList(units);
					detailNumber++;
					param.setUnits(units);
					param.setDetailNumber(detailNumber);
					int index = 0;
					for (PaperDetailUnitStructDto us : ds.getUnitStructs()) {
						index++;
						param.setIndex(index);
						param.setUs(us);
						createUnitByExact(param);
					}
				}
			}
		}
		paper.setFullyObjective(param.getFullyObjective());

		log.warn("抽卷完成！ 总耗时：{}ms ID:{}", System.currentTimeMillis() - start, randomPaperId);
		return paper;
	}

	private void createUnitByExact(CreateDefaultPaperParam param) {
		PaperDetailUnitStructDto us = param.getUs();
		if (us.getNoPublicDifficulty() > 0) {
			param.setUnitCount(us.getNoPublicDifficulty());
			String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + false + "-"
					+ QuestionDifficulty.HARD.getName();
			param.setKey(key);
			createUnit(param);
		}
		if (us.getPublicDifficulty() > 0) {
			param.setUnitCount(us.getPublicDifficulty());
			String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + true + "-"
					+ QuestionDifficulty.HARD.getName();
			param.setKey(key);
			createUnit(param);
		}

		if (us.getNoPublicMedium() > 0) {
			param.setUnitCount(us.getNoPublicMedium());
			String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + false + "-"
					+ QuestionDifficulty.MEDIUM.getName();
			param.setKey(key);
			createUnit(param);
		}
		if (us.getPublicMedium() > 0) {
			param.setUnitCount(us.getPublicMedium());
			String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + true + "-"
					+ QuestionDifficulty.MEDIUM.getName();
			param.setKey(key);
			createUnit(param);
		}
		if (us.getNoPublicSimple() > 0) {
			param.setUnitCount(us.getNoPublicSimple());
			String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + false + "-"
					+ QuestionDifficulty.EASY.getName();
			param.setKey(key);
			createUnit(param);
		}
		if (us.getPublicSimple() > 0) {
			param.setUnitCount(us.getPublicSimple());
			String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + true + "-"
					+ QuestionDifficulty.EASY.getName();
			param.setKey(key);
			createUnit(param);
		}
		//不限难度的一定要在最后抽题
		if (getIntVal(us.getPublicAnyDifficulty()) > 0) {
			param.setUnitCount(us.getPublicAnyDifficulty());
			String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + true + "-"
					+ QuestionDifficulty.ANY.getName();
			param.setKey(key);
			createUnit(param);
		}
		if (getIntVal(us.getNoPublicAnyDifficulty()) > 0) {
			param.setUnitCount(us.getNoPublicAnyDifficulty());
			String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + false + "-"
					+ QuestionDifficulty.ANY.getName();
			param.setKey(key);
			createUnit(param);
		}
	}

	private void createUnitByBlueProp(CreateDefaultPaperParam param) {
		CoursePropertyNumberDto cp = param.getCp();
		if (cp.getNoPublicDifficulty() > 0) {
			String key = param.getDetailNumber() + "-" + bulidPropertyGroupByBlueStruct(cp.getPropertyParentId(),
					cp.getPropertyId(), false, QuestionDifficulty.HARD.getName());
			param.setKey(key);
			param.setUnitCount(cp.getNoPublicDifficulty());
			createUnit(param);
		}
		if (cp.getPublicDifficulty() > 0) {
			String key = param.getDetailNumber() + "-" + bulidPropertyGroupByBlueStruct(cp.getPropertyParentId(),
					cp.getPropertyId(), true, QuestionDifficulty.HARD.getName());
			param.setKey(key);
			param.setUnitCount(cp.getPublicDifficulty());
			createUnit(param);
		}

		if (cp.getNoPublicMedium() > 0) {
			String key = param.getDetailNumber() + "-" + bulidPropertyGroupByBlueStruct(cp.getPropertyParentId(),
					cp.getPropertyId(), false, QuestionDifficulty.MEDIUM.getName());
			param.setKey(key);
			param.setUnitCount(cp.getNoPublicMedium());
			createUnit(param);
		}
		if (cp.getPublicMedium() > 0) {
			String key = param.getDetailNumber() + "-" + bulidPropertyGroupByBlueStruct(cp.getPropertyParentId(),
					cp.getPropertyId(), true, QuestionDifficulty.MEDIUM.getName());
			param.setKey(key);
			param.setUnitCount(cp.getPublicMedium());
			createUnit(param);
		}
		if (cp.getNoPublicSimple() > 0) {
			String key = param.getDetailNumber() + "-" + bulidPropertyGroupByBlueStruct(cp.getPropertyParentId(),
					cp.getPropertyId(), false, QuestionDifficulty.EASY.getName());
			param.setKey(key);
			param.setUnitCount(cp.getNoPublicSimple());
			createUnit(param);
		}
		if (cp.getPublicSimple() > 0) {
			String key = param.getDetailNumber() + "-" + bulidPropertyGroupByBlueStruct(cp.getPropertyParentId(),
					cp.getPropertyId(), true, QuestionDifficulty.EASY.getName());
			param.setKey(key);
			param.setUnitCount(cp.getPublicSimple());
			createUnit(param);
		}
	}

	private void createUnit(CreateDefaultPaperParam param) {
		List<RandomPaperQuestion> rpqs = param.getRp().getQuestionMap().get(param.getKey());
		Collections.shuffle(rpqs);
		for (int i = 0; i < param.getUnitCount(); i++) {
			RandomPaperQuestion rpq = rpqs.get(i);
			DefaultQuestionStructureWrapper qw = new DefaultQuestionStructureWrapper();
			param.getUnits().add(qw);
			qw.setLimitedPlayTimes(param.getPlayTime());
			qw.setPlayedTimes(0);
			qw.setQuestionId(rpq.getQuestionId());
			qw.setQuestionScore(rpq.getScore());
			List<DefaultQuestionUnitWrapper> qList = new ArrayList<>();
			qw.setQuestionUnitWrapperList(qList);
			if (QuesStructType.NESTED_ANSWER_QUESTION.equals(rpq.getQuestionType())) {
				for (RandomPaperQuestion sub : rpq.getSubQuestions()) {
					DefaultQuestionUnitWrapper q = new DefaultQuestionUnitWrapper();
					qList.add(q);
					q.setAnswerType(sub.getAnswerType());
					q.setOptionPermutation(getOption(sub.getOptionCount()));
					q.setQuestionScore(sub.getScore());
					q.setQuestionType(getByOldType(sub.getQuestionType()));
					if (!PaperUtil.isObjecttive(sub.getQuestionType())) {
						param.setFullyObjective(false);
					}
				}
			} else {
				DefaultQuestionUnitWrapper q = new DefaultQuestionUnitWrapper();
				qList.add(q);
				q.setAnswerType(rpq.getAnswerType());
				q.setOptionPermutation(getOption(rpq.getOptionCount()));
				q.setQuestionScore(rpq.getScore());
				q.setQuestionType(getByOldType(rpq.getQuestionType()));
				if (!PaperUtil.isObjecttive(rpq.getQuestionType())) {
					param.setFullyObjective(false);
				}
			}
		}
	}

	private QuestionType getByOldType(QuesStructType quesStructType) {
		if (quesStructType == QuesStructType.BOOL_ANSWER_QUESTION) {
			return QuestionType.TRUE_OR_FALSE;
		}
		if (quesStructType == QuesStructType.FILL_BLANK_QUESTION) {
			return QuestionType.FILL_UP;
		}
		if (quesStructType == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
			return QuestionType.MULTIPLE_CHOICE;
		}
		if (quesStructType == QuesStructType.SINGLE_ANSWER_QUESTION) {
			return QuestionType.SINGLE_CHOICE;
		}
		if (quesStructType == QuesStructType.TEXT_ANSWER_QUESTION) {
			return QuestionType.ESSAY;
		}
		return null;
	}

	private Integer[] getOption(Integer count) {
		if (count == null) {
			return null;
		}
		Integer[] ret = new Integer[count];
		for (int i = 0; i < count; i++) {
			ret[i] = i;
		}
		return ret;
	}

	private RandomPaperCache getRandomPaperTemplateCacheById(String randomPaperId) {
		// 抽卷模板 优先从本地缓存中获取
		String key = CacheConstants.CACHE_Q_RANDOM_PAPER + randomPaperId;
		RandomPaperCache rp = localRandomPaperCache.getIfPresent(key);
		if (rp != null) {
			log.warn("从【本地缓存】中获取抽卷模板！ key:{}", key);
			return rp;
		}

		// 从redis缓存中获取
		rp = redisClient.get(key, RandomPaperCache.class, cacheTimeOut);
		if (rp != null) {
			localRandomPaperCache.put(key, rp);
			log.warn("从【Redis缓存】中获取抽卷模板！ key:{}", key);
			return rp;
		}

		// 从数据库中获取
		RandomPaper entity = Model.of(randomPaperRepo.findById(randomPaperId));
		if (entity == null) {
			throw new StatusException("未找到随机模板:" + randomPaperId);
		}

		List<RandomPaperQuestion> rpQuestions = randomPaperQuestionRepo.findByRandomPaperId(randomPaperId);
		if (CollectionUtils.isEmpty(rpQuestions)) {
			throw new StatusException("随机模板试题库为空:" + randomPaperId);
		}

		log.warn("从【数据库】中获取抽卷模板！ key:{}", key);
		rp = new RandomPaperCache();
		rp.setName(entity.getName());
		rp.setPaperStructId(entity.getPaperStructId());

		Map<String, List<RandomPaperQuestion>> map = new HashMap<>();
		for (RandomPaperQuestion rpq : rpQuestions) {
			List<RandomPaperQuestion> list = map.get(rpq.getKey());
			if (list == null) {
				list = new ArrayList<>();
				map.put(rpq.getKey(), list);
			}
			list.add(rpq);
		}
		rp.setQuestionMap(map);

		redisClient.set(key, rp, cacheTimeOut);
		localRandomPaperCache.put(key, rp);
		return rp;
	}

	@Override
	public boolean existStruct(String paperStructId) {
		Query query = new Query();
		query.addCriteria(Criteria.where("paperStructId").is(paperStructId));
		RandomPaper rp = mongoTemplate.findOne(query, RandomPaper.class);
		return rp != null;
	}

	@Override
	public boolean existPaper(Long courseId, String paperId) {
		Query query = new Query();
		query.addCriteria(Criteria.where("courseId").is(courseId));
		List<RandomPaper> rps = mongoTemplate.find(query, RandomPaper.class);
		if (CollectionUtils.isEmpty(rps)) {
			return false;
		}
		for (RandomPaper rp : rps) {
			if (rp.getPaperIds().contains(paperId)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public List<RandomPaperListVo> getList(Long rootOrgId, Long courseId) {
		List<Criteria> cs = new ArrayList<>();
		cs.add(Criteria.where("rootOrgId").is(rootOrgId));

		cs.add(Criteria.where("enable").is(true));

		cs.add(Criteria.where("courseId").is(courseId));
		Criteria and = new Criteria();
		Criteria[] cas = new Criteria[cs.size()];
		and.andOperator(cs.toArray(cas));
		Query query = Query.query(and);

		List<RandomPaperListVo> paperList = this.mongoTemplate.find(query, RandomPaperListVo.class, "randomPaper");

		return paperList;
	}
}