package com.ruoyi.web.service;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.ruoyi.common.core.content.VistorContextHolder;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.ExamType;
import com.ruoyi.common.enums.SubjectType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.dz.SubjectScore;
import com.ruoyi.dz.domain.DzControl;
import com.ruoyi.dz.service.IDzControlService;
import com.ruoyi.ie.domain.*;
import com.ruoyi.ie.mapper.*;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.syzy.domain.BBusiWishUniversities;
import com.ruoyi.syzy.domain.BBusiWishUniversitiesProfession;
import com.ruoyi.syzy.dto.UniversityDetailDTO;
import com.ruoyi.syzy.mapper.BBusiWishRecordsMapper;
import com.ruoyi.syzy.mapper.BBusiWishUniversitiesMapper;
import com.ruoyi.syzy.mapper.BBusiWishUniversitiesProfessionMapper;
import com.ruoyi.util.PageUtil;
import com.ruoyi.web.domain.Constant;
import com.ruoyi.web.domain.VoluntaryDto;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiParam;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestParam;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
public class VoluntaryService {
    private ObjectMapper mapper = new ObjectMapper();
    private static TypeReference<List<VoluntaryDto.SingleResponse>> wishDetailsTypeReference = new TypeReference<List<VoluntaryDto.SingleResponse>>() { };
    private static TypeReference<List<VoluntaryDto.AIResponse>> wishAiDetailsTypeReference = new TypeReference<List<VoluntaryDto.AIResponse>>() { };
    Set<String> NumberTypeSet = Sets.newHashSet(VoluntaryDto.EnumInputType.Number.name(), VoluntaryDto.EnumInputType.Eyesight.name(), VoluntaryDto.EnumInputType.Score.name());
    private static VoluntaryRuleHelper voluntaryRuleHelper = new VoluntaryRuleHelper();
    private final IDzControlService dzControlService;
    private final AWishRecordMapper aWishRecordMapper;
    private final AEnrollScoreMapper aEnrollScoreMapper;
    private final AEnrollSpecialMapper aEnrollSpecialMapper;
    private final AMarjorPlanMapper aMarjorPlanMapper;
    private final AMarjorSubmitMapper aMarjorSubmitMapper;
    private final AEnrollUniversityMapper aEnrollUniversityMapper;
    private final BBusiWishUniversitiesMapper bBusiWishUniversitiesMapper;
    private final BBusiWishUniversitiesProfessionMapper bBusiWishUniversitiesProfessionMapper;

    private final ISysConfigService sysConfigService;
    private final EnrollRateCalculator enrollRateCalculator;
    private final BBusiWishRecordsMapper busiWishRecordsMapper;

    public VoluntaryService(IDzControlService dzControlService, AWishRecordMapper aWishRecordMapper, AEnrollScoreMapper aEnrollScoreMapper, AEnrollSpecialMapper aEnrollSpecialMapper, AMarjorPlanMapper aMarjorPlanMapper, AMarjorSubmitMapper aMarjorSubmitMapper, AEnrollUniversityMapper aEnrollUniversityMapper, BBusiWishUniversitiesMapper bBusiWishUniversitiesMapper, BBusiWishUniversitiesProfessionMapper bBusiWishUniversitiesProfessionMapper, ISysConfigService sysConfigService, EnrollRateCalculator enrollRateCalculator, BBusiWishRecordsMapper busiWishRecordsMapper) {
        this.dzControlService = dzControlService;
        this.aWishRecordMapper = aWishRecordMapper;
        this.aEnrollScoreMapper = aEnrollScoreMapper;
        this.aEnrollSpecialMapper = aEnrollSpecialMapper;
        this.aMarjorPlanMapper = aMarjorPlanMapper;
        this.aMarjorSubmitMapper = aMarjorSubmitMapper;
        this.aEnrollUniversityMapper = aEnrollUniversityMapper;
        this.bBusiWishUniversitiesMapper = bBusiWishUniversitiesMapper;
        this.bBusiWishUniversitiesProfessionMapper = bBusiWishUniversitiesProfessionMapper;
        this.sysConfigService = sysConfigService;
        this.enrollRateCalculator = enrollRateCalculator;
        this.busiWishRecordsMapper = busiWishRecordsMapper;
        // mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    public Pair<List<UniversityDetailDTO.WishPlan>, List<UniversityDetailDTO.WishSubmit>> getUniversityHistory(Long universityId, String province, String examineType) {
        AMarjorPlan planCond = new AMarjorPlan();
        planCond.setUniversityId(universityId);
        planCond.setExamineeType(examineType);
        List<UniversityDetailDTO.WishPlan> planList = aMarjorPlanMapper.selectAMarjorPlanList(planCond).stream().map(t -> {
            UniversityDetailDTO.WishPlan p = new UniversityDetailDTO.WishPlan();
            p.setId(t.getId());
            p.setCollegeCode(t.getEnrollCode());
            p.setYear(String.valueOf(t.getYear()));
            p.setLevel(t.getLevel());
            p.setType("");
            p.setMarjorCode(t.getMajorCode());
            p.setMarjorName(t.getMajorName());
            p.setMarjorBelongs(t.getMajorEnrollCode());
            p.setMarjorDirection(t.getMajorDirection());
            p.setSpecialProject("");
            p.setPlanCount(t.getPlanTotal());
            p.setXuefei(String.valueOf(t.getXuefei()));
            p.setXuezhi(String.valueOf(t.getLengthOfSchooling()));
            p.setEnrollFormula(t.getEnrollFormula());
            p.setGroupsName(t.getMajorGroup());
            p.setAcceptanceRate(t.getAcceptanceRate());
            p.setEnrollCount(t.getEnrollCount());
            return p;
        }).collect(Collectors.toList());

        AMarjorSubmit submitCond = new AMarjorSubmit();
        submitCond.setUniversityId(universityId);
        submitCond.setEnrollType("初录");
        submitCond.setExamineeType(examineType);
        List<UniversityDetailDTO.WishSubmit> submitList = aMarjorSubmitMapper.selectAMarjorSubmitList(submitCond).stream().map(t -> {
            UniversityDetailDTO.WishSubmit s = new UniversityDetailDTO.WishSubmit();
            s.setId(t.getId());
            s.setYear(String.valueOf(t.getYear()));
            s.setLevel(t.getLevel());
            s.setType("");
            s.setMarjorCode("");
            s.setMarjorName(t.getMajorName());
            s.setMarjorBelongs("");
            s.setMarjorDirection(t.getMajorDirection());
            s.setSpecialProject("");
            s.setScore(null != t.getScore() ? t.getScore().longValue() : null);
            s.setSeat(null);
            s.setNumReal(null != t.getEnrollTotal() ? t.getEnrollTotal().longValue() : null);
            s.setEnrollFormula(t.getEnrollFormula());
            s.setGroupsName(t.getMajorGroup());
            return s;
        }).collect(Collectors.toList());
        return Pair.of(planList, submitList);
    }

    private void setOptionValue(VoluntaryDto.AIRenderRule r, String valueType, String valueRule, String options, Integer correctType, String correctValue, Integer scoreTotal) {
        r.setEnumInputType(VoluntaryDto.EnumInputType.valueOf(valueType));
        if (VoluntaryDto.EnumInputType.Score.equals(r.getEnumInputType())) {
            r.setOptions(new String[]{null == scoreTotal ? "0" : String.valueOf(scoreTotal.intValue())});
        } else if (StringUtils.isNotBlank(options) && (VoluntaryDto.EnumInputType.Checkbox.equals(r.getEnumInputType())
                || VoluntaryDto.EnumInputType.Radio.equals(r.getEnumInputType()) || VoluntaryDto.EnumInputType.Picker.equals(r.getEnumInputType()))) {
            r.setOptions(options.split(","));
        }
        // r.setRequired();
        // 词典选项类选项 // 词典类选项的优先级最高
        // String dictOptions;
        // 非词典选项 // 分制类规则，将多分制在此options中返回
        // String[] options;
    }

    public Integer getPlanYear(SysUser u) {
        DzControl dzControl = dzControlService.selectDzControl(VistorContextHolder.getContext());
        return dzControl.getPlanYear();
    }

    public Integer getPlanYear(String location, ExamType type) {
        DzControl dzControl = dzControlService.selectDzControl(location, type);
        return dzControl.getPlanYear();
    }

    public Integer getSubmitYear() {
        return 2025;
    }

    public R<VoluntaryDto.RenderMajorResult> postRenderResult(VoluntaryDto.SingleRequest req, Boolean isScoreOnly) {
        Integer planYear = getPlanYear(SecurityUtils.getLoginUser().getUser());
        Integer submitYear = getSubmitYear();
        String examType = VistorContextHolder.getExamType().title(); // TODO MF
        String gender = "0".equals(SecurityUtils.getLoginUser().getUser().getSex()) ? "男生" : "女生";
        Map cond = new HashMap();
        cond.put("universityCode", req.getUniversityCode());
        cond.put("examineeType", examType);
        Map<Long, List<AMarjorPlan>> currUniversityMajorPlansMap = Maps.newHashMap();
        Map<Long, List<AMarjorPlan>> historyUniversityMajorPlansMap = Maps.newHashMap();
        List<Map<Long, List<AMarjorPlan>>> bothPlanList = Lists.newArrayList(currUniversityMajorPlansMap, historyUniversityMajorPlansMap);
        Set<String> existSet = Sets.newHashSet();
        Long planId = NumberUtils.toLong(req.getMajorEnrollCode(), 0L);
        for (AMarjorPlan mp : aMarjorPlanMapper.selectListByRuleCond(cond)) {
            String key = mp.getUniversityId() + mp.getMajorName() + StringUtils.trimToEmpty(mp.getMajorDirection()) + mp.getYear(); // TODO 考虑为什么会重的问题 examType ?
            if (!existSet.add(key)) {
                continue;
            }
            for(Map<Long, List<AMarjorPlan>> tmpMap : bothPlanList) {
                if (tmpMap == currUniversityMajorPlansMap) {
                    if(!mp.getId().equals(planId)) {
                        continue;
                    }
                } else if(mp.getYear() > submitYear) {
                    continue;
                }
                List<AMarjorPlan> tmpMpList = tmpMap.get(mp.getUniversityId());
                if (null == tmpMpList) {
                    tmpMpList = Lists.newArrayList(mp);
                    tmpMap.put(mp.getUniversityId(), tmpMpList);
                } else {
                    tmpMpList.add(mp);
                }
            }
        }
        Map<Long, List<BBusiWishUniversitiesProfession>> universityMajorsMap = Maps.newHashMap();
        // 有计划以计划为准的输出，无计划时则以所有专业输出为准
        Map<Long, BBusiWishUniversities> universitiesMap = null;
        if (MapUtils.isEmpty(currUniversityMajorPlansMap)) {
            Map<String, List<BBusiWishUniversitiesProfession>> tmpUniversityMajorsMap = bBusiWishUniversitiesProfessionMapper.selectListByRuleCond(cond).stream().collect(Collectors.groupingBy(BBusiWishUniversitiesProfession::getCollegeCode));
            if (MapUtils.isNotEmpty(tmpUniversityMajorsMap)) {
                Map uCond = Maps.newHashMap();
                uCond.put("codes", tmpUniversityMajorsMap.keySet());
                universitiesMap = Maps.newHashMap();
                for (BBusiWishUniversities u : bBusiWishUniversitiesMapper.selectBBusiWishUniversitiesListSimpleByMap(uCond)) {
                    universitiesMap.put(u.getId(), u);
                    universityMajorsMap.put(u.getId(), tmpUniversityMajorsMap.get(u.getCode()));
                }
            }
        } else {
            cond.put("universityIds", currUniversityMajorPlansMap.keySet());
            cond.put("examType", Constant.EXAM_TYPE_PG);
            Map<String, List<BBusiWishUniversitiesProfession>> tmpUniversityMajorsMap = bBusiWishUniversitiesProfessionMapper.selectListByRuleCond(cond).stream().collect(Collectors.groupingBy(BBusiWishUniversitiesProfession::getCollegeCode));
            cond.remove("universityIds");

            if (MapUtils.isNotEmpty(tmpUniversityMajorsMap)) {
                Map uCond = new HashMap();
                uCond.put("ids", currUniversityMajorPlansMap.keySet());
                universitiesMap = Maps.newHashMap();
                for (BBusiWishUniversities u : bBusiWishUniversitiesMapper.selectBBusiWishUniversitiesListSimpleByIds(uCond)) {
                    universitiesMap.put(u.getId(), u);
                    universityMajorsMap.put(u.getId(), tmpUniversityMajorsMap.get(u.getCode()));
                }
            }
        }
        // 无计划，也无院校专业
        if(MapUtils.isEmpty(universitiesMap)) {
            return R.fail("无计划院校专业");
        }
        // 查询涉及的规则
        cond.put("year", planYear);
        cond.put("universityIds", universitiesMap.keySet());
        Map<Long, List<AEnrollScore>> universityScoreListMap = aEnrollScoreMapper.selectListByRuleCond(cond).stream().collect(Collectors.groupingBy(AEnrollScore::getUniversityId));
        cond.remove("universityIds");
        cond.remove("year");

        Map<String, String> mutexOptionMap = getMutexOptionMap();
        for (Long universityId : universitiesMap.keySet()) {
            List<AEnrollScore> scoreList = universityScoreListMap.get(universityId); // 分数条件
            List<AMarjorPlan> currPlanList = currUniversityMajorPlansMap.get(universityId);// 当年计划
            List<AMarjorPlan> historyPlanList = historyUniversityMajorPlansMap.get(universityId);// 历年计划
            BBusiWishUniversities u = universitiesMap.get(universityId);// 院校
            List<BBusiWishUniversitiesProfession> professionList = universityMajorsMap.get(u.getId()); // 所有专业
            // 院校变更标志
            AEnrollUniversity euCond = new AEnrollUniversity();
            euCond.setUniversityId(u.getId());
            euCond.setYear(planYear);
            List<AEnrollUniversity> enrollUniversityList = aEnrollUniversityMapper.selectAEnrollUniversityList(euCond);
            // 院校历史情况
            AMarjorSubmit submitCond = new AMarjorSubmit();
            submitCond.setExamineeType(examType);
            submitCond.setUniversityId(universityId);
            List<AMarjorSubmit> submitList = aMarjorSubmitMapper.selectAMarjorSubmitList(submitCond);

            VoluntaryDto.RenderMajorResult sr;
            if (CollectionUtils.isNotEmpty(currPlanList)) {
                Map<String, BBusiWishUniversitiesProfession> professionMap = null == professionList ? Maps.newHashMap() : professionList.stream().collect(Collectors.toMap(BBusiWishUniversitiesProfession::getName, Function.identity()));
                for (AMarjorPlan mp : currPlanList) { // 只有一个
                    if (null != (sr = buildRenderResponse(submitYear, planYear, gender, mp, professionMap.get(mp.getMajorName()), enrollUniversityList, req.getForm(), u,
                            historyPlanList, submitList, scoreList, mutexOptionMap, isScoreOnly))) {
                        sr.setUniversityId(universityId);
                        sr.setMajorId(planId);
                        return R.ok(sr);
                    }
                }
            }
        }
        return R.fail("无计划院校专业");
    }

    private VoluntaryDto.RenderMajorResult buildRenderResponse(Integer submitYear, Integer planYear, String gender, AMarjorPlan currPlan, BBusiWishUniversitiesProfession prof,
                                                            List<AEnrollUniversity> enrollUniversityList, Map<String, String> paramMap, BBusiWishUniversities u,
                                                            List<AMarjorPlan> historyPlanList, List<AMarjorSubmit> submitList,
                                                            List<AEnrollScore> scoreList, Map<String,String> mutexOptionMap, boolean isScoreOnly) {
        submitList = voluntaryRuleHelper.sortSubmits(submitList);
        historyPlanList = voluntaryRuleHelper.sortPlans(historyPlanList);

        Boolean typeChange = false;
        VoluntaryDto.FormulaScoreStat formulaScoreStat = new VoluntaryDto.FormulaScoreStat();
        String needGroup = StringUtils.trimToEmpty(null != currPlan && null != currPlan.getMajorGroup() ? currPlan.getMajorGroup() : "");
        String needMajor = StringUtils.trimToEmpty(null != currPlan ? currPlan.getMajorName() : (null != prof ? prof.getName() : ""));
        String needDirect = StringUtils.trimToEmpty(null != currPlan ? currPlan.getMajorDirection() : "");
        String needMajorDirect = needMajor + needDirect;

        // 判断是否政策变化情况
        List<AEnrollUniversity> validEuList = enrollUniversityList.stream()
                .filter(t -> needGroup.equals(StringUtils.trimToEmpty(t.getMajorGroups())) && t.getMajorNames().contains(needMajor))
                .collect(Collectors.toList());
        typeChange = CollectionUtils.isNotEmpty(validEuList) && new Integer(1).equals(validEuList.get(0).getTypeChange());

        Set<String> existSet = Sets.newHashSet();
        List<AMarjorSubmit> validSubmitList = voluntaryRuleHelper.filtValidSubmits(needMajorDirect, submitList, existSet);

        // 录取线
        List<VoluntaryDto.RenderMajorHistory> histories = Lists.newArrayList(); // 历史录取情况
        VoluntaryDto.RenderMajorResult sr = new VoluntaryDto.RenderMajorResult();
        sr.setHistories(histories);

        if (null == scoreList) {
            throw new RuntimeException("无分数规则");
        }
        Map<Boolean, List<AEnrollScore>> majorEnrollScoresMap = voluntaryRuleHelper.buildMajorEnrollScoresMap(scoreList, needGroup);
        existSet.clear();
        List<AEnrollScore> fEnrollScoreList = Lists.newArrayList();
        Double inputScoreRate = NumberUtils.toInt(paramMap.get("ScoreRate"), 0) / 100.0;
        String inclMajorDirection = needMajor + "(" + needDirect + ")";
        String exclMajorDirection = needMajor + "(";
        List<AEnrollScore> tmpEnrollScoreList = majorEnrollScoresMap.get(Boolean.TRUE);
        if (null != tmpEnrollScoreList) {
            for (AEnrollScore r : tmpEnrollScoreList) {
                if(!voluntaryRuleHelper.isMatchMajor(r, needMajor, needMajorDirect, inclMajorDirection, exclMajorDirection)) {
                    continue;
                }
                Boolean isSkillScore = isScoreOnly && r.getItemType().equals("ScoreSkill");
                appendScoreRule(formulaScoreStat, existSet, r, fEnrollScoreList, false, paramMap, inputScoreRate, isSkillScore);
            }
        }
        if (fEnrollScoreList.size() == 0 && null != (tmpEnrollScoreList = majorEnrollScoresMap.get(Boolean.FALSE))) {
            for (AEnrollScore r : tmpEnrollScoreList) {
                Boolean isSkillScore = isScoreOnly && r.getItemType().equals("ScoreSkill");
                appendScoreRule(formulaScoreStat, existSet, r, fEnrollScoreList, false, paramMap, inputScoreRate, isSkillScore);
            }
        }

        Double currTotal = formulaScoreStat.getTypeValue("StatRateScore", "", false);
        existSet.clear();
        Map<String, AMarjorPlan> historyPlanMap = historyPlanList.stream().collect(Collectors.toMap(t -> t.getYear() + t.getMajorName() + StringUtils.trimToEmpty(t.getMajorDirection()), Function.identity()));
        AMarjorSubmit lastSubmit1 = null, lastSubmit2 = null;
        for (AMarjorSubmit s : validSubmitList) {
            if (s.getYear().equals(submitYear)) {
                if(s.getEnrollType().equals("初录")) {
                    lastSubmit1 = s;
                } else {
                    lastSubmit2 = s;
                }
            }
            VoluntaryDto.RenderMajorHistory h = new VoluntaryDto.RenderMajorHistory();
            h.setYear(s.getYear());
            AMarjorPlan p = historyPlanMap.get(s.getYear() + s.getMajorName() + StringUtils.trimToEmpty(s.getMajorDirection()));
            if(null != p) {
                h.setPlan(p.getPlanTotal());
            } else {
                // System.out.println(s.getId());
            }
            h.setEnroll(s.getEnrollTotal());
            if(null != s.getScore()) {
                h.setScore(String.valueOf(s.getScore()));
                h.setDiff((int) Math.round(currTotal - s.getScore()));
            } else {
                h.setScore("");
                h.setDiff(null);
            }
            h.setRuleContent(s.getEnrollFormula());
            String[] rates;
            if(StringUtils.isNotBlank(p.getAcceptanceRate()) && 2 == (rates = p.getAcceptanceRate().split(":")).length) {
                h.setApplication(rates[0]);
                h.setAdmission(rates[1]);
            } else {
                h.setApplication("");
                h.setAdmission("");
            }
            histories.add(h);
        }

        AMarjorSubmit lastSubmit = null == lastSubmit1 ? lastSubmit2 : lastSubmit1;
        Double cultureScore = formulaScoreStat.getTypeValue("StatCategoryScore", "ScoreBase", false);

        VoluntaryDto.RenderMajorSkill skill = new VoluntaryDto.RenderMajorSkill();
        skill.setYear(currPlan.getYear());
        skill.setCultureScore(toString(cultureScore));
        String[] formulas = fEnrollScoreList.get(0).getEnrollFormula().split("\\+职业技能");
        if(formulas.length != 2) {
            throw new RuntimeException("错误定义考试规则" + fEnrollScoreList.get(0).getEnrollFormula());
        }
        skill.setCultureRule(formulas[0]); // 拆分公式
        skill.setEnrollScore(toString(lastSubmit.getScore()));

        // 计算概念或反算技能分
        boolean isSameYear = planYear.equals(submitYear);
        Integer validScoreTotal = isSameYear ? formulaScoreStat.getAllTotal() : lastSubmit.getScoreTotal();
        Double currScoreRate = isSameYear ? 1.0 : formulaScoreStat.getAllTotal() * 1.0 / lastSubmit.getScoreTotal();
        Double needSkillScore = lastSubmit.getScore() * currScoreRate - cultureScore;
        if(isScoreOnly) {
            skill.setSkillScore(toString(formulaScoreStat.getImproveSkillScore(needSkillScore)));
        } else {
            Double skillScore = formulaScoreStat.getTypeValue("StatCategoryScore", "ScoreSkill", true);
            skill.setSkillScore(toString(needSkillScore));
            skill.setDiff(toString(formulaScoreStat.getImproveSkillScore(skillScore - needSkillScore)));
            EnrollRateCalculator.RateLevel rl;
            if (currTotal != null && null != (rl = enrollRateCalculator.calSchoolEnrollRate(validScoreTotal, lastSubmit.getScore(), currTotal / currScoreRate))) { // 按去年标准算概率
                sr.setEnrollRate(rl.rate);
                sr.setEnrollRateText(rl.typeLabel);
                sr.setEnumPickType(rl.type);
            }
        }
        sr.setSkill(skill);
        return sr;
    }

    public String toString(Double v) {
        return String.valueOf(v);
    }

    public R<List<VoluntaryDto.RenderRule>> getRenderRules(VoluntaryDto.RenderRequest req) {
        Map cond = new HashMap();
        Integer planYear = getPlanYear(SecurityUtils.getLoginUser().getUser());
        cond.put("year", planYear);
        AMarjorPlan plan = aMarjorPlanMapper.selectAMarjorPlanById(req.getMajorId());
        cond.put("majorGroup", StringUtils.trimToEmpty(plan.getMajorGroup()));
        cond.put("majorGroupCodes", plan.getMajorCode());
        cond.put("universityCode", req.getUniversityId());
        return R.ok(findMatchRenderRules(cond, req.getRenderType() == 2));
    }

    public R<List<VoluntaryDto.AIRenderRule>> getAIRenderRules(VoluntaryDto.AIRenderRequest req) {
        Map cond = new HashMap();
        Integer planYear = getPlanYear(SecurityUtils.getLoginUser().getUser());
        cond.put("year", planYear);
        Set<String> majorCodeSet = null;
        String majorGroup = null;
        if (ArrayUtils.isNotEmpty(req.getMajorEnrollCodes()) && StringUtils.isNotBlank(req.getMajorEnrollCodes()[0])) {
            List<Long> majorEnrollCodes = Arrays.asList(req.getMajorEnrollCodes()).stream().map(t -> NumberUtils.toLong(t, 0L)).collect(Collectors.toList());
            for(AMarjorPlan plan : aMarjorPlanMapper.selectMajorCodesByIds(majorEnrollCodes.toArray(new Long[majorEnrollCodes.size()]), planYear)) {
                majorGroup = StringUtils.trimToEmpty(plan.getMajorGroup());
                majorCodeSet = Sets.newHashSet(plan.getMajorCode());
            }
        }
        if(CollectionUtils.isNotEmpty(majorCodeSet)) {
            cond.put("majorGroup", majorGroup);
            cond.put("majorGroupCodes", majorCodeSet.toArray(new String[majorCodeSet.size()]));
        } else if (ArrayUtils.isNotEmpty(req.getMajorCodes()) && StringUtils.isNotBlank(req.getMajorCodes()[0])) {
            cond.put("majorCodes", req.getMajorCodes());
        } else if (ArrayUtils.isNotEmpty(req.getMajorTypes()) && StringUtils.isNotBlank(req.getMajorTypes()[0])) {
            cond.put("majorTypes", req.getMajorTypes());
        } else if (StringUtils.isNotBlank(req.getMajorCategory())) {
            cond.put("majorCategory", req.getMajorCategory());
        }
        cond.put("universityCode", req.getUniversityCode());
        return R.ok(findMatchRules(cond, req.getRenderType() == 1, req.getRenderType() == 2));
    }

    public R<VoluntaryDto.SingleResponse> postSingleResult(VoluntaryDto.SingleRequest req, Boolean isScoreOnly) {
        VoluntaryDto.MultipleRequest mr = new VoluntaryDto.MultipleRequest();
        VoluntaryDto.CollegeMajorDto collegeMajor = new VoluntaryDto.CollegeMajorDto();
        collegeMajor.setCode(req.getUniversityCode());
        if (StringUtils.isNotBlank(req.getMajorCode())) {
            collegeMajor.setMajorCodes(Lists.newArrayList(req.getMajorCode()));
        }
        if (StringUtils.isNotBlank(req.getMajorEnrollCode())) {
            collegeMajor.setMajorEnrollCodes(Lists.newArrayList(req.getMajorEnrollCode()));
        }
        collegeMajor.setForm(req.getForm());

        mr.setUniversities(Lists.newArrayList(collegeMajor));
        List<VoluntaryDto.SingleResponse> respList = postMultipleResult(mr, isScoreOnly).getData();
        if (respList.size() > 0) {
            Optional<VoluntaryDto.SingleResponse> optionalSingleResponse = respList.stream().filter(t -> StringUtils.isBlank(t.getMajorDirection())).findFirst();
            if (optionalSingleResponse.isPresent()) {
                return R.ok(optionalSingleResponse.get());
            }
        }
        return respList.size() > 0 ? R.ok(respList.get(0)) : null;
    }

    public R<List<VoluntaryDto.SingleResponse>> postMultipleResult(VoluntaryDto.MultipleRequest req, boolean isScoreOnly) {
        List<VoluntaryDto.SingleResponse> respList = Lists.newArrayList();
        for (VoluntaryDto.CollegeMajorDto cm : req.getUniversities()) {
            Map cond = new HashMap();
            if (CollectionUtils.isNotEmpty(cm.getMajorEnrollCodes()) && StringUtils.isNotBlank(cm.getMajorEnrollCodes().get(0))) {
                cond.put("majorEnrollCodes", cm.getMajorEnrollCodes()); // TODO 代替majorCodes更精准，只是现在没有，使用planId代替
            } else if (CollectionUtils.isNotEmpty(cm.getMajorCodes()) && StringUtils.isNotBlank(cm.getMajorCodes().get(0))) {
                cond.put("majorCodes", cm.getMajorCodes());
            } else if (StringUtils.isNotBlank(req.getMajorCategory())) {
                cond.put("majorCategory", req.getMajorCategory());
            }
            cond.put("universityCode", StringUtils.trimToEmpty(cm.getCode()));
            List<VoluntaryDto.AIResponse> responses = findMajorSuggest(getPlanYear(SecurityUtils.getLoginUser().getUser()), getSubmitYear(), cond, cm.getForm(), false, isScoreOnly);
            if(CollectionUtils.isNotEmpty(responses)) {
                responses.stream().forEach(t -> respList.addAll(t.getMajorDetails()));
            }
        }
        return R.ok(respList);
    }

    public TableDataInfo postAIResult(VoluntaryDto.AIRequest req) {
        VoluntaryDto.AIRequestFilter filter = req.getFilter();
        Map cond = new HashMap();
        if (CollectionUtils.isNotEmpty(filter.getMajorTypes()) && StringUtils.isNotBlank(filter.getMajorTypes().get(0))) {
            cond.put("majorTypes", filter.getMajorTypes());
        } else if (StringUtils.isNotBlank(req.getMajorCategory())) {
            cond.put("majorCategory", req.getMajorCategory());
        }
        cond.put("universityName", filter.getKeyword());// 院校名称
        if (CollectionUtils.isNotEmpty(filter.getLevel())) {
            cond.put("universityLevel", filter.getLevel());// 办学层次
        }
        if (CollectionUtils.isNotEmpty(filter.getType())) {
            cond.put("universityType", filter.getType());// 院校类型
        }
        if (CollectionUtils.isNotEmpty(filter.getNatureTypeCN())) {
            cond.put("universityNatureType", filter.getNatureTypeCN()); // 办学类型
        }
        if (CollectionUtils.isNotEmpty(filter.getLocation())) {
            cond.put("universityLocation", filter.getLocation()); // 院校省份
        }
        if (null != filter.getHasClearing()) {
            cond.put("hasClearing", filter.getHasClearing() ? "补录" : "初录");
        }
        List<VoluntaryDto.AIResponse> responses = findMajorSuggest(getPlanYear(SecurityUtils.getLoginUser().getUser()), getSubmitYear(), cond, req.getForm(), true, false);
        if (CollectionUtils.isEmpty(responses)) {
            return new TableDataInfo(Lists.newArrayList(), 0);
        }

        if (null != filter.getEnumPickEmpty()) {
            responses = responses.stream().filter(t -> filter.getEnumPickEmpty().equals(t.getEnumPickEmpty())).collect(Collectors.toList());
        } else if (null != filter.getEnumPickType() && !VoluntaryDto.EnumPickType.All.equals(filter.getEnumPickType())) {
            responses = responses.stream().filter(t -> filter.getEnumPickType().equals(t.getEnumPickType())).collect(Collectors.toList());
        }
        Integer start = null != req.getPageNum() ? (req.getPageNum() - 1) * req.getPageSize() : 0;
        List<VoluntaryDto.AIResponse> finalList = responses.subList(start, Math.min(start + (null == req.getPageSize() ? 10 : req.getPageSize()), responses.size()));
        TableDataInfo tableDataInfo = new TableDataInfo(finalList, responses.size());
        tableDataInfo.setCode(200);
        return tableDataInfo;
    }

    private List<VoluntaryDto.RenderRule> findMatchRenderRules(Map cond, boolean isScore) {
        String examType = VistorContextHolder.getExamType().title(); // TODO MF
        // String gender = "0".equals(SecurityUtils.getLoginUser().getUser().getSex()) ? "男生" : "女生";
        cond.put("examineeType", examType);

        List<VoluntaryDto.AIRenderRule> cultureRuleList = Lists.newArrayList();
        List<VoluntaryDto.AIRenderRule> skillRuleList = Lists.newArrayList();

        List<AEnrollScore> enrollScoreList = aEnrollScoreMapper.selectListByRuleCond(cond);
        if (CollectionUtils.isEmpty(enrollScoreList) && cond.containsKey("majorGroup")) {
            cond.put("majorGroup", "");
            enrollScoreList = aEnrollScoreMapper.selectListByRuleCond(cond);
        }
        Set<String> existItemSet  = Sets.newHashSet();
        String vt;
        SysUser sysUser = SecurityUtils.getLoginUser().getUser();;
        boolean isOHS = ExamType.OHS.equals(sysUser.getExamType());
        SubjectScore subjectScore = sysUser.getScores();
        for (AEnrollScore s : enrollScoreList) { // 跳过统计类和提示类的规则
            boolean isSkill = "职业技能测试".equals(s.getItemCategory());
            if (null == (vt = s.getValueType()) || vt.startsWith("Stat") || vt.equals("Notice")) {
                continue;
            }
            List<VoluntaryDto.AIRenderRule> ruleList = isSkill ? skillRuleList : cultureRuleList ;
            if (!existItemSet.add(s.getItemField())) {
                continue;
            }
            VoluntaryDto.AIRenderRule r = new VoluntaryDto.AIRenderRule();
            r.setEnumRuleCategory(VoluntaryDto.EnumRuleCategory.Enroll);
            if (!isSkill && isOHS && !isScore && "Score".equals(s.getValueType())) {
                r.setReadonly(true);
                Double score = subjectScore.getSelectScore(s.getItemField());
                if (null == score && SubjectType.Foreign.name().equals(s.getItemField())) {
                    score = subjectScore.getSelectScore(SubjectType.English.name());
                }
                r.setDefaultValue(null != score ? String.valueOf(score) : "0");
            } else {
                r.setReadonly(false);
                r.setDefaultValue(s.getDefaultValue());
            }
            r.setFieldName(s.getItemField());
            setOptionValue(r, vt, s.getValueRule(), s.getValueOptional(), s.getCorrectType(), s.getCorrectValue(), s.getScoreTotal());
            r.setRegex(s.getRegex());
            r.setLabel(s.getItemName());
            r.setDescription(s.getDescription());
            r.setPlaceholder(s.getPlaceholder());
            r.setTips(s.getTips());
            r.setDotDisable(null != s.getDotDisable() && s.getDotDisable() > 0);
            r.setKeyboardMode(s.getKeyboardMode()); // number card car，默认number
            ruleList.add(r);
        }
        if(CollectionUtils.isEmpty(enrollScoreList)) {
            throw new RuntimeException("未定义考试规则" + StringUtils.join(cond.values(), ","));
        }
        String[] formulas = enrollScoreList.get(0).getEnrollFormula().split("\\+职业技能");
        if(formulas.length != 2) {
            throw new RuntimeException("错误定义考试规则" + enrollScoreList.get(0).getEnrollFormula());
        }
        List<VoluntaryDto.RenderRule> allRenderRuleList = Lists.newArrayList();
        allRenderRuleList.add(new VoluntaryDto.RenderRule("文化素质", formulas[0], cultureRuleList));
        allRenderRuleList.add(new VoluntaryDto.RenderRule("职业技能", "职业技能" + formulas[1], isScore ? null : skillRuleList));
        return allRenderRuleList;
    }

    private List<VoluntaryDto.AIRenderRule> findMatchRules(Map cond, boolean isAi, boolean isScore) {
        String examType = VistorContextHolder.getExamType().title(); // TODO MF
        String gender = "0".equals(SecurityUtils.getLoginUser().getUser().getSex()) ? "男生" : "女生";
        cond.put("examineeType", examType);

        List<AEnrollScore> enrollScoreList = aEnrollScoreMapper.selectListByRuleCond(cond);
        if (CollectionUtils.isEmpty(enrollScoreList) && cond.containsKey("majorGroup")) {
            cond.put("majorGroup", "");
            enrollScoreList = aEnrollScoreMapper.selectListByRuleCond(cond);
        }
        List<VoluntaryDto.AIRenderRule> ruleList = Lists.newArrayList();
        Set<String> existItemSet  = Sets.newHashSet();
        String vt;
        VoluntaryDto.AIRenderRule scoreRateRule = null;
        for (AEnrollScore s : enrollScoreList) { // 跳过统计类和提示类的规则
            if (null == (vt = s.getValueType()) || vt.startsWith("Stat") || vt.equals("Notice")) {
                continue;
            } else if(isScore && !"学考".equals(s.getItemCategory()) && !"校考".equals(s.getItemCategory())) {
                continue;
            } else if (vt.equals("ScoreRate")) {
                if (!isAi) {
                    vt = "Score";
                } else if (existItemSet.add("ScoreRate")) {
                    scoreRateRule = new VoluntaryDto.AIRenderRule();
                    scoreRateRule.setEnumRuleCategory(VoluntaryDto.EnumRuleCategory.Enroll);
                    scoreRateRule.setFieldName("ScoreRate");
                    scoreRateRule.setEnumInputType(VoluntaryDto.EnumInputType.Number);
                    scoreRateRule.setMin(0);
                    scoreRateRule.setMax(100);
                    scoreRateRule.setLabel("技能测试得分率");
                    scoreRateRule.setDescription("%");
                    continue;
                } else {
                    continue;
                }
            }
            if (!existItemSet.add(s.getItemField())) {
                continue;
            }
            VoluntaryDto.AIRenderRule r = new VoluntaryDto.AIRenderRule();
            r.setEnumRuleCategory(VoluntaryDto.EnumRuleCategory.Enroll);
            r.setFieldName(s.getItemField());
            setOptionValue(r, vt, s.getValueRule(), s.getValueOptional(), s.getCorrectType(), s.getCorrectValue(), s.getScoreTotal());
            r.setRegex(s.getRegex());
            r.setLabel(s.getItemName());
            r.setDescription(s.getDescription());
            r.setPlaceholder(s.getPlaceholder());
            r.setTips(s.getTips());
            r.setDefaultValue(s.getDefaultValue());
            r.setDotDisable(null != s.getDotDisable() && s.getDotDisable() > 0);
            r.setKeyboardMode(s.getKeyboardMode()); // number card car，默认number
            ruleList.add(r);
        }
        if (null != scoreRateRule) {
            ruleList.add(scoreRateRule);
        }
        if(isScore) {
            return ruleList;
        }
        List<AEnrollSpecial> enrollSpecialList = Lists.newArrayList();
        Map<String, AEnrollSpecial> mergeEnrollSpecialMap = Maps.newHashMap();
        List<AEnrollSpecial> oriErollSpecialList = aEnrollSpecialMapper.selectListByRuleCond(cond);
        for (AEnrollSpecial s : oriErollSpecialList) {
            if (VoluntaryDto.EnumRuleType.Readonly.name().equals(s.getItemType()) || StringUtils.isNotBlank(s.getGender()) && !s.getGender().equals(gender)) {
                continue;
            }
            if (null == s.getItemGroup() || !"Checkbox".equals(s.getValueType())) {
                enrollSpecialList.add(s);
                continue;
            }
            //
            String key = s.getItemGroup(); // s.getUniversityId() + "_" + s.getMajorName() + "_" + StringUtils.trimToEmpty(s.getMajorDirection()) + "_" + StringUtils.trimToEmpty(s.getGender()) + "_" + s.getItemCategory() + "_" +
            AEnrollSpecial exist = mergeEnrollSpecialMap.get(key);
            if (null == exist) {
                // 修正分组后的名称及字段名
                String[] groupNameField = StringUtils.split(s.getItemGroup(), "_");
                s.setValueOptional(s.getItemName());
                s.setItemName(groupNameField[0]);
                s.setItemField(groupNameField[groupNameField.length > 1 ? 1 : 0]);
                mergeEnrollSpecialMap.put(key, s);
                enrollSpecialList.add(s);
                continue;
            }
            if(StringUtils.isBlank(exist.getValueOptional()) || !exist.getValueOptional().contains(s.getItemName())) {
                exist.setValueOptional(exist.getValueOptional() + "," + s.getItemName());
            }
        }
        Map<String,String> mutexOptionMap = getMutexOptionMap();
        for (AEnrollSpecial s : enrollSpecialList) {
            if (null == (vt = s.getValueType()) || vt.startsWith("Stat") || vt.equals("Notice") || !existItemSet.add(s.getItemField())) {
                continue;
            }
            VoluntaryDto.AIRenderRule r = new VoluntaryDto.AIRenderRule();
            r.setEnumRuleCategory(VoluntaryDto.EnumRuleCategory.Special);
            r.setFieldName(s.getItemField());
            setOptionValue(r, s.getValueType(), s.getValueRule(), s.getValueOptional(), s.getCorrectType(), s.getCorrectValue(), null);
            if (VoluntaryDto.EnumInputType.Checkbox.equals(r.getEnumInputType())) {
                r.setMutexOption(mutexOptionMap.get(r.getFieldName()));
            }
            r.setRegex(s.getRegex());
            r.setLabel(s.getItemName());
            r.setDescription(s.getDescription());
            r.setPlaceholder(s.getPlaceholder());
            r.setTips(s.getTips());
            r.setDefaultValue(s.getDefaultValue());
            r.setDotDisable(null != s.getDotDisable() && s.getDotDisable() > 0);
            r.setKeyboardMode(s.getKeyboardMode()); // number card car，默认number
            ruleList.add(r);
        }
        return ruleList;
    }

    private Map<String, String> getMutexOptionMap() {
        String mutexOptionConf = sysConfigService.selectConfigByKey("voluntary.rule.mutexOption");
        Map<String, String> mutexOptionMap = Maps.newHashMap();
        if (StringUtils.isNotBlank(mutexOptionConf)) {
            try {
                JSONArray.parseArray(mutexOptionConf).forEach(l -> {
                    JSONObject lo = (JSONObject) l;
                    mutexOptionMap.put(lo.getString("n"), lo.getString("m"));
                });
            } catch (JSONException e) {

            }
        }
        return mutexOptionMap;
    }

    // TODO 同一专业号有多个专业方向
    private List<VoluntaryDto.AIResponse> findMajorSuggest(Integer planYear, Integer submitYear, Map cond, Map<String, String> paramMap, boolean isAi, boolean isScoreOnly) {
        String examType = VistorContextHolder.getExamType().title(); // TODO MF
        String gender = "0".equals(SecurityUtils.getLoginUser().getUser().getSex()) ? "男生" : "女生";
        cond.remove("year");
        cond.put("examineeType", examType);
        Map<Long, List<AMarjorPlan>> currUniversityMajorPlansMap = Maps.newHashMap();
        Map<Long, List<AMarjorPlan>> historyUniversityMajorPlansMap = Maps.newHashMap();
        List<Map<Long, List<AMarjorPlan>>> bothPlanList = Lists.newArrayList(currUniversityMajorPlansMap, historyUniversityMajorPlansMap);
        Set<String> existSet = Sets.newHashSet();
        for (AMarjorPlan mp : aMarjorPlanMapper.selectListByRuleCond(cond)) {
            String key = mp.getUniversityId() + mp.getMajorName() + StringUtils.trimToEmpty(mp.getMajorDirection()) + mp.getYear(); // TODO 考虑为什么会重的问题 examType ?
            if (!existSet.add(key)) {
                continue;
            }
            for(Map<Long, List<AMarjorPlan>> tmpMap : bothPlanList) {
                if (!mp.getMatchYear().equals(planYear) && tmpMap == currUniversityMajorPlansMap) {
                    continue;
                }
                List<AMarjorPlan> tmpMpList = tmpMap.get(mp.getUniversityId());
                if (null == tmpMpList) {
                    tmpMpList = Lists.newArrayList(mp);
                    tmpMap.put(mp.getUniversityId(), tmpMpList);
                } else {
                    tmpMpList.add(mp);
                }
            }
        }
        Map<Long, List<BBusiWishUniversitiesProfession>> universityMajorsMap = Maps.newHashMap();
        // 有计划以计划为准的输出，无计划时则以所有专业输出为准
        Map<Long, BBusiWishUniversities> universitiesMap = null;
        if (MapUtils.isEmpty(currUniversityMajorPlansMap)) {
            Map<String, List<BBusiWishUniversitiesProfession>> tmpUniversityMajorsMap = bBusiWishUniversitiesProfessionMapper.selectListByRuleCond(cond).stream().collect(Collectors.groupingBy(BBusiWishUniversitiesProfession::getCollegeCode));
            if (MapUtils.isNotEmpty(tmpUniversityMajorsMap)) {
                Map uCond = Maps.newHashMap();
                uCond.put("codes", tmpUniversityMajorsMap.keySet());
                universitiesMap = Maps.newHashMap();
                for (BBusiWishUniversities u : bBusiWishUniversitiesMapper.selectBBusiWishUniversitiesListSimpleByMap(uCond)) {
                    universitiesMap.put(u.getId(), u);
                    universityMajorsMap.put(u.getId(), tmpUniversityMajorsMap.get(u.getCode()));
                }
            }
        } else {
            cond.put("universityIds", currUniversityMajorPlansMap.keySet());
            cond.put("examType", Constant.EXAM_TYPE_PG);
            Map<String, List<BBusiWishUniversitiesProfession>> tmpUniversityMajorsMap = bBusiWishUniversitiesProfessionMapper.selectListByRuleCond(cond).stream().collect(Collectors.groupingBy(BBusiWishUniversitiesProfession::getCollegeCode));
            cond.remove("universityIds");

            if (MapUtils.isNotEmpty(tmpUniversityMajorsMap)) {
                Map uCond = new HashMap();
                uCond.put("ids", currUniversityMajorPlansMap.keySet());
                universitiesMap = Maps.newHashMap();
                for (BBusiWishUniversities u : bBusiWishUniversitiesMapper.selectBBusiWishUniversitiesListSimpleByIds(uCond)) {
                    universitiesMap.put(u.getId(), u);
                    universityMajorsMap.put(u.getId(), tmpUniversityMajorsMap.get(u.getCode()));
                }
            }
        }
        // 无计划，也无院校专业
        if(MapUtils.isEmpty(universitiesMap)) {
            return Lists.newArrayList();
        }
        // 查询涉及的规则
        cond.put("year", planYear);
        cond.put("universityIds", universitiesMap.keySet());
        Map<Long, List<AEnrollScore>> universityScoreListMap = aEnrollScoreMapper.selectListByRuleCond(cond).stream().collect(Collectors.groupingBy(AEnrollScore::getUniversityId));
        Map<Long, List<AEnrollSpecial>> universitySpecialListMap = aEnrollSpecialMapper.selectListByRuleCond(cond).stream().collect(Collectors.groupingBy(AEnrollSpecial::getUniversityId));
        cond.remove("universityIds");
        cond.remove("year");

        Map<String, String> mutexOptionMap = getMutexOptionMap();
        List<VoluntaryDto.AIResponse> aiRespList = Lists.newArrayList();
        for (Long universityId : universitiesMap.keySet()) {
            List<AEnrollScore> scoreList = universityScoreListMap.get(universityId); // 分数条件
            List<AEnrollSpecial> specialList = universitySpecialListMap.get(universityId);// 专项条件
            List<AMarjorPlan> currPlanList = currUniversityMajorPlansMap.get(universityId);// 当年计划
            List<AMarjorPlan> historyPlanList = historyUniversityMajorPlansMap.get(universityId);// 历年计划
            BBusiWishUniversities u = universitiesMap.get(universityId);// 院校
            List<BBusiWishUniversitiesProfession> professionList = universityMajorsMap.get(u.getId()); // 所有专业
            // 院校变更标志
            AEnrollUniversity euCond = new AEnrollUniversity();
            euCond.setUniversityId(u.getId());
            euCond.setYear(planYear);
            List<AEnrollUniversity> enrollUniversityList = aEnrollUniversityMapper.selectAEnrollUniversityList(euCond);
            // 院校历史情况
            AMarjorSubmit submitCond = new AMarjorSubmit();
            submitCond.setExamineeType(examType);
            submitCond.setUniversityId(universityId);
            List<AMarjorSubmit> submitList = aMarjorSubmitMapper.selectAMarjorSubmitList(submitCond);

            VoluntaryDto.AIResponse aiResp = new VoluntaryDto.AIResponse();
            aiResp.setUniversity(u);

            VoluntaryDto.SingleResponse sr;
            List<VoluntaryDto.SingleResponse> singleResponseList = Lists.newArrayList();
            if (CollectionUtils.isNotEmpty(currPlanList)) {
                aiResp.setEnrollCode(currPlanList.get(0).getEnrollCode());
                Map<String, BBusiWishUniversitiesProfession> professionMap = null == professionList ? Maps.newHashMap() : professionList.stream().collect(Collectors.toMap(BBusiWishUniversitiesProfession::getName, Function.identity()));
                for (AMarjorPlan mp : currPlanList) {
                    if (null != (sr = buildSingleResponse(submitYear, planYear, gender, mp, professionMap.get(mp.getMajorName()), enrollUniversityList, paramMap, u,
                            historyPlanList, submitList, scoreList, specialList, mutexOptionMap, isAi, isScoreOnly))) {
                        singleResponseList.add(sr);
                    }
                }
            } else {
                for (BBusiWishUniversitiesProfession prof : professionList) {
                    if (null != (sr = buildSingleResponse(submitYear, planYear, gender, null, prof, enrollUniversityList, paramMap, u,
                            historyPlanList, submitList, scoreList, specialList, mutexOptionMap, isAi, isScoreOnly))) {
                        singleResponseList.add(sr);
                    }
                }
            }

            aiResp.setMajorDetails(singleResponseList);
            sortAndExtractEnroll(singleResponseList, aiResp);
            aiRespList.add(aiResp);
        }
        Collections.sort(aiRespList, new Comparator<VoluntaryDto.AIResponse>() {
            @Override
            public int compare(VoluntaryDto.AIResponse o1, VoluntaryDto.AIResponse o2) {
                Integer d1 = o1.getEnrollRate();
                Integer d2 = o2.getEnrollRate();
                d1 = null == d1 ? 999 : (d1 == 0 ? 998 : d1);
                d2 = null == d2 ? 999 : (d2 == 0 ? 998 : d2);
                return d1.compareTo(d2);
            }
        });
        return aiRespList;
    }

    private VoluntaryDto.SingleResponse buildSingleResponse(Integer submitYear, Integer planYear, String gender, AMarjorPlan currPlan, BBusiWishUniversitiesProfession prof,
                                                          List<AEnrollUniversity> enrollUniversityList, Map<String, String> paramMap, BBusiWishUniversities u,
                                                          List<AMarjorPlan> historyPlanList, List<AMarjorSubmit> submitList,
                                                          List<AEnrollScore> scoreList, List<AEnrollSpecial> specialList, Map<String,String> mutexOptionMap, boolean isAi, boolean isScoreOnly) {
        if (null != submitList) {
            Collections.sort(submitList, new Comparator<AMarjorSubmit>() {
                @Override
                public int compare(AMarjorSubmit o1, AMarjorSubmit o2) {
                    int iRet;
                    if (0 != (iRet = o1.getYear().compareTo(o2.getYear()))) {
                        return -iRet;
                    }
                    if (0 != (iRet = o1.getMajorName().compareTo(o2.getMajorName()))) {
                        return -iRet;
                    }
                    if (null == o1.getMajorDirection()) {
                        if (null != o2.getMajorDirection()) {
                            return 1;
                        }
                        return 0;
                    } else if (null == o2.getMajorDirection()) {
                        return -1;
                    }
                    return 0;
                }
            });
        } else {
            submitList = Lists.newArrayList();
        }
        if (null != historyPlanList) {
            Collections.sort(historyPlanList, new Comparator<AMarjorPlan>() {
                @Override
                public int compare(AMarjorPlan o1, AMarjorPlan o2) {
                    int iRet;
                    if (0 != (iRet = o1.getYear().compareTo(o2.getYear()))) {
                        return -iRet;
                    }
                    if (0 != (iRet = o1.getMajorName().compareTo(o2.getMajorName()))) {
                        return -iRet;
                    }
                    if (null == o1.getMajorDirection()) {
                        if (null != o2.getMajorDirection()) {
                            return 1;
                        }
                        return 0;
                    } else if (null == o2.getMajorDirection()) {
                        return -1;
                    }
                    return o1.getMajorDirection().compareTo(o2.getMajorDirection());
                }
            });
        } else {
            historyPlanList = Lists.newArrayList();
        }

        // 录取线
        List<VoluntaryDto.MajorClearingHistory> clearings = Lists.newArrayList(); // 补录情况
        List<VoluntaryDto.MajorEnrollHistory> histories = Lists.newArrayList(); // 初录情况

        List<VoluntaryDto.MajorEnrollRule> ruleMatchList = Lists.newArrayList();
        List<VoluntaryDto.MajorEnrollRule> improveList = Lists.newArrayList();
        Double tmpValue; // 总分，得分
        AMarjorSubmit lastSubmit = null;
        Boolean typeChange = false;
        VoluntaryDto.FormulaScoreStat formulaScoreStat = new VoluntaryDto.FormulaScoreStat();
        String needGroup = StringUtils.trimToEmpty(null != currPlan && null != currPlan.getMajorGroup() ? currPlan.getMajorGroup() : "");
        String needMajor = StringUtils.trimToEmpty(null != currPlan ? currPlan.getMajorName() : (null != prof ? prof.getName() : ""));
        String needDirect = StringUtils.trimToEmpty(null != currPlan ? currPlan.getMajorDirection() : "");
        String needMajorDirect = needMajor + needDirect;
        // 判断是否政策变化情况
        List<AEnrollUniversity> validEuList = enrollUniversityList.stream()
                .filter(t -> needGroup.equals(StringUtils.trimToEmpty(t.getMajorGroups())) && t.getMajorNames().contains(needMajor))
                .collect(Collectors.toList());
        typeChange = CollectionUtils.isNotEmpty(validEuList) && new Integer(1).equals(validEuList.get(0).getTypeChange());

        Set<String> existSet = Sets.newHashSet();
        existSet.clear();
        List<AMarjorSubmit> validSubmitList = Lists.newArrayList();
        if (null != submitList) {
            for (AMarjorSubmit s : submitList) { // 历年录取通过专业分来判断差别
                String majorNameDirect = s.getMajorName() + StringUtils.trimToEmpty(s.getMajorDirection());
                if (needMajorDirect.equals(majorNameDirect) && existSet.add(needMajorDirect + s.getYear() + s.getEnrollType())) {
                    validSubmitList.add(s);
                    if (s.getYear().equals(submitYear) && s.getEnrollType().equals("初录") ) {
                        lastSubmit = s; // TODO 初录，补录与分数线的关系 要处理
                    }
                }
            }
        }
        // 分项规则检查
        existSet.clear();
        Double currTotal = null;
        if (null != scoreList) {
            Double inputScoreRate = NumberUtils.toInt(paramMap.get("ScoreRate"), 0) / 100.0;
            List<AEnrollScore> fEnrollScoreList = Lists.newArrayList();
            String inclMajorDirection = needMajor + "(" + needDirect + ")";
            String exclMajorDirection = needMajor + "(";
            Map<Boolean, List<AEnrollScore>> majorEnrollScoresMap = scoreList.stream()
                    .filter(t -> needGroup.equals(StringUtils.trimToEmpty(t.getMajorGroups())))
                    .collect(Collectors.groupingBy(t -> StringUtils.isNotBlank(t.getMajorNames())));

            if(majorEnrollScoresMap.isEmpty() && StringUtils.isNotBlank(needGroup)) {
                majorEnrollScoresMap = scoreList.stream()
                        .filter(t -> StringUtils.isBlank(t.getMajorGroups()))
                        .collect(Collectors.groupingBy(t -> StringUtils.isNotBlank(t.getMajorNames())));

            }
            List<AEnrollScore> tmpEnrollScoreList = majorEnrollScoresMap.get(Boolean.TRUE);
            AEnrollScore skillEnrollScore = null;
            if (null != tmpEnrollScoreList) {
                for (AEnrollScore r : tmpEnrollScoreList) {
                    if (StringUtils.isNotBlank(r.getMajorNames())) {
                        if (!r.getMajorNames().contains(needMajor)) {
                            continue;
                        }
                        if (StringUtils.isNotBlank(r.getMajorDirections())) { // 有单独的方向时，分开判断
                            if (StringUtils.isBlank(needDirect) || !r.getMajorDirections().contains(needDirect)) {
                                continue;
                            }
                        } else if (StringUtils.isNotBlank(needDirect)) { // 有direct要求时，这里需要有 "<专业>(<方向>)
                            if (!r.getMajorNames().contains(inclMajorDirection)) {
                                continue;
                            }
                        } else if (r.getMajorNames().contains(exclMajorDirection)) { // 当无direct要求时，这时不能有 "<专业>("
                            continue;
                        }
                    }
                    Boolean isSkillScore = isScoreOnly && r.getItemType().equals("ScoreSkill");
                    if(isSkillScore) {
                        skillEnrollScore = r;
                    }
                    appendScoreRule(formulaScoreStat, existSet, r, fEnrollScoreList, isAi, paramMap, inputScoreRate, isSkillScore);
                }
            }
            if (fEnrollScoreList.size() == 0 && null != (tmpEnrollScoreList = majorEnrollScoresMap.get(Boolean.FALSE))) {
                for (AEnrollScore r : tmpEnrollScoreList) {
                    Boolean isSkillScore = isScoreOnly && r.getItemType().equals("ScoreSkill");
                    if(isSkillScore) {
                        skillEnrollScore = r;
                    }
                    appendScoreRule(formulaScoreStat, existSet, r, fEnrollScoreList, isAi, paramMap, inputScoreRate, isSkillScore);
                }
            }

            boolean isSameYear = null == lastSubmit || planYear.equals(lastSubmit.getYear());
            currTotal = formulaScoreStat.getTypeValue("StatRateScore", "", false);
            for (AEnrollScore r : fEnrollScoreList) {
                VoluntaryDto.MajorEnrollRule mr = buildEnrollRule(r);
                mr.setContent(StringUtils.isNotBlank(r.getEqualFormula()) ? r.getEqualFormula() : r.getEnrollFormula());
                if(isScoreOnly) {
                    String vt;
                    if (null != (vt = r.getValueType()) && vt.startsWith("Score")) {
                        improveList.add(mr);
                        mr.setCategory(VoluntaryDto.EnumRuleCategory.Enroll);
                        mr.setType(VoluntaryDto.EnumRuleType.ScoreTotal);
                        // mr.setValue(null != currTotal ? String.valueOf(currTotal.intValue()) : "");
                        if (null == lastSubmit || null == lastSubmit.getScore() || null == lastSubmit.getScoreTotal() && !isSameYear) {
                            mr.setFailedMessage("缺历史录取数据");
                            continue;
                        }
                        mr.setYear(lastSubmit.getYear());
                        if (typeChange) {
                            mr.setFailedMessage("规则变更，无法计算");
                            continue;
                        } else if (!formulaScoreStat.isScoreValid() && (!isAi || inputScoreRate <= 0.0)) {
                            mr.setFailedMessage("用户没有填写完整");
                            continue;
                        }
                        Integer validScoreTotal = isSameYear ? formulaScoreStat.getAllTotal() : lastSubmit.getScoreTotal();
                        Double currScoreRate = isSameYear ? 1.0 : formulaScoreStat.getAllTotal() * 1.0 / lastSubmit.getScoreTotal(); // 去年的分转成今年的分
                        EnrollRateCalculator.RateLevel rl;
                        if (null != lastSubmit.getScore() && currTotal != null && null != (rl = enrollRateCalculator.satisfy(validScoreTotal, lastSubmit.getScore(), currTotal / currScoreRate))) { // 按去年标准算概率
                            Double skillRate = Double.parseDouble(skillEnrollScore.getValueRule());
                            mr.setValue(String.valueOf(Math.round((lastSubmit.getScore() * currScoreRate - currTotal) * 10 / skillRate) / 10.0));
                            mr.setValid(true);
                        } else {
                            mr.setFailedMessage("没有对应计算条件");
                            continue;
                        }
                    } else {
                        ruleMatchList.add(mr);
                        mr.setValid(true);
                    }
                    continue;
                }
                ruleMatchList.add(mr);
                String vt;
                if (null == (vt = r.getValueType())) { // 无值时相当于直接通过
                    mr.setValid(true);
                } else if (null != (vt = r.getValueType()) && vt.startsWith("Score")) {
                    mr.setValue(null != currTotal ? String.valueOf(currTotal.intValue()) : "");
                    if (null == lastSubmit || null == lastSubmit.getScore() || null == lastSubmit.getScoreTotal() && !isSameYear) {
                        mr.setFailedMessage("缺历史录取数据");
                        continue;
                    } else if (typeChange) {
                        mr.setFailedMessage("规则变更，无法计算");
                        continue;
                    } else if (!formulaScoreStat.isValid() && (!isAi || inputScoreRate <= 0.0)) {
                        mr.setFailedMessage("用户没有填写完整");
                        continue;
                    }
                    mr.setValid(true);

                    Integer validScoreTotal = isSameYear ? formulaScoreStat.getAllTotal() : lastSubmit.getScoreTotal();
                    Double currScoreRate = isSameYear ? 1.0 : formulaScoreStat.getAllTotal() * 1.0 / lastSubmit.getScoreTotal(); // 去年的分转成今年的分
                    Double currScore = null;
                    if (null != lastSubmit.getCulturalScore()) {
                        if (null == (currScore = formulaScoreStat.getTypeValue("ScoreSingle", "", false))) {
                            currScore = formulaScoreStat.getTypeValue("ScoreBase", "", false);
                        }
                        if (null != currScore && currScore < lastSubmit.getCulturalScore() * currScoreRate) {
                            // mr.setValid(false);
                            mr.setMissingValue(String.valueOf(Math.round(lastSubmit.getCulturalScore() * currScoreRate - currScore)));
                            mr.setFailedMessage("校考分低于分数线");
                        }
                    }
                    if (null != lastSubmit.getProfScore() && null != (currScore = formulaScoreStat.getTypeValue("ScoreSkill", "", false)) && currScore < lastSubmit.getProfScore() * currScoreRate) {
                        // mr.setValid(false);
                        mr.setMissingValue(String.valueOf(Math.round(lastSubmit.getProfScore() * currScoreRate - currScore)));
                        mr.setFailedMessage("技能测试分低于分数线");
                    }
                    // 检查总分，学考(文化),专业(技能)
                    EnrollRateCalculator.RateLevel rl;
                    if (null != lastSubmit.getScore() && currTotal != null && null != (rl = enrollRateCalculator.calSchoolEnrollRate(validScoreTotal, lastSubmit.getScore(), currTotal / currScoreRate))) { // 按去年标准算概率
                        mr.setRl(rl);
                        mr.setEnrollRate(rl.rate);
                        mr.setEnrollRateText(rl.typeLabel);
                        mr.setEnumPickType(rl.type);
                        if (mr.getValid() && currTotal < lastSubmit.getScore() * currScoreRate) {
                            // mr.setValid(false);
                            mr.setMissingValue(String.valueOf(Math.round(lastSubmit.getScore() * currScoreRate - currTotal))); // 按今年的算差值
                            mr.setFailedMessage("综合分低于分数线");
                        }
                    }
                } else { // 专项符合型
                    mr.setValid(processPassCheck(paramMap, formulaScoreStat, r.getValueRule(), mr, r.getValueType(), r.getItemField(), r.getValuePassRule()));
                }
            }
        }
        if (null != specialList) {
            existSet.clear();
            List<AEnrollSpecial> validSpecialList = Lists.newArrayList();
            Map<String, Pair<String, Set<String>>> groupMutexOptionsMap = Maps.newHashMap();
            for (AEnrollSpecial r : specialList) {
                if (StringUtils.isNotBlank(r.getGender()) && !r.getGender().equals(gender)
                        || StringUtils.isNotBlank(r.getMajorName()) && !Sets.newHashSet(r.getMajorName().split(",")).contains(needMajor)
                        || StringUtils.isNotBlank(r.getMajorDirection()) && !r.getMajorDirection().equals(needDirect)) {
                    continue;
                }
                if(r.getValueType().startsWith("StatBMI")) { // TODO 身高体重的名称需要是固定的
                    String height = paramMap.get("身高");
                    String weight = paramMap.get("体重");
                    Integer iHeight;
                    if (StringUtils.isNotBlank(height) && StringUtils.isNotBlank(weight) && (iHeight = NumberUtils.toInt(height, 0)) > 0) {
                        paramMap.put(r.getItemField(), String.valueOf(NumberUtils.toInt(height, 0) * 100 / iHeight));
                    }
                } else if (null != r.getItemGroup() && "Checkbox".equals(r.getValueType())) {
                    Pair<String, Set<String>> p = getGroupOptions(groupMutexOptionsMap, mutexOptionMap, paramMap, r.getItemGroup());
                    if (p.getRight().size() == 0) { // 无值时
                        paramMap.put(r.getItemField(), null);
                    } else if (StringUtils.isNotBlank(p.getLeft()) && p.getRight().contains(p.getLeft())) { // 选中互斥时全是否
                        paramMap.put(r.getItemField(), !p.getLeft().contains("无") ? "[\"是\"]" : "[\"否\"]");
                    } else {
                        paramMap.put(r.getItemField(), p.getRight().contains(r.getItemName()) ? "[\"是\"]" : "[\"否\"]");
                    }
                }
                validSpecialList.add(r);
            }
            for (AEnrollSpecial r : validSpecialList) {
                VoluntaryDto.MajorEnrollRule mr = new VoluntaryDto.MajorEnrollRule();
                mr.setCategory(VoluntaryDto.EnumRuleCategory.Special);
                mr.setContent(r.getEnrollFormula());
                mr.setDescription(r.getComment());
                ruleMatchList.add(mr);
                if(isScoreOnly) {
                    mr.setType(VoluntaryDto.EnumRuleType.Readonly);
                    mr.setValid(true);
                    continue;
                }
                mr.setType(itemType2RuleType(r.getItemType(), r.getValueType(), false));
                if (VoluntaryDto.EnumRuleType.Readonly.equals(mr.getType())) {
                    mr.setValid(true);
                } else {
                    mr.setValid(processPassCheck(paramMap, formulaScoreStat, r.getValueRule(), mr, r.getValueType(), r.getItemField(), r.getValuePassRule()));
                }
            }
        }
        existSet.clear();
        Map<String, AMarjorPlan> historyPlanMap = historyPlanList.stream().collect(Collectors.toMap(t -> t.getYear() + t.getMajorName() + StringUtils.trimToEmpty(t.getMajorDirection()), Function.identity()));
        for (AMarjorSubmit s : validSubmitList) {
            if ("初录".equals(s.getEnrollType())) {
                VoluntaryDto.MajorEnrollHistory h = new VoluntaryDto.MajorEnrollHistory();
                h.setYear(s.getYear());
                h.setScore(null != s.getScore() ? String.valueOf(s.getScore()) : ""); // TODO 240301 直接输出录取分
                /*if (null != currTotal && null != s.getScore()) {
                    Double diff = currTotal - s.getScore();
                    h.setScore(diff > 0 ? "+" + diff.intValue() : "" + diff.intValue()); // TODO 需要一个分来比较，综合分？
                } else {
                    h.setScore("");
                }*/
                AMarjorPlan p = historyPlanMap.get(s.getYear() + s.getMajorName() + StringUtils.trimToEmpty(s.getMajorDirection()));
                if(null != p) {
                    h.setPlan(p.getPlanTotal()); // TODO 估计输出录取人数 23.12.26 分开输入录取和计划 24.12.27
                } else {
                    // System.out.println(s.getId());
                }
                h.setEnroll(s.getEnrollTotal());
                histories.add(h);
            } else {
                /*VoluntaryDto.MajorClearingHistory c = new VoluntaryDto.MajorClearingHistory();
                c.setYear(s.getYear());
                if (null != currTotal && null != s.getScore()) {
                    Double diff = currTotal - s.getScore();
                    c.setScore(diff > 0 ? "+" + diff.intValue() : "" + diff.intValue()); // TODO 需要一个分来比较，综合分？
                } else {
                    c.setScore("");
                }
                c.setRealNum(s.getEnrollTotal());
                clearings.add(c);*/ // TODO 240301 关闭补录
            }
        }

        VoluntaryDto.SingleResponse sr = new VoluntaryDto.SingleResponse();
        sr.setUniversityCode(u.getCode());
        sr.setMajorCode(null != prof ? prof.getCode() : "");
        sr.setMajorName(needMajor);
        sr.setMajorDirection(needDirect);
        // 查找最高的专业 EnrollRule 为本专业的，理论上只有一个合并rule有此值
        EnrollRateCalculator.RateLevel lastRl = null;
        boolean specialValid = true, specialPass = true;
        for (VoluntaryDto.MajorEnrollRule er : ruleMatchList) {
            if (null != er.getRl() && (null == lastRl || er.getRl().getRate() < lastRl.getRate())) {
                lastRl = er.getRl();
            }
            if ((specialPass || specialValid) && VoluntaryDto.EnumRuleType.Special.equals(er.getType())) {
                if (null == er.getValid()) {
                    specialValid = false;
                } else if (!er.getValid()) {
                    specialPass = false;
                }
            }
        }
        if (!specialPass) {
            sr.setEnrollRate(0);
            sr.setEnrollRateText("零概率");
            sr.setEnumPickEmpty(VoluntaryDto.EnumPickEmpty.EnrollPass);
        } else if (null != lastRl && specialValid) {
            sr.setEnumPickType(lastRl.getType());
            sr.setEnrollRate(lastRl.getRate());
            sr.setEnrollRateText(lastRl.getTypeLabel());
        } else {
            sr.setEnrollRateText("无概率");
            sr.setEnumPickEmpty(VoluntaryDto.EnumPickEmpty.New);
        }
        if (null != currPlan) {
            sr.setMajorGroup(currPlan.getMajorGroup()); // 没有MajorEnrollId, 暂时用这个
            sr.setMajorEnrollCode(StringUtils.isBlank(currPlan.getMajorEnrollCode()) ? String.valueOf(currPlan.getId()) : currPlan.getMajorEnrollCode());
            sr.setTips(null != currPlan ? currPlan.getComment() : "");
        }
        sr.setHistories(histories);
        sr.setClearings(clearings);
        sr.setRules(ruleMatchList);
        sr.setImproves(improveList);
        sr.setScore(currTotal);
        if (!isScoreOnly && null != lastSubmit && null != lastSubmit.getScore() && null != currTotal && currTotal < lastSubmit.getScore()) {
            Double diffValue = lastSubmit.getScore() - currTotal;
            sr.setImproves(formulaScoreStat.getImproveScore(diffValue));
        }
        return sr;
    }

    private void appendScoreRule(VoluntaryDto.FormulaScoreStat formulaScoreStat, Set<String> existSet, AEnrollScore r, List<AEnrollScore> fEnrollScoreList,
                                 Boolean isAi, Map<String, String> paramMap, Double inputScoreRate, Boolean isSkillScore) {
        String vt, iv;
        Double currScore = null;
        Double scoreRate = null;
        Double tmpValue; // 总分，得分
        if (null != (vt = r.getValueType()) && vt.startsWith("Score")) { //需要合并Score型(ScoreSingle, ScoreBase, ScoreSkill)的各类成绩, 其他为符合条件
            if (existSet.add(r.getEnrollFormula())) { // 合并规则只留第一个
                if (!existSet.add("_ScoreFormula_")) {
                    existSet.remove(r.getEnrollFormula());
                    return;
                }
                fEnrollScoreList.add(r);
            }
            if (null != r.getValueRule() && (scoreRate = Double.parseDouble(r.getValueRule())) <= 0.0) {
                scoreRate = 1.0;
            }
            formulaScoreStat.addTypeTotal(r.getScoreTotal());
            if (isAi && vt.equals("ScoreRate")) { //　根据得分率及总分计算分值
                currScore = isSkillScore ? 0.0 : r.getScoreTotal() * inputScoreRate;
                formulaScoreStat.addGroup(r.getItemGroup(), currScore, r.getScoreTotal().doubleValue(), scoreRate);
                formulaScoreStat.addType(r.getItemType(), r.getItemField(), currScore, scoreRate);
            } else {
                currScore = (!isSkillScore && StringUtils.isNotBlank((iv = paramMap.get(r.getItemField())))) ? NumberUtils.toDouble(iv, 0.0) : 0.0;
                formulaScoreStat.addGroup(r.getItemGroup(), currScore, r.getScoreTotal().doubleValue(), scoreRate);
                formulaScoreStat.addType(r.getItemType(), r.getItemField(), currScore, scoreRate);
            }
        } else {
            fEnrollScoreList.add(r);
        }
    }

    private VoluntaryDto.MajorEnrollRule buildEnrollRule(AEnrollScore r) {
        VoluntaryDto.MajorEnrollRule mr = new VoluntaryDto.MajorEnrollRule();
        mr.setCategory(VoluntaryDto.EnumRuleCategory.Enroll);
        mr.setType(itemType2RuleType(r.getItemType(), r.getValueType(), true)); // 学考 ScoreSingle 强转为 ScoreTotal, ScoreBase 校考, ScoreSkill 技能, Special 专项
        mr.setContent(r.getEnrollFormula());
        mr.setImproveType(r.getItemCategory());
        mr.setDescription(r.getComment());
        return mr;
    }
    private Pair<String, Set<String>> getGroupOptions(Map<String, Pair<String, Set<String>>> groupOptionsMap, Map<String, String> mutexOptionMap, Map<String, String> paramMap, String itemGroup) {
        String[] groupNameField = StringUtils.split(itemGroup, "_");
        String groupFieldName = groupNameField[groupNameField.length > 1 ? 1 : 0];
        Pair<String, Set<String>> pair = groupOptionsMap.get(groupFieldName);
        if(null == pair) {
            String iv = paramMap.get(groupFieldName);
            Set<String> options = StringUtils.isNotBlank(iv) ? JSONArray.parseArray(iv).stream().map(t -> (String) t).collect(Collectors.toSet()) : Sets.newHashSet();
            pair = new MutablePair<>(mutexOptionMap.get(groupFieldName), options);
            groupOptionsMap.put(groupFieldName, pair);
        }
        return pair;
    }

    private Boolean processPassCheck(Map<String, String> paramMap, VoluntaryDto.FormulaScoreStat stat, String valueRule,
                                          VoluntaryDto.MajorEnrollRule mr, String valueType, String itemField, String passRule) {
        Boolean matched = null;
        Double tmpValue;
        boolean isStat = null != valueType && valueType.startsWith("Stat");
        if (isStat || NumberTypeSet.contains(valueType)) {
            Double v = null;
            String iv;
            if (isStat) { // TODO 统计分
                v = stat.getTypeValue(valueType, itemField, "0".equals(valueRule));
            } else if(StringUtils.isNotBlank(passRule) && StringUtils.isNotBlank((iv = paramMap.get(itemField)))
                    && (tmpValue = NumberUtils.toDouble(iv, 0.0)) > 0.01) {
                v = tmpValue;
            }
            if (v != null) {
                String[] range = passRule.split("-");
                if (StringUtils.isNotBlank(range[0]) && (tmpValue = NumberUtils.toDouble(range[0], 0.0)) > 0.01 && v < tmpValue) {
                    matched = false;
                    mr.setFailedMessage("小于最大值");
                } else if (range.length > 1 && StringUtils.isNotBlank(range[1]) && (tmpValue = NumberUtils.toDouble(range[1], 0.0)) > 0.01 && v > tmpValue) {
                    matched = false;
                    mr.setFailedMessage("超过最大值");
                } else {
                    matched = true;
                }
            } else {
                mr.setFailedMessage("未输入参数");
            }
        } else if ("Radio".equals(valueType)) {
            String iv = paramMap.get(itemField);
            if (StringUtils.isNotBlank(iv)) {
                if (!(matched = StringUtils.isNotBlank(passRule) && passRule.contains(iv))) {
                    mr.setFailedMessage("条件不满足");
                }
            } else {
                mr.setFailedMessage("未输入参数");
            }
        } else if ("Checkbox".equals(valueType) || "Picker".equals(valueType)) {
            String iv = paramMap.get(itemField);
            if(StringUtils.isBlank(passRule)) {
                if (!(matched = StringUtils.isBlank(iv))) {
                    mr.setFailedMessage("选择条件不满足1");
                }
            } else if (StringUtils.isNotBlank(iv)) {
                List<String> valueList = Arrays.asList(passRule.split(","));
                List<String> inputList = JSONArray.parseArray(iv).stream().map(t -> (String) t).collect(Collectors.toList());
                if (!(matched = CollectionUtils.intersection(valueList, inputList).size() == valueList.size())) {
                    mr.setFailedMessage("选择条件不满足2");
                }
            }
        }
        return matched;
    }
    private void sortAndExtractEnroll(List<VoluntaryDto.SingleResponse> singleResponseList, VoluntaryDto.AIResponse aiResp) {
        if (CollectionUtils.isEmpty(singleResponseList)) {
            aiResp.setEnrollRate(null);
            aiResp.setEnrollRateText("无概率");
            aiResp.setEnumPickEmpty(VoluntaryDto.EnumPickEmpty.EnrollPass);
        }
        Collections.sort(singleResponseList, new Comparator<VoluntaryDto.SingleResponse>() {
            @Override
            public int compare(VoluntaryDto.SingleResponse o1, VoluntaryDto.SingleResponse o2) {
                Integer d1 = o1.getEnrollRate();
                Integer d2 = o2.getEnrollRate();
                if (null == d1) {
                    if (null != d2) {
                        return 1;
                    }
                    return 0;
                } else if (null == d2) {
                    return -1;
                }
                return -d1.compareTo(d2);
            }
        });
        for (VoluntaryDto.SingleResponse sr : singleResponseList) {
            aiResp.setEnrollRate(sr.getEnrollRate());
            aiResp.setEnrollRateText(sr.getEnrollRateText());
            aiResp.setEnumPickType(sr.getEnumPickType());
            aiResp.setEnumPickEmpty(sr.getEnumPickEmpty());
            break;
        }
    }

    public R<VoluntaryDto.VoluntaryConfig> getVoluntaryConfig() { // 如果有填报配置相关，放在这里
        VoluntaryDto.VoluntaryConfig resp = new VoluntaryDto.VoluntaryConfig();
        return R.ok(resp);
    }

    public List<JSONObject> getVoluntaryList(@ApiParam @RequestParam VoluntaryDto.EnumVoluntaryType type) { // 我的志愿表 // 后台填充快照缺省
        AWishRecord cond = new AWishRecord();
        cond.setUserId(SecurityUtils.getLoginUser().getUserId());
        if (null != type) {
            cond.setType(type.name());
        }
        cond.setStatus(1);
        List<AWishRecord> aWishRecordList = aWishRecordMapper.selectAWishRecordList(cond);
        List<JSONObject> dtoList = Lists.newArrayList();
        aWishRecordList.stream().forEach(t -> {
            dtoList.add(toModel(t));
        });
        return PageUtil.getDtoListWithPageable(aWishRecordList, dtoList);
    }

    public R<Long> submitVoluntary(JSONObject model) { // 填报 // 前端+后台按需要剔除一些不需快照的信息（目前主要是院校信息）
        SysUser user = SecurityUtils.getLoginUser().getUser();
        AWishRecord wishRecord = new AWishRecord();
        VoluntaryDto.User userSnapshot = new VoluntaryDto.User();
        userSnapshot.setName(user.getNickName());
        userSnapshot.setSex(user.getSex());
        userSnapshot.setExamType(user.getExamType().title()); // TODO MF
        userSnapshot.setProvinceName(user.getLocation());

        Long modelId = null;
        String modelName = null;
        Integer year = null;
        VoluntaryDto.EnumVoluntaryType type = null;
        try {
            modelId = model.getLong("id");
            modelName = model.getString("name");
            year = model.getInteger("year");
            type = VoluntaryDto.EnumVoluntaryType.valueOf(model.getString("voluntaryType"));
            wishRecord.setBatchName(model.getString("batchName"));
            wishRecord.setRequest(model.getString("request"));
            wishRecord.setDetails(model.getString("details"));
            wishRecord.setUserSnapshot(mapper.writeValueAsString(userSnapshot));
        } catch (JsonProcessingException e) {
            return R.fail("格式错误: " + modelId);
        }
        wishRecord.setYear(null == year ? getPlanYear(user) : year);
        wishRecord.setType(type.name());
        if (null != modelId) {
            wishRecord.setUpdateTime(new Date());
            wishRecord.setId(modelId);
            wishRecord.setName(modelName);
            wishRecord.setUpdateTime(new Date());
            aWishRecordMapper.updateAWishRecord(wishRecord);
        } else {
            wishRecord.setCreateTime(new Date());
            wishRecord.setSeq(getWishSeq(wishRecord.getYear(), wishRecord.getType()));
            wishRecord.setName(StringUtils.isNotBlank(modelName) ? modelName : ("AI".equals(wishRecord.getType()) ? "AI志愿" : "模拟志愿") + wishRecord.getSeq());
            wishRecord.setUserId(user.getUserId());
            wishRecord.setStatus(1);
            aWishRecordMapper.insertAWishRecord(wishRecord);
        }
        return R.ok(wishRecord.getId());
    }

    private Integer getWishSeq(Integer year, String type) {
        AWishRecord cond = new AWishRecord();
        cond.setUserId(SecurityUtils.getLoginUser().getUserId());
        cond.setYear(year);
        cond.setType(type);
        List<AWishRecord> aWishRecordList = aWishRecordMapper.selectAWishRecordList(cond);
        Integer newSeq = CollectionUtils.isNotEmpty(aWishRecordList) ? aWishRecordList.get(0).getSeq() + 1 : 1;
        return newSeq;
    }

    public void obsoleteWishRecord(Long userId) {
        aWishRecordMapper.updateObsoleteByUser(userId);
        busiWishRecordsMapper.updateObsoleteByUser(userId);
    }

    public R<JSONObject> getVoluntary(Long id) { // 志愿表详情 // 后台填充快照缺省
        SysUser user = SecurityUtils.getLoginUser().getUser();
        AWishRecord wishRecord = aWishRecordMapper.selectAWishRecordById(id);
        if (null == wishRecord || !SecurityUtils.getUserId().equals(wishRecord.getUserId())) {
            return R.fail("错误id");
        }
        if (null == wishRecord) {
            throw new ServiceException("无此志愿id");
        } else if (!user.getUserId().equals(wishRecord.getUserId())) {
            throw new ServiceException("无此志愿id号");
        }
        JSONObject resp = toModel(wishRecord);
        return R.ok(resp);
    }

    private JSONObject toModel(AWishRecord wishRecord) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        JSONObject model = new JSONObject();
        model.put("id", wishRecord.getId());
        model.put("year", wishRecord.getYear());
        model.put("voluntaryType", VoluntaryDto.EnumVoluntaryType.valueOf(wishRecord.getType()));
        model.put("name", wishRecord.getName());
        model.put("batchName", wishRecord.getBatchName());
        model.put("createTime", sdf.format(wishRecord.getCreateTime()));
        try {
            model.put("userSnapshot", mapper.readValue(wishRecord.getUserSnapshot(), VoluntaryDto.User.class));
            model.put("request", JSONObject.parseObject(wishRecord.getRequest()));
            model.put("details", JSONArray.parseArray(wishRecord.getDetails()));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return model;
    }

    private VoluntaryDto.SingleResponse buildTestResp() {
        String json = sysConfigService.selectConfigByKey("demo.voluntary.single");
        if (StringUtils.isNotBlank(json)) {
            try {
                return mapper.readValue(json, VoluntaryDto.SingleResponse.class);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return new VoluntaryDto.SingleResponse();
    }

    private VoluntaryDto.AIResponse buildAiResp() {
        String json = sysConfigService.selectConfigByKey("demo.voluntary.ai");
        if (StringUtils.isNotBlank(json)) {
            try {
                VoluntaryDto.AIResponse resp = mapper.readValue(json, VoluntaryDto.AIResponse.class);
                resp.setMajorDetails(Lists.newArrayList(buildTestResp()));
                return resp;
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return new VoluntaryDto.AIResponse();
    }

    private VoluntaryDto.VoluntaryModel buildModel() {
        String json = sysConfigService.selectConfigByKey("demo.voluntary.model");
        if(StringUtils.isNotBlank(json)) {
            try {
                VoluntaryDto.VoluntaryModel model = mapper.readValue(json, VoluntaryDto.VoluntaryModel.class);
                model.setDetails(Lists.newArrayList(buildAiResp()));
                return model;
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return new VoluntaryDto.VoluntaryModel();
    }

    private VoluntaryDto.EnumRuleType itemType2RuleType(String itemType, String valueType, boolean foreSingleTotal) {
        if ("Notice".equals(valueType)) {
            return VoluntaryDto.EnumRuleType.Readonly;
        } else if (foreSingleTotal && VoluntaryDto.EnumRuleType.ScoreSingle.name().equals(itemType)) {
            return VoluntaryDto.EnumRuleType.ScoreTotal;
        }
        return VoluntaryDto.EnumRuleType.valueOf(itemType);
    }



    public List<VoluntaryDto.VoluntaryRecord> getVoluntaryList2() { // 我的志愿表 // 后台填充快照缺省
        AWishRecord cond = new AWishRecord();
        cond.setUserId(SecurityUtils.getLoginUser().getUserId());
        cond.setStatus(1);
        List<AWishRecord> aWishRecordList = aWishRecordMapper.selectAWishRecordList(cond);
        if(CollectionUtils.isEmpty(aWishRecordList)) {
            return Collections.emptyList();
        }
        return JSONArray.parseArray(aWishRecordList.get(0).getDetails(), VoluntaryDto.VoluntaryRecord.class);
    }

    public R<Long> addVoluntary2(JSONObject model) { // 填报 // 前端+后台按需要剔除一些不需快照的信息（目前主要是院校信息）
        SysUser user = SecurityUtils.getLoginUser().getUser();

        AWishRecord wishRecord = getWishRecord(user.getUserId());
        List<VoluntaryDto.VoluntaryRecord> voluntaryRecordList = (null == wishRecord.getId()) ? Lists.newArrayList()
                : JSONArray.parseArray(wishRecord.getDetails(), VoluntaryDto.VoluntaryRecord.class);

        Long universityId = model.getLong("universityId");
        Long majorId = model.getLong("majorId");
        Optional<VoluntaryDto.VoluntaryRecord> optionalVoluntary = voluntaryRecordList.stream().filter(t -> t.getUniversityId().equals(universityId)).findFirst();
        VoluntaryDto.VoluntaryRecord voluntaryRecord;
        if(optionalVoluntary.isPresent()) {
            voluntaryRecord = optionalVoluntary.get();
            if(null == voluntaryRecord.getMajors()) {
                voluntaryRecord.setMajors(Lists.newArrayList());
            }
        } else {
            BBusiWishUniversities u = bBusiWishUniversitiesMapper.selectBBusiWishUniversitiesById(universityId);
            voluntaryRecord = new VoluntaryDto.VoluntaryRecord();
            voluntaryRecord.setUniversityLogo(u.getLogo());
            voluntaryRecord.setUniversityName(u.getName());
            voluntaryRecord.setUniversityId(u.getId());
            voluntaryRecord.setMajors(Lists.newArrayList());
            voluntaryRecord.setRank(voluntaryRecordList.size() + 1);
            voluntaryRecordList.add(voluntaryRecord);
        }
        Optional<VoluntaryDto.VoluntaryMajorRecord> optionalMajor = voluntaryRecord.getMajors().stream().filter( t -> t.getMajorId().equals(majorId)).findFirst();
        if(!optionalMajor.isPresent()) {
            AMarjorPlan p = aMarjorPlanMapper.selectAMarjorPlanById(majorId);
            VoluntaryDto.VoluntaryMajorRecord major = new VoluntaryDto.VoluntaryMajorRecord();
            major.setMajorId(p.getId());
            major.setMajorGroup(p.getMajorGroup());
            major.setMajorName(p.getMajorName());
            major.setMajorAncestors("");
            major.setRank(voluntaryRecord.getMajors().size() + 1);
            voluntaryRecord.getMajors().add(major);
        }

        wishRecord.setDetails(JSONArray.toJSONString(voluntaryRecordList));

        wishRecord.setYear(getPlanYear(user));
        if (null != wishRecord.getId()) {
            wishRecord.setUpdateTime(new Date());
            wishRecord.setUpdateTime(new Date());
            aWishRecordMapper.updateAWishRecord(wishRecord);
        } else {
            wishRecord.setCreateTime(new Date());
            wishRecord.setUserId(user.getUserId());
            wishRecord.setStatus(1);
            aWishRecordMapper.insertAWishRecord(wishRecord);
        }
        return R.ok(wishRecord.getId());
    }

    public Boolean removeVoluntaryByUniversity(Long userId, Long universityId) {
        AWishRecord wishRecord = getWishRecord(userId);
        List<VoluntaryDto.VoluntaryRecord> voluntaryRecordList = JSONArray.parseArray(wishRecord.getDetails(), VoluntaryDto.VoluntaryRecord.class);

        List<VoluntaryDto.VoluntaryRecord> newRecordList = Lists.newArrayList();
        for(VoluntaryDto.VoluntaryRecord r : voluntaryRecordList) {
            if(r.getUniversityId().equals(universityId)) {
                continue;
            }
            r.setRank(newRecordList.size());
            newRecordList.add(r);
        }
        updateWishRecord(wishRecord, newRecordList);
        return true;
    }

    public Boolean removeVoluntaryByMajor(Long userId, Long majorId) {
        AMarjorPlan plan = aMarjorPlanMapper.selectAMarjorPlanById(majorId);
        if(null == plan) {
            throw new RuntimeException("错误的专业计划ID");
        }
        AWishRecord wishRecord = getWishRecord(userId);
        List<VoluntaryDto.VoluntaryRecord> voluntaryRecordList = JSONArray.parseArray(wishRecord.getDetails(), VoluntaryDto.VoluntaryRecord.class);
        Optional<VoluntaryDto.VoluntaryRecord> optionalVoluntary = voluntaryRecordList.stream().filter(t -> t.getUniversityId().equals(plan.getUniversityId())).findFirst();
        if(!optionalVoluntary.isPresent()) {
            throw new RuntimeException("错误的院校ID");
        }
        VoluntaryDto.VoluntaryRecord voluntaryRecord = optionalVoluntary.get();
        List<VoluntaryDto.VoluntaryMajorRecord> newRecordList = Lists.newArrayList();
        for(VoluntaryDto.VoluntaryMajorRecord r : voluntaryRecord.getMajors()) {
            if(r.getMajorId().equals(majorId)) {
               continue;
            }
            r.setRank(newRecordList.size());
            newRecordList.add(r);
        }
        if(newRecordList.isEmpty()) {
            updateWishRecord(wishRecord, voluntaryRecordList.stream().filter(t -> !t.getUniversityId().equals(plan.getUniversityId())).collect(Collectors.toList()));
        } else {
            voluntaryRecord.setMajors(newRecordList);
            updateWishRecord(wishRecord, voluntaryRecordList);
        }
        return true;
    }

    public void sortVoluntaryByMajor(Long userId, Long universityId, Map<Long, Integer> majorRankMap) {
        AWishRecord wishRecord = getWishRecord(userId);
        List<VoluntaryDto.VoluntaryRecord> voluntaryRecordList = JSONArray.parseArray(wishRecord.getDetails(), VoluntaryDto.VoluntaryRecord.class);
        Optional<VoluntaryDto.VoluntaryRecord> optionalVoluntary = voluntaryRecordList.stream().filter(t -> t.getUniversityId().equals(universityId)).findFirst();
        VoluntaryDto.VoluntaryRecord voluntaryRecord;
        if(!optionalVoluntary.isPresent()) {
            throw new RuntimeException("错误的院校ID");
        }
        voluntaryRecord = optionalVoluntary.get();
        Integer rank;
        for(VoluntaryDto.VoluntaryMajorRecord record : voluntaryRecord.getMajors()) {
            if(null == (rank = majorRankMap.remove(record.getMajorId()))) {
                throw new RuntimeException("错误的专业计划ID");
            }
            record.setRank(rank);
        }
        Collections.sort(voluntaryRecord.getMajors(), new Comparator<VoluntaryDto.VoluntaryMajorRecord>() {
            @Override
            public int compare(VoluntaryDto.VoluntaryMajorRecord o1, VoluntaryDto.VoluntaryMajorRecord o2) {
                return o1.getRank().compareTo(o2.getRank());
            }
        });
        updateWishRecord(wishRecord, voluntaryRecordList);
    }

    public void sortVoluntaryByUniversity(Long userId, Map<Long, Integer> universityRankMap) {
        AWishRecord wishRecord = getWishRecord(userId);
        List<VoluntaryDto.VoluntaryRecord> voluntaryRecordList = JSONArray.parseArray(wishRecord.getDetails(), VoluntaryDto.VoluntaryRecord.class);
        Integer rank;
        for(VoluntaryDto.VoluntaryRecord record : voluntaryRecordList) {
            if(null == (rank = universityRankMap.remove(record.getUniversityId()))) {
                throw new RuntimeException("错误的院校ID");
            }
            record.setRank(rank);
        }
        Collections.sort(voluntaryRecordList, new Comparator<VoluntaryDto.VoluntaryRecord>() {
            @Override
            public int compare(VoluntaryDto.VoluntaryRecord o1, VoluntaryDto.VoluntaryRecord o2) {
                return o1.getRank().compareTo(o2.getRank());
            }
        });
        updateWishRecord(wishRecord, voluntaryRecordList);
    }

    private void updateWishRecord(AWishRecord wishRecord, List<VoluntaryDto.VoluntaryRecord> voluntaryRecordList) {
        wishRecord.setUpdateTime(new Date());
        wishRecord.setUpdateTime(new Date());
        wishRecord.setDetails(JSONArray.toJSONString(voluntaryRecordList));
        aWishRecordMapper.updateAWishRecord(wishRecord);
    }
    private AWishRecord getWishRecord(Long userId) {
        AWishRecord wrCond = new AWishRecord();
        wrCond.setUserId(userId);
        wrCond.setStatus(1);
        List<AWishRecord> wishList = aWishRecordMapper.selectAWishRecordList(wrCond);
        return wishList.isEmpty() ? new AWishRecord() : wishList.get(0);
    }
}
