package com.qmth.teachcloud.mark.service.impl;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.qmth.boot.api.exception.ApiException;
import com.qmth.teachcloud.common.contant.SystemConstant;
import com.qmth.teachcloud.common.entity.MarkQuestion;
import com.qmth.teachcloud.common.entity.SysUser;
import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
import com.qmth.teachcloud.common.enums.mark.*;
import com.qmth.teachcloud.common.service.SysUserService;
import com.qmth.teachcloud.common.service.TeachcloudCommonService;
import com.qmth.teachcloud.common.util.ServletUtil;
import com.qmth.teachcloud.mark.dto.mark.ScoreItem;
import com.qmth.teachcloud.mark.dto.mark.manage.Task;
import com.qmth.teachcloud.mark.dto.mark.mark.MarkGroupDto;
import com.qmth.teachcloud.mark.dto.mark.mark.MarkSettingDto;
import com.qmth.teachcloud.mark.dto.mark.mark.MarkStatusDto;
import com.qmth.teachcloud.mark.dto.mark.mark.SubmitResult;
import com.qmth.teachcloud.mark.entity.*;
import com.qmth.teachcloud.mark.enums.ExamType;
import com.qmth.teachcloud.mark.enums.LockType;
import com.qmth.teachcloud.mark.enums.MarkTaskStatus;
import com.qmth.teachcloud.mark.enums.ScorePolicy;
import com.qmth.teachcloud.mark.lock.LockService;
import com.qmth.teachcloud.mark.params.MarkHeaderGroupResult;
import com.qmth.teachcloud.mark.params.MarkResult;
import com.qmth.teachcloud.mark.params.MarkScoreList;
import com.qmth.teachcloud.mark.service.*;
import com.qmth.teachcloud.mark.utils.BigDecimalUtils;
import com.qmth.teachcloud.mark.utils.TaskLock;
import com.qmth.teachcloud.mark.utils.TaskLockUtil;

import io.lettuce.core.GeoArgs.Sort;

@Service
public class MarkServiceImpl implements MarkService {

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

    public static final int UN_SELECTIVE_SCORE = -1;

    private Map<Long, Long> markerLastUpdateTime = new ConcurrentHashMap<>();

    @Resource
    private MarkPaperService markPaperService;

    @Resource
    private MarkQuestionService markQuestionService;

    @Resource
    private MarkGroupService markGroupService;

    @Resource
    private MarkUserGroupService markUserGroupService;
    @Resource
    private MarkUserClassService markUserClassService;

    @Resource
    private MarkTaskService markTaskService;

    @Resource
    private MarkTrackService markTrackService;

    @Resource
    private MarkStudentService markStudentService;

    @Resource
    private MarkGroupStudentService markGroupStudentService;

    @Resource
    private MarkProblemHistoryService markProblemHistoryService;

    @Resource
    private MarkArbitrateHistoryService markArbitrateHistoryService;

    @Resource
    private MarkSubjectiveScoreService markSubjectiveScoreService;

    @Resource
    private MarkHeaderTrackService markHeaderTrackService;

    @Resource
    private MarkSpecialTagService markSpecialTagService;

    @Resource
    private MarkHeaderTagService markHeaderTagService;

    @Resource
    LockService lockService;

    @Resource
    ScanAnswerCardService scanAnswerCardService;

    @Resource
    TaskService taskService;

    @Resource
    TeachcloudCommonService teachcloudCommonService;
    @Resource
    private SysUserService sysUserService;

    /**
     * 释放某个评卷员已完成的评卷任务
     *
     * @param submitResult  评卷结果
     * @param markUserGroup 评卷员
     */
    @Override
    public void releaseTask(SubmitResult submitResult, MarkUserGroup markUserGroup) {
        if (submitResult.getMarkLibrary() != null) {
            TaskLock taskLock = TaskLockUtil.getFormalTask(getGroupKey(markUserGroup));
            taskLock.remove(submitResult.getMarkLibrary().getStudentId(), submitResult.getMarkLibrary().getTaskNumber(),
                    markUserGroup.getId());
            taskLock.refresh(markUserGroup.getId());
        }
    }

    /**
     * 某个评卷分组已申请的评卷任务数量
     *
     * @return
     */
    @Override
    public int applyCurrentCount(MarkGroup markGroup) {
        // todo 正在评卷数量查询 (评卷员和评卷分组分开2个方法)
        TaskLock taskLock = getTaskLock(markGroup);
        int count = 0;
        if (taskLock != null) {
            count = taskLock.count();
        }
        return count;
    }

    private TaskLock getTaskLock(MarkGroup markGroup) {
        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markGroup.getExamId(),
                markGroup.getPaperNumber());
        if (markPaper.getStatus() == MarkPaperStatus.FORMAL) {
            return TaskLockUtil.getFormalTask(getGroupKey(markGroup));
        }
        return null;
    }

    /**
     * 释放某个评卷员的所有锁定任务
     *
     * @param markUserGroup
     */
    @Override
    public void releaseByMarkUserGroup(MarkUserGroup markUserGroup) {
        TaskLock taskLock = TaskLockUtil.getFormalTask(getGroupKey(markUserGroup));
        taskLock.clear(markUserGroup.getId());
    }

    @Override
    public int applyCurrentCount(MarkGroup markGroup, Long markUserGroupId) {
        int count = 0;
        TaskLock taskLock = getTaskLock(markGroup);
        if (taskLock != null) {
            count = taskLock.count(markUserGroupId);
        }
        return count;
    }

    @Override
    public void resetMarker(MarkUserGroup markUserGroup) {
        Long examId = markUserGroup.getExamId();
        String paperNumber = markUserGroup.getPaperNumber();
        Integer groupNumber = markUserGroup.getGroupNumber();
        Long userId = markUserGroup.getUserId();
        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
        if (markPaper == null || markUserGroupService.getById(markUserGroup.getId()) == null) {
            return;
        }
        if (markPaper.getStatus() == MarkPaperStatus.FORMAL) {
            // 遍历相关评卷任务的模式
            List<MarkTaskStatus> statusList = Arrays.asList(MarkTaskStatus.ARBITRATED, MarkTaskStatus.WAIT_ARBITRATE,
                    MarkTaskStatus.PROBLEM);
            List<MarkTask> markTaskList = markTaskService
                    .listByExamIdAndPaperNumberAndGroupNumberAndUserIdAndStatusNotIn(examId, paperNumber, groupNumber,
                            userId, statusList);
            for (MarkTask markTask : markTaskList) {
                Long studentId = markTask.getStudentId();
                markTrackService.deleteByTaskId(markTask.getId());
                markSpecialTagService.deleteByTaskId(markTask.getId());
                markTaskService.resetById(markTask.getId(), null, null, null, MarkTaskStatus.WAITING);
                lockService.waitlock(LockType.STUDENT, markTask.getStudentId());
                updateStudentGroupStatus(studentId, examId, paperNumber, groupNumber, SubjectiveStatus.UNMARK);
                markStudentService.updateSubjectiveStatusAndScore(studentId, SubjectiveStatus.UNMARK, null, null);
                lockService.unlock(LockType.STUDENT, markTask.getStudentId());
            }
            markUserGroupService.resetById(markUserGroup.getId());
        }
        this.updateMarkedCount(examId, paperNumber, groupNumber);
        releaseByMarkUserGroup(markUserGroup);
    }

    @Override
    public void updateMarkedCount(Long examId, String paperNumber, Integer groupNumber) {
        List<MarkTaskStatus> markTaskStatuses = Arrays.asList(MarkTaskStatus.MARKED, MarkTaskStatus.ARBITRATED);
        int count = markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndStatusIn(examId, paperNumber,
                groupNumber, markTaskStatuses);
        markGroupService.updateMarkedCount(examId, paperNumber, groupNumber, count);
    }

    @Override
    public boolean rejectMarkTask(MarkProblemHistory markProblemHistory, MarkTask markTask, Long userId) {
        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markTask.getExamId(),
                markTask.getPaperNumber());
        if (markPaper.getStatus() == MarkPaperStatus.FINISH) {
            return false;
        }
        Long now = System.currentTimeMillis();
        if (markTaskService.resetById(markTask.getId(), null, userId, now, MarkTaskStatus.WAITING)) {
            markTrackService.deleteByTaskId(markTask.getId());
            resetStudentGroup(markTask.getStudentId(), markTask.getExamId(), markTask.getPaperNumber(),
                    markTask.getGroupNumber());
            markProblemHistoryService.resetByMarkProblemId(markProblemHistory.getId(), MarkProblemStatus.WAITING,
                    userId, MarkProblemStatus.BACK, now);
            updateMarkedCount(markTask.getExamId(), markTask.getPaperNumber(), markTask.getGroupNumber());
            return true;
        } else {
            return false;
        }
    }

    private void resetStudentGroup(Long studentId, Long examId, String paperNumber, Integer groupNumber) {
        updateStudentGroupStatus(studentId, examId, paperNumber, groupNumber, SubjectiveStatus.UNMARK);
        markStudentService.updateSubjectiveStatusAndScore(studentId, SubjectiveStatus.UNMARK, null, null);
    }

    private void updateStudentGroupStatus(Long studentId, Long examId, String paperNumber, Integer groupNumber,
                                          SubjectiveStatus status) {
        MarkGroupStudent gs = new MarkGroupStudent();
        gs.setStudentId(studentId);
        gs.setExamId(examId);
        gs.setPaperNumber(paperNumber);
        gs.setGroupNumber(groupNumber);
        gs.setStatus(status);
        markGroupStudentService.saveOrUpdateByMultiId(gs);
    }

    @Override
    public String getGroupKey(MarkGroup markGroup) {
        return markGroup.getExamId() + "_" + markGroup.getPaperNumber() + "_" + markGroup.getNumber();
    }

    @Override
    public void updateQuality(MarkUserGroup markUserGroup) {
        List<MarkTask> list = markTaskService.listByExamIdAndPaperNumberAndGroupNumberAndUserIdAndClassName(
                markUserGroup.getExamId(), markUserGroup.getPaperNumber(), markUserGroup.getGroupNumber(),
                markUserGroup.getUserId(), null);
        int finishCount = 0;
        int validCount = 0;
        int headerFinishCount = 0;
        double sumScore = 0;
        double sumScore2 = 0;
        double avgScore = 0;
        double stdevScore = 0;
        double sumSpent = 0;
        double avgSpent = 0;
        double maxScore = 0;
        double minScore = 0;
        for (MarkTask markTask : list) {
            if (!markTask.getStatus().equals(MarkTaskStatus.PROBLEM)) {
                finishCount++;
            }
            if (markTask.getStatus() == MarkTaskStatus.MARKED && markTask.getHeaderScore() == null) {
                validCount++;
            }
            // 管理员打分数量（主观题检查，仲裁）
            if ((markTask.getStatus() == MarkTaskStatus.MARKED || markTask.getStatus() == MarkTaskStatus.ARBITRATED) && markTask.getHeaderScore() != null) {
                headerFinishCount++;
            }
            double score = markTask.getMarkerScore() != null && markTask.getMarkerScore() != UN_SELECTIVE_SCORE
                    ? markTask.getMarkerScore()
                    : 0;
            int spent = markTask.getMarkerSpent() != null ? markTask.getMarkerSpent() : 0;

            sumScore += score;
            sumScore2 += Math.pow(score, 2);
            sumSpent += spent;
            if (finishCount > 0) {
                avgScore = sumScore / finishCount;
                avgSpent = sumSpent / finishCount;
                // 递归法计算标准差
                stdevScore = Math.sqrt(sumScore2 / finishCount - Math.pow(sumScore / finishCount, 2));
            }

            // 最高分
            if (score > maxScore) {
                maxScore = score;
            }
            // 最低分
            if (finishCount == 1) {
                minScore = score;
            } else if (score < minScore) {
                minScore = score;
            }
        }
        markUserGroupService.updateQualityById(markUserGroup.getId(), finishCount, headerFinishCount, validCount,
                avgSpent / 1000, avgScore, stdevScore, maxScore, minScore);
        markerLastUpdateTime.put(markUserGroup.getId(), System.currentTimeMillis());
    }

    @Override
    public boolean needUpdateQuality(MarkUserGroup markUserGroup, int expireMinutes) {
        if (markUserGroup == null) {
            return false;
        }
        Long lastUpdateTime = markerLastUpdateTime.get(markUserGroup.getId());
        MarkTask markTask = markTaskService.getLastOneByUserIdAndStatus(markUserGroup.getExamId(),
                markUserGroup.getPaperNumber(), markUserGroup.getGroupNumber(), markUserGroup.getUserId(),
                MarkTaskStatus.MARKED);
        if (markTask != null && markTask.getMarkerTime() != null) {
            long lastMarkTime = markTask.getMarkerTime();
            if (lastUpdateTime != null && lastUpdateTime > lastMarkTime) {
                return false;
            } else {
                return (System.currentTimeMillis() - lastMarkTime) < (expireMinutes * 60 * 1000);
            }
        }
        return false;
    }

    /**
     * 管理员/组长处理仲裁卷
     *
     * @param markResult
     * @param userId
     */
    @Transactional
    @Override
    public void processArbitrate(MarkResult markResult, Long userId) {
        MarkArbitrateHistory markArbitrateHistory = markArbitrateHistoryService.getById(markResult.getTaskId());
        markArbitrateHistory.setUpdateUserId(userId);
        markArbitrateHistory
                .setTotalScore(markResult.isUnselective() ? UN_SELECTIVE_SCORE : markResult.getMarkerScore());
        markArbitrateHistory.setScoreList(markResult.isUnselective() ? null : markResult.getScoreList());
        markArbitrateHistory.setStatus(MarkArbitrateStatus.MARKED);
        markArbitrateHistory.setUpdateTime(System.currentTimeMillis());
        // 保存阅卷轨迹
        // int unansweredCount = 0;
        if (markResult.getTrackList() != null && !markResult.isUnselective()) {
            markHeaderTrackService.deleteByExamIdAndPaperNumberAndGroupNumberAndStudentId(
                    markArbitrateHistory.getExamId(), markArbitrateHistory.getPaperNumber(),
                    markArbitrateHistory.getGroupNumber(), markArbitrateHistory.getStudentId());
            List<MarkHeaderTrack> tracks = markResult.getTrackList(markArbitrateHistory);
            markHeaderTrackService.saveOrUpdateBatchByMultiId(tracks);
        }
        // 保存特殊标记
        if (markResult.getSpecialTagList() != null) {
            markHeaderTagService.deleteByStudentIdAndGroupNumber(markArbitrateHistory.getStudentId(),
                    markArbitrateHistory.getGroupNumber());
            markHeaderTagService.saveBatch(markResult.getHeaderTagList(markArbitrateHistory));
        }
        markArbitrateHistoryService.saveOrUpdate(markArbitrateHistory);
        markTaskService.updateHeaderResult(markArbitrateHistory.getExamId(), markArbitrateHistory.getPaperNumber(),
                markArbitrateHistory.getGroupNumber(), markArbitrateHistory.getStudentId(),
                markArbitrateHistory.getUpdateUserId(), markArbitrateHistory.getTotalScore(),
                markArbitrateHistory.getScoreList(), markArbitrateHistory.getUpdateTime(), MarkTaskStatus.ARBITRATED);
        updateMarkedCount(markArbitrateHistory.getExamId(), markArbitrateHistory.getPaperNumber(),
                markArbitrateHistory.getGroupNumber());
        checkStudentGroup(markArbitrateHistory.getStudentId(),
                markGroupService.getByExamIdAndPaperNumberAndGroupNumber(markArbitrateHistory.getExamId(),
                        markArbitrateHistory.getPaperNumber(), markArbitrateHistory.getGroupNumber()));
        // 评卷质量重新统计
        List<MarkUserGroup> markUserGroups = markUserGroupService.listByExamIdAndPaperNumberAndGroupNumber(markArbitrateHistory.getExamId(), markArbitrateHistory.getPaperNumber(), markArbitrateHistory.getGroupNumber());
        markUserGroups.forEach(m -> this.updateQuality(m));
    }

    private String getGroupKey(MarkUserGroup markUserGroup) {
        return markUserGroup.getExamId() + "_" + markUserGroup.getPaperNumber() + "_" + markUserGroup.getGroupNumber();
    }

    /**
     * 考生分组判断是否评卷完成，以及后续的统一处理动作
     *
     * @param studentId
     * @param group
     */
    private void checkStudentGroup(Long studentId, MarkGroup group) {
        if (calculateGroup(group, studentId)) {
            updateStudentGroupStatus(studentId, group.getExamId(), group.getPaperNumber(), group.getNumber(),
                    SubjectiveStatus.MARKED);
            updateStudentGroupScore(studentId, group.getExamId(), group.getPaperNumber(), group, group.getMarkScore(),
                    group.getMarkScoreDetail());
            // 未分组的题目
            long unGroupQuestionCount = markQuestionService.countByExamIdAndPaperNumberAndObjectiveAndGroupNumberIsNull(
                    group.getExamId(), group.getPaperNumber(), false);
            long groupCount = CollectionUtils
                    .size(markGroupService.listGroupByExamIdAndPaperNumber(group.getExamId(), group.getPaperNumber()));
            checkStudentSubjective(studentId, groupCount, unGroupQuestionCount);
        } else {
            updateStudentGroupStatus(studentId, group.getExamId(), group.getPaperNumber(), group.getNumber(),
                    SubjectiveStatus.UNMARK);
            markStudentService.updateSubjectiveStatusAndScore(studentId, SubjectiveStatus.UNMARK, null, null);
        }
    }

    @Override
    public void checkStudentSubjective(Long studentId, long groupCount, long unGroupQuestionCount) {
        if (markGroupStudentService.countByStudentIdAndStatus(studentId, SubjectiveStatus.MARKED) == groupCount
                && unGroupQuestionCount == 0) {
            scoreCalculate(studentId);
        } else {
            markStudentService.updateSubjectiveStatusAndScore(studentId, SubjectiveStatus.UNMARK, null, null);
        }
    }

    @Override
    public void buildMarkTask(MarkPaper markPaper) {
        try {
            lockService.watch(LockType.EXAM_SUBJECT, markPaper.getExamId(), markPaper.getPaperNumber());
            log.info("start create library for examId=" + markPaper.getExamId() + ", paperNumber="
                    + markPaper.getPaperNumber());
            // 清除缺考考生和违纪考生
            List<MarkStudent> markStudentList = markStudentService
                    .listAbsentOrBreachMarkTaskStudent(markPaper.getExamId(), markPaper.getPaperNumber());
            if (CollectionUtils.isNotEmpty(markStudentList)) {
                for (MarkStudent student : markStudentList) {
                    try {
                        lockService.waitlock(LockType.STUDENT, student.getId());
                        log.info("delete library for studentId=" + student.getId());
                        this.deleteMarkTaskByStudent(student);
                    } catch (Exception e) {
                        log.error("delete student library error", e);
                    } finally {
                        lockService.unlock(LockType.STUDENT, student.getId());
                    }
                }
            }
            // 处理正常考生
            List<MarkGroup> groups = markGroupService.listGroupByExamIdAndPaperNumber(markPaper.getExamId(),
                    markPaper.getPaperNumber());
            for (MarkGroup group : groups) {
                // 生成正评任务
                buildFormalLibrary(group);
            }
        } finally {
            lockService.unwatch(LockType.EXAM_SUBJECT, markPaper.getExamId(), markPaper.getPaperNumber());
        }
    }

    private void buildFormalLibrary(MarkGroup group) {
        int pageSize = 100;
        try {
            lockService.watch(LockType.GROUP, group.getExamId(), group.getPaperNumber(), group.getNumber());
            // 上锁后重复验证分组状态
            MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(group.getExamId(), group.getPaperNumber());
            if (MarkPaperStatus.FINISH.equals(markPaper.getStatus())) {
                return;
            }
            int count = 0;
            List<MarkStudent> studentList = markStudentService.listUnMarkTaskStudent(group.getExamId(),
                    group.getPaperNumber(), group.getNumber(), pageSize);
            while (CollectionUtils.isNotEmpty(studentList)) {
                // 已生成的双评任务总数的第一组任务数
                int doubleMarkTaskCount1 = markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndTaskNumber(
                        group.getExamId(), group.getPaperNumber(), group.getNumber(), 1);
                // 已生成的双评任务总数的第二组任务数
                int doubleMarkTaskCount2 = markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndTaskNumber(
                        group.getExamId(), group.getPaperNumber(), group.getNumber(), 2);
                List<MarkTask> taskList = new ArrayList<>();
                int doubleCount = 0;
                int studentCount = studentList.size();
                for (MarkStudent student : studentList) {
                    MarkTask markTask = new MarkTask();
                    markTask.setId(SystemConstant.getDbUuid());
                    markTask.setExamId(student.getExamId());
                    markTask.setCourseCode(student.getCourseCode());
                    markTask.setCourseName(student.getCourseName());
                    markTask.setPaperNumber(student.getPaperNumber());
                    markTask.setGroupNumber(group.getNumber());
                    markTask.setStudentId(student.getId());
                    markTask.setStudentCode(student.getStudentCode());
                    markTask.setSecretNumber(student.getSecretNumber());
                    markTask.setTaskNumber(1);
                    markTask.setStatus(MarkTaskStatus.WAITING);
                    taskList.add(markTask);
                    // 开启双评时需要判断是否生成第二份评卷任务
                    if (group.getDoubleEnable() != null && group.getDoubleEnable()) {
                        boolean needDouble;
                        if (group.getDoubleRate() == 100) {
                            needDouble = true;
                        } else {
                            double libraryCount = taskList.size();
                            int expectCount = (int) ((doubleMarkTaskCount1 + studentCount) * group.getDoubleRate()
                                    / 100);
                            // 随机数判断加入当前已经生成双评任务的比例加权
                            // 实际双评任务数小于理论生成数 &&（剩余未生成双评的考生数量小于剩余应生成的数量）
                            needDouble = (doubleMarkTaskCount2 + doubleCount) < expectCount
                                    && ((studentCount - libraryCount + doubleCount) <= (expectCount
                                    - doubleMarkTaskCount2 - doubleCount));
                        }
                        if (needDouble) {
                            markTask = new MarkTask();
                            markTask.setId(SystemConstant.getDbUuid());
                            markTask.setExamId(student.getExamId());
                            markTask.setCourseCode(student.getCourseCode());
                            markTask.setCourseName(student.getCourseName());
                            markTask.setPaperNumber(student.getPaperNumber());
                            markTask.setGroupNumber(group.getNumber());
                            markTask.setStudentId(student.getId());
                            markTask.setStudentCode(student.getStudentCode());
                            markTask.setSecretNumber(student.getSecretNumber());
                            markTask.setTaskNumber(2);
                            markTask.setStatus(MarkTaskStatus.WAITING);
                            taskList.add(markTask);
                            doubleCount++;
                        }
                    }
                }
                // 有新任务创建
                if (CollectionUtils.isNotEmpty(taskList)) {
                    count += taskList.size();
                    // 任务乱序
                    Collections.shuffle(taskList);
                    // 批量保存
                    markTaskService.saveBatch(taskList);
                    // 正评状态才需要更新任务数量
                    if (MarkPaperStatus.FORMAL.equals(markPaper.getStatus())) {
                        this.updateMarkTaskCount(group.getExamId(), group.getPaperNumber(), group.getNumber());
                        this.updateMarkedCount(group.getExamId(), group.getPaperNumber(), group.getNumber());
                    }
                }
                studentList = markStudentService.listUnMarkTaskStudent(group.getExamId(), group.getPaperNumber(),
                        group.getNumber(), pageSize);
            }
            if (count != 0) {
                log.info("finish create " + count + " markTask for examId=" + group.getExamId() + ", paperNumber="
                        + group.getPaperNumber() + ", groupNumber=" + group.getNumber());
            }
        } catch (Exception e) {
            log.error("build formal markTask error for examId=" + group.getExamId() + ", paperNumber="
                    + group.getPaperNumber() + ", groupNumber=" + group.getNumber(), e);
        } finally {
            lockService.unwatch(LockType.GROUP, group.getExamId(), group.getPaperNumber(), group.getNumber());
        }
    }

    @Transactional
    @Override
    public void deleteMarkTaskByStudent(MarkStudent student) {
        markStudentService.updateSubjectiveStatusAndScore(student.getId(), SubjectiveStatus.UNMARK, null, null);
        // 正评相关数据
        markTrackService.deleteByStudentId(student.getId());
        markSpecialTagService.deleteByStudentId(student.getId());
        markHeaderTagService.deleteByStudentIdAndGroupNumber(student.getId(), null);
        markHeaderTrackService.deleteByStudentId(student.getId());
        markArbitrateHistoryService.deleteByStudentId(student.getId());
        markProblemHistoryService.deleteByStudentId(student.getId());
        markTaskService.deleteByStudentId(student.getId());
        // 主观状态与得分明细
        markGroupStudentService.deleteByStudentId(student.getId());
        markSubjectiveScoreService.deleteByStudentId(student.getId());
        // selectiveStudentDao.deleteByStudentId(student.getId());
        updateGroupAllCount(student.getExamId(), student.getPaperNumber());
        // 复核记录
        // inspectedService.clearByStudent(student.getId());
        // 打回记录
        // rejectHistoryDao.deleteByStudentId(student.getId());
    }

    @Transactional
    @Override
    public void updateGroupAllCount(Long examId, String paperNumber) {
        List<MarkGroup> groups = markGroupService.listGroupByExamIdAndPaperNumber(examId, paperNumber);
        for (MarkGroup group : groups) {
            updateMarkedCount(group.getExamId(), group.getPaperNumber(), group.getNumber());
            updateMarkTaskCount(group.getExamId(), group.getPaperNumber(), group.getNumber());
        }
    }

    private void updateMarkTaskCount(Long examId, String paperNumber, Integer groupNumber) {
        int count = markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndUserId(examId, paperNumber, groupNumber,
                null);
        markGroupService.updateTaskCount(examId, paperNumber, groupNumber, count);
    }

    private boolean calculateGroup(MarkGroup group, Long studentId) {
        double score = 0;
        List<ScoreItem> detail = new ArrayList<>();
        int count = 0;
        // 未设置算分策略的情况下，默认取平均分
        ScorePolicy policy = group.getScorePolicy() != null ? group.getScorePolicy() : ScorePolicy.AVG;
        List<MarkTask> list = markTaskService.listByExamIdAndPaperNumberAndGroupNumberAndStudentId(group.getExamId(),
                group.getPaperNumber(), group.getNumber(), studentId);
        if (list.isEmpty()) {
            return false;
        }
        boolean selectiveAll = false;
        for (MarkTask markTask : list) {
            if (markTask.getStatus() != MarkTaskStatus.MARKED && markTask.getStatus() != MarkTaskStatus.ARBITRATED) {
                // 有非完成状态的评卷任务，直接返回
                return false;
            }
            double markerScore = markTask.getStatus() == MarkTaskStatus.ARBITRATED ? markTask.getHeaderScore()
                    : markTask.getMarkerScore();
            if (markerScore == UN_SELECTIVE_SCORE) {
                selectiveAll = true;
            }
        }
        if (selectiveAll) {
            group.setMarkScore(UN_SELECTIVE_SCORE);
            return true;
        }
        for (MarkTask markTask : list) {
            count++;
            Double current = markTask.getHeaderScore() != null ? markTask.getHeaderScore() : markTask.getMarkerScore();
            List<ScoreItem> scores = markTask.getScoreList();
            if (count == 1) {
                // 首份评卷任务，直接取总分与明细
                score = current;
                detail = scores;
            } else {
                switch (policy) {
                    case AVG:
                        // 直接累加
                        score = BigDecimalUtils.add(score, current);
                        for (int i = 0; i < detail.size(); i++) {
                            try {
                                ScoreItem item = detail.get(i);
                                ScoreItem other = scores.get(i);
                                item.setScore(BigDecimalUtils.add(item.getScore(), other.getScore()));
                            } catch (Exception e) {
                                continue;
                            }
                        }
                        break;
                    case MAX:
                        // 高分优先
                        if (current > score) {
                            score = current;
                            detail = scores;
                        }
                        break;
                    case MIN:
                        // 低分优先
                        if (current < score) {
                            score = current;
                            detail = scores;
                        }
                        break;
                    default:
                        break;
                }
            }
        }
        // 取平均分策略下，累计分数需要重新计算一次
        if (policy == ScorePolicy.AVG && count > 1) {
            score = BigDecimalUtils.div(score, count);
            for (ScoreItem item : detail) {
                item.setScore(BigDecimalUtils.div(item.getScore(), count));
            }
        }
        group.setMarkScore(score);
        group.setMarkScoreDetail(detail);
        return true;
    }

    private void updateStudentGroupScore(Long studentId, Long examId, String paperNumber, MarkGroup group, double score,
                                         List<ScoreItem> scoreList) {
        // scoreDao.deleteByStudentIdAndGroupNumber(studentId, groupNumber);
        List<MarkQuestion> questions = markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(examId,
                paperNumber, group.getNumber(), false);
        List<MarkSubjectiveScore> subjectiveScores = markSubjectiveScoreService.listByStudentIdAndGroupNumber(studentId,
                group.getNumber());
        for (int i = 0; i < questions.size(); i++) {
            MarkQuestion question = questions.get(i);
            MarkSubjectiveScore ss;
            if (!subjectiveScores.isEmpty() && subjectiveScores.size() == questions.size()) {
                ss = subjectiveScores.get(i);
            } else {
                ss = new MarkSubjectiveScore();
            }
            ss.setStudentId(studentId);
            ss.setExamId(examId);
            ss.setPaperNumber(paperNumber);
            ss.setGroupNumber(group.getNumber());
            ss.setGroupScore(score);
            ss.setMainNumber(question.getMainNumber());
            ss.setSubNumber(question.getSubNumber());
            if (score != UN_SELECTIVE_SCORE) {
                ScoreItem item = scoreList.get(i);
                ss.setScore(item.getScore());
                ss.setMainScore(0.0);
                ss.setUncalculate(false);
            } else {
                ss.setScore(score);
                ss.setMainScore(score);
                ss.setUncalculate(true);
            }
            // if (group.getArbitrateThreshold() != null && group.getArbitrateThreshold() >
            // 0) {
            // ss.setUnansweredCount(markHeaderTrackService.countByStudentIdAndQuestionNumberAndUnanswered(studentId,
            // question.getQuestionNumber(), true));
            // } else {
            // ss.setUnansweredCount(markTaskService.countByStudentIdAndQuestionNumberAndUnanswered(studentId,
            // question.getQuestionNumber(), true));
            // }
            markSubjectiveScoreService.saveOrUpdateByMultiId(ss);
        }
    }

    private void scoreCalculate(Long studentId) {
        List<ScoreItem> scoreList = new ArrayList<>();
        Map<Integer, List<MarkSubjectiveScore>> mainScoreMap = new HashMap<>();
        Map<Integer, Double> scoreMap = new HashMap<>();

        // 循环所有主观得分明细
        List<MarkSubjectiveScore> list = markSubjectiveScoreService.listByStudentIdAndGroupNumber(studentId, null);
        // list.sort(null);
        for (MarkSubjectiveScore ss : list) {
            List<MarkSubjectiveScore> mainScoreList = mainScoreMap.get(ss.getMainNumber());
            if (mainScoreList == null) {
                mainScoreList = new ArrayList<MarkSubjectiveScore>();
            }
            mainScoreList.add(ss);
            mainScoreMap.put(ss.getMainNumber(), mainScoreList);
            scoreList.add(new ScoreItem(ss));
        }
        // 计算大题分
        for (Integer mainNumber : mainScoreMap.keySet()) {
            List<MarkSubjectiveScore> mainScoreList = mainScoreMap.get(mainNumber);
            BigDecimal mainScore = BigDecimal.ZERO;
            for (MarkSubjectiveScore subjectiveScore : mainScoreList) {
                if (subjectiveScore.getScore() != UN_SELECTIVE_SCORE) {
                    mainScore = mainScore.add(BigDecimal.valueOf(subjectiveScore.getScore()));
                    subjectiveScore.setUncalculate(false);
                } else {
                    mainScore = new BigDecimal(UN_SELECTIVE_SCORE);
                    subjectiveScore.setUncalculate(true);
                    break;
                }
            }
            for (MarkSubjectiveScore subjectiveScore : mainScoreList) {
                subjectiveScore.setMainScore(mainScore.doubleValue());
                markSubjectiveScoreService.saveOrUpdateByMultiId(subjectiveScore);
            }
            scoreMap.put(mainNumber, mainScore.doubleValue());
        }
        // 计算选做题分数
        BigDecimal totalScore = BigDecimal.ZERO;
        // 计算非选做题总分
        for (Integer mainNumber : scoreMap.keySet()) {
            totalScore = totalScore.add(BigDecimal.valueOf(scoreMap.get(mainNumber)));
        }
        // 全部评完，更新考生主观题得分
        markStudentService.updateSubjectiveStatusAndScore(studentId, SubjectiveStatus.MARKED, totalScore.doubleValue(),
                MarkStudent.buildScoreList(scoreList));
    }

    @Override
    public MarkSettingDto getSetting(SysUser user, Long examId, String paperNumber, Integer groupNumber) {
        MarkUserGroup markUserGroup = markUserGroupService.getByExamIdAndPaperNumberAndGroupNumberAndUserId(examId,
                paperNumber, groupNumber, user.getId());
        user = sysUserService.getById(user.getId());
        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);

        MarkSettingDto dto = new MarkSettingDto();
        dto.setExamType(ExamType.SCAN_IMAGE);
        dto.setMode(markUserGroup != null && markUserGroup.getMode() != null ? markUserGroup.getMode()
                : MarkMode.UNLIMITED.equals(markPaper.getMarkMode()) ? MarkMode.TRACK : markPaper.getMarkMode());
        dto.setForceMode(markPaper.getForceMode());
        dto.setSheetView(markPaper.getSheetView());
        dto.setSheetConfig(markPaper.getSheetConfig());
        dto.setEnableAllZore(false);
        dto.setFileServer(null);
        dto.setUserName(user.getRealName());
        dto.getSubject().setAnswerUrl(StringUtils.isBlank(markPaper.getAnswerFilePath()) ? null
                : teachcloudCommonService.filePreview(markPaper.getAnswerFilePath()));
        dto.getSubject().setPaperUrl(StringUtils.isBlank(markPaper.getPaperFilePath()) ? null
                : teachcloudCommonService.filePreview(markPaper.getPaperFilePath()));
        dto.getSubject().setCode(markPaper.getPaperNumber());
        dto.getSubject().setName(markPaper.getCourseName());
        dto.setForceSpecialTag(false);
        dto.setUiSetting(user.getUiSetting());
        dto.setStatusValue(markPaper.getStatus());
        dto.setProblemTypes(MarkProblemType.listTypes());
        dto.setGroupNumber(groupNumber);
        dto.setTopCount(markUserGroup != null && markUserGroup.getTopCount() != null ? markUserGroup.getTopCount() : 0);
        dto.setSplitConfig(new Double[0]);
        dto.setPrefetchCount(3);
        dto.setStartTime(markPaper.getMarkStartTime());
        dto.setEndTime(markPaper.getMarkEndTime());
        dto.setSelective(false);
        dto.setAutoScroll(markPaper.getAutoScroll());
        dto.setEnableSplit(false);
        dto.setShowObjectScore(markPaper.getShowObjectScore());
        return dto;
    }

    @Override
    public MarkStatusDto getStatus(Long userId, Long examId, String paperNumber, Integer groupNumber) {
        MarkStatusDto dto = new MarkStatusDto();
        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
        List<String> classNames = null;
        if (markPaper != null && markPaper.getOpenMarkClass() != null && markPaper.getOpenMarkClass().booleanValue()) {
            List<MarkUserClass> markUserClassList = markUserClassService.listByExamIdAndPaperNumberAndUserId(examId, paperNumber, userId);
            if (CollectionUtils.isNotEmpty(markUserClassList)) {
                classNames = markUserClassList.stream().map(MarkUserClass::getClassName).collect(Collectors.toList());
            }
        }
        dto.setArbitrateCount(markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndUserIdAndAndClassNameStatusIn(examId,
                paperNumber, groupNumber, null, classNames, MarkTaskStatus.WAIT_ARBITRATE));
        dto.setTotalCount(markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndUserIdAndAndClassNameStatusIn(examId, paperNumber, groupNumber, null, classNames));
        dto.setMarkedCount(markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndUserIdAndAndClassNameStatusIn(examId, paperNumber, groupNumber, null, classNames, MarkTaskStatus.MARKED, MarkTaskStatus.ARBITRATED));
        dto.setPersonCount(markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndUserIdAndAndClassNameStatusIn(examId,
                paperNumber, groupNumber, userId, classNames, MarkTaskStatus.MARKED, MarkTaskStatus.ARBITRATED,
                MarkTaskStatus.WAIT_ARBITRATE));
        dto.setProblemCount(markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndUserIdAndAndClassNameStatusIn(examId,
                paperNumber, groupNumber, null, classNames, MarkTaskStatus.PROBLEM));
        return dto;
    }

    @Override
    public List<MarkGroupDto> getGroup(Long userId, Long examId, String paperNumber) {
        List<MarkUserGroup> markUserGroups = markUserGroupService.listByExamIdAndPaperNumberAndUserId(examId,
                paperNumber, userId);
        List<MarkGroupDto> list = new ArrayList<MarkGroupDto>();
        for (MarkUserGroup markUserGroup : markUserGroups) {
            MarkGroup markGroup = markGroupService.getByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber,
                    markUserGroup.getGroupNumber());
            markGroup.setQuestionList(markQuestionService.listQuestionByExamIdAndPaperNumberAndGroupNumber(examId,
                    paperNumber, markUserGroup.getGroupNumber(), false));
            MarkGroupDto dto = new MarkGroupDto();
            dto.setGroupNumber(markUserGroup.getGroupNumber());
            dto.setTotalCount(markTaskService.countByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber,
                    markUserGroup.getGroupNumber()));
            dto.setMarkedCount(markTaskService.countByExamIdAndPaperNumberAndGroupNumberAndStatusIn(examId, paperNumber,
                    markUserGroup.getGroupNumber(), Arrays.asList(MarkTaskStatus.MARKED, MarkTaskStatus.ARBITRATED)));
            dto.setGroupQuestions(markGroup.getGroupQuestions());
            dto.setTitle(markGroup.getTitle());
            list.add(dto);
        }
        return list;
    }

    @Override
    public void clear(Long userId, Long examId, String paperNumber, Integer groupNumber) {
        String key = examId + "_" + paperNumber + "_" + groupNumber;
        TaskLock taskLock = TaskLockUtil.getFormalTask(key);
        MarkUserGroup markUserGroup = markUserGroupService.getByExamIdAndPaperNumberAndGroupNumberAndUserId(examId,
                paperNumber, groupNumber, userId);
        taskLock.clear(markUserGroup.getId());
    }

    @Override
    public void deleteMarkGroup(MarkGroup markGroup, boolean deleteGroupInfo) {
        // 正评相关数据
        markTrackService.deleteByExamIdAndPaperNumberAndGroupNumber(markGroup.getExamId(), markGroup.getPaperNumber(),
                markGroup.getNumber());
        markSpecialTagService.deleteByExamIdAndPaperNumberAndGroupNumber(markGroup.getExamId(),
                markGroup.getPaperNumber(), markGroup.getNumber());
        markHeaderTrackService.deleteByExamIdAndPaperNumberAndGroupNumberAndStudentId(markGroup.getExamId(),
                markGroup.getPaperNumber(), markGroup.getNumber(), null);
        markHeaderTagService.deleteByExamIdAndPaperNumberAndGroupNumber(markGroup.getExamId(),
                markGroup.getPaperNumber(), markGroup.getNumber());
        markArbitrateHistoryService.deleteByExamIdAndPaperNumberAndGroupNumber(markGroup.getExamId(),
                markGroup.getPaperNumber(), markGroup.getNumber());
        markProblemHistoryService.deleteByExamIdAndPaperNumberAndGroupNumber(markGroup.getExamId(),
                markGroup.getPaperNumber(), markGroup.getNumber());
        markTaskService.deleteByExamIdAndPaperNumberAndGroupNumber(markGroup.getExamId(), markGroup.getPaperNumber(),
                markGroup.getNumber());
        // 考生分组状态与得分明细
        markGroupStudentService.deleteByExamIdAndPaperNumberAndGroupNumber(markGroup.getExamId(),
                markGroup.getPaperNumber(), markGroup.getNumber());
        markSubjectiveScoreService.deleteByExamIdAndPaperNumberAndGroupNumber(markGroup.getExamId(),
                markGroup.getPaperNumber(), markGroup.getNumber());
        // 删除分组
        releaseByMarkGroup(markGroup);
        // 删除分组时，同步删除评卷员、分组等信息
        if (deleteGroupInfo) {
            // 评卷员数据
            markUserGroupService.deleteByExamIdAndPaperNumberAndGroupNumber(markGroup.getExamId(),
                    markGroup.getPaperNumber(), markGroup.getNumber());
            // 小题数据
            markQuestionService.updateGroupNumberByExamIdAndPaperNumberAndGroupNumber(null, markGroup.getExamId(),
                    markGroup.getPaperNumber(), markGroup.getNumber());
            markGroupService.deleteByExamIdAndPaperNumberAndGroupNumber(markGroup.getExamId(),
                    markGroup.getPaperNumber(), markGroup.getNumber());
            // 分班阅数据
            markUserClassService.deleteByExamIdAndPaperNumberAndClassName(markGroup.getExamId(), markGroup.getPaperNumber(), null);
            // 更新MarkPaper中groupStatus
            updateMarkGroupStatus(markGroup.getExamId(), markGroup.getPaperNumber());
        }

        // 未分组的题目
        long unGroupQuestionCount = markQuestionService.countByExamIdAndPaperNumberAndObjectiveAndGroupNumberIsNull(
                markGroup.getExamId(), markGroup.getPaperNumber(), false);
        // 考生整体状态与总分更新
        long groupCount = markGroupService.countByExamIdAndPaperNumber(markGroup.getExamId(),
                markGroup.getPaperNumber());
        if (groupCount == 0 || unGroupQuestionCount > 0) {
            markStudentService.updateSubjectiveStatusAndScore(markGroup.getExamId(), markGroup.getPaperNumber(),
                    SubjectiveStatus.UNMARK, null, null);
        } else {
            List<Long> studentList = markStudentService.findIdByExamIdAndPaperNumber(markGroup.getExamId(), markGroup.getPaperNumber());
            for (Long studentId : studentList) {
                checkStudentSubjective(studentId, groupCount, unGroupQuestionCount);
            }
        }
    }

    @Override
    public void releaseByMarkGroup(MarkGroup markGroup) {
        TaskLock taskLock = getTaskLock(markGroup);
        taskLock.clear();
    }

    @Override
    public void releaseByStudent(MarkStudent student) {
        TaskLock taskLock = TaskLockUtil.getInspectedStudentTask(getKey(student));
        taskLock.remove(student.getId(), 1);
    }

    @Override
    public boolean applyStudent(MarkStudent student, Long userId) {
        TaskLock taskLock = TaskLockUtil.getInspectedStudentTask(getKey(student));
        boolean lock = taskLock.add(student.getId(), 1, userId);
        // 上锁失败直接返回
        if (!lock) {
            return false;
        }
        // 重复校验任务状态
        if (student.getSubjectiveStatus().equals(SubjectiveStatus.MARKED)) {
            return true;
        } else {
            taskLock.remove(student.getId(), 1, userId);
            return false;
        }
    }

    @Override
    public void submitHeaderTask(List<MarkHeaderGroupResult> markHeaderGroupResultList, MarkStudent markStudent) {
        Long userId = ServletUtil.getRequestUserId();
        for (MarkHeaderGroupResult groupResult : markHeaderGroupResultList) {
            try {
                lockService.watch(LockType.GROUP, markStudent.getExamId(), markStudent.getPaperNumber(),
                        groupResult.getGroupNumber());
                Long currentTime = System.currentTimeMillis();
                if (groupResult.getTrackList() != null) {
                    for (MarkScoreList scoreList : groupResult.getMarkerScore()) {
                        String questionNumber = scoreList.getMainNumber() + "." + scoreList.getSubNumber();
                        markHeaderTrackService.deleteByExamIdAndPaperNumberAndGroupNumberAndStudentIdAndQuestionNumber(
                                markStudent.getExamId(), markStudent.getPaperNumber(), groupResult.getGroupNumber(),
                                groupResult.getStudentId(), questionNumber);
                    }
                    List<MarkHeaderTrack> tracks = groupResult.getTrackList(groupResult, markStudent, userId);
                    markHeaderTrackService.saveOrUpdateBatchByMultiId(tracks);
                }
                if (groupResult.getHeaderTagList(groupResult, userId) != null) {
                    markHeaderTagService.deleteByExamIdAndPaperNumberAndGroupNumberAndStudentId(markStudent.getExamId(),
                            markStudent.getPaperNumber(), groupResult.getGroupNumber(), groupResult.getStudentId());
                    List<MarkHeaderTag> tags = groupResult.getHeaderTagList(groupResult, userId);
                    markHeaderTagService.saveOrUpdateBatch(tags);
                }
                updateMarkSubjectScore(markStudent, groupResult);
                markTaskService.updateHeaderResult(markStudent.getExamId(), markStudent.getPaperNumber(),
                        groupResult.getGroupNumber(), groupResult.getStudentId(), userId, groupResult.getGroupScore(),
                        groupResult.getScoreList(), currentTime, MarkTaskStatus.MARKED);
                updateMarkedCount(markStudent.getExamId(), markStudent.getPaperNumber(), groupResult.getGroupNumber());
                markStudentService.updateCheckInfo(markStudent.getId(), userId);
                checkStudentGroup(groupResult.getStudentId(), markGroupService.getByExamIdAndPaperNumberAndGroupNumber(
                        markStudent.getExamId(), markStudent.getPaperNumber(), groupResult.getGroupNumber()));
            } catch (ApiException e) {
                throw ExceptionResultEnum.ERROR.exception(e.getMessage());
            } finally {
                lockService.unwatch(LockType.GROUP, markStudent.getExamId(), markStudent.getPaperNumber(),
                        groupResult.getGroupNumber());
            }
        }

        // 未分组的题目
        long unGroupQuestionCount = markQuestionService.countByExamIdAndPaperNumberAndObjectiveAndGroupNumberIsNull(
                markStudent.getExamId(), markStudent.getPaperNumber(), false);
        long groupCount = CollectionUtils.size(markGroupService.listGroupByExamIdAndPaperNumber(markStudent.getExamId(),
                markStudent.getPaperNumber()));
        checkStudentSubjective(markStudent.getId(), groupCount, unGroupQuestionCount);
    }

    private void updateMarkSubjectScore(MarkStudent markStudent, MarkHeaderGroupResult groupResult) {
        List<MarkSubjectiveScore> markSubjectiveScoreList = markSubjectiveScoreService
                .listByStudentIdAndGroupNumber(markStudent.getId(), groupResult.getGroupNumber());
        markSubjectiveScoreList.sort(Comparator.comparing(MarkSubjectiveScore::getMainNumber)
                .thenComparingInt(MarkSubjectiveScore::getSubNumber));
        List<Double> scoreList = new ArrayList<>();
        for (MarkSubjectiveScore markSubjectiveScore : markSubjectiveScoreList) {
            List<MarkScoreList> markScoreLists = groupResult.getMarkerScore();
            Optional<MarkScoreList> optional = markScoreLists.stream()
                    .filter(m -> m.getMainNumber().equals(markSubjectiveScore.getMainNumber())
                            && m.getSubNumber().equals(markSubjectiveScore.getSubNumber()))
                    .findFirst();
            if (optional.isPresent()) {
                markSubjectiveScore.setScore(optional.get().getScore());
            }
            scoreList.add(markSubjectiveScore.getScore());
        }
        groupResult.setGroupScore(scoreList.stream().collect(Collectors.summingDouble(x -> x)));
        groupResult.setScoreList(scoreList.toArray(new Double[]{}));
        markSubjectiveScoreService.saveOrUpdateBatchByMultiId(markSubjectiveScoreList);
    }

    private String getKey(MarkStudent student) {
        return student.getExamId() + "_" + student.getPaperNumber();
    }

    @Override
    public IPage<Task> getHistory(Long userId, int pageNumber, int pageSize, Sort sort, String order, Long examId,
                                  String paperNumber, Integer groupNumber, String secretNumber, Double markerScore) {
        if (!"marker_time".equals(order) && !"marker_score".equals(order)) {
            order = "marker_time";
        }
        MarkGroup group = markGroupService.getByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, groupNumber);
        if (group == null) {
            throw ExceptionResultEnum.ERROR.exception("评卷大题不存在");
        }
        Page<MarkTask> page = new Page<>(pageNumber, pageSize);
        OrderItem orderItem = new OrderItem(order, sort.equals(Sort.asc));
        page.addOrder(orderItem);
        IPage<MarkTask> list = markTaskService.listPageHistory(page, userId, examId, paperNumber, groupNumber,
                secretNumber, markerScore);
        List<Task> recordsDtos = new ArrayList<Task>();
        for (MarkTask task : list.getRecords()) {
            Task dto = taskService.build(task);
            dto.setPrevious(true);
            recordsDtos.add(dto);
        }
        IPage<Task> result = new Page<Task>();
        result.setCurrent(list.getCurrent());
        result.setPages(list.getPages());
        result.setRecords(recordsDtos);
        result.setSize(list.getSize());
        result.setTotal(list.getTotal());
        return result;
    }

    @Override
    public Task getTask(Long userId, Long examId, String paperNumber, Integer groupNumber) {
        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(examId, paperNumber);
        if (markPaper == null) {
            throw ExceptionResultEnum.ERROR.exception("评卷试卷不存在");
        }
        MarkGroup group = markGroupService.getByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, groupNumber);
        if (group == null) {
            throw ExceptionResultEnum.ERROR.exception("评卷大题不存在");
        }
        MarkUserGroup markUserGroup = markUserGroupService.getByExamIdAndPaperNumberAndGroupNumberAndUserId(examId,
                paperNumber, groupNumber, userId);
        Task task = null;
        List<MarkTask> list;
        int pageNumber = 1;
        while (task == null) {
            if (markPaper.getOpenMarkClass()) {
                list = markTaskService.findUnMarkedFilterClass(new Page<>(pageNumber, 20), examId, paperNumber,
                        groupNumber, userId);
            } else {
                list = markTaskService.findUnMarked(new Page<>(pageNumber, 20), examId, paperNumber, groupNumber,
                        userId);
            }
            if (list.isEmpty()) {
                break;
            }
            for (MarkTask t : list) {
                if (this.applyTask(t, markUserGroup.getId())) {
                    task = taskService.build(t);
                    break;
                }
            }
            if (task == null) {
                pageNumber++;
            }
        }
        return task;
    }

    @Override
    public boolean applyTask(MarkTask t, Long userId) {
        // 查询待领取任务时，已经做了多评同一studentId互斥处理
        String key = t.getExamId() + "_" + t.getPaperNumber() + "_" + t.getGroupNumber();
        TaskLock taskLock = TaskLockUtil.getFormalTask(key);
        boolean lock = taskLock.add(t.getStudentId(), t.getTaskNumber(), userId);
        // 上锁失败直接返回
        if (!lock) {
            return false;
        }
        // 重复校验任务状态
        if (markTaskService.countByIdAndStatus(t.getId(), t.getStatus()) == 1) {
            return true;
        } else {
            taskLock.remove(t.getStudentId(), t.getTaskNumber(), userId);
            return false;
        }
    }

    @Override
    public boolean hasApplied(MarkTask t, Long userId) {
        String key = t.getExamId() + "_" + t.getPaperNumber() + "_" + t.getGroupNumber();
        TaskLock taskLock = TaskLockUtil.getFormalTask(key);
        return taskLock.exist(t.getStudentId(), t.getTaskNumber(), userId);
    }

    @Override
    public void deleteInitMarkData(Long examId, String paperNumber) {
        markPaperService.deleteByExamIdAndPaperNumber(examId, paperNumber, false);
        markStudentService.deleteByExamIdAndPaperNumber(examId, paperNumber);
        scanAnswerCardService.deleteByExamIdAndPaperNumber(examId, paperNumber);
        markQuestionService.deleteByExamIdAndPaperNumber(examId, paperNumber);
    }

    @Override
    public void calcObjectiveScore(MarkPaper markPaper) {
        int pageNumber = 1;
        int pageSize = 1000;
        IPage<MarkStudent> iPage = markStudentService.pageByExamAndPaperNumber(markPaper.getExamId(),
                markPaper.getPaperNumber(), pageNumber, pageSize);
        while (CollectionUtils.isNotEmpty(iPage.getRecords())) {
            for (MarkStudent student : iPage.getRecords()) {
                calculate(student);
            }
            pageNumber++;
            iPage = markStudentService.pageByExamAndPaperNumber(markPaper.getExamId(), markPaper.getPaperNumber(),
                    pageNumber, pageSize);
        }
    }

    private void calculate(MarkStudent student) {
        // 未上传、缺考、违纪的考生不统分
        if (!student.getUpload() || student.getAbsent() || student.getBreach()) {
            return;
        }
        try {
            // 更新客观题得分
            markStudentService.calculateObjectiveScore(student);
            // 增加主观题总分统计
            // markService.scoreCalculate(student,
            // findMarkGroup(student.getSubjectCode()));
        } catch (Exception e) {
            log.error("calculate error for studentId=" + student.getId(), e);
        }
    }

    @Override
    public SubmitResult saveTask(Long examId, String paperNumber, Integer groupNumber, Long userId, MarkResult result) {
        MarkUserGroup markUserGroup = markUserGroupService.getByExamIdAndPaperNumberAndGroupNumberAndUserId(examId,
                paperNumber, groupNumber, userId);
        if (markUserGroup == null) {
            throw ExceptionResultEnum.ERROR.exception("评卷员未绑定该评卷任务");
        }
        MarkGroup group = markGroupService.getByExamIdAndPaperNumberAndGroupNumber(markUserGroup.getExamId(),
                markUserGroup.getPaperNumber(), markUserGroup.getGroupNumber());
        if (group == null) {
            throw ExceptionResultEnum.ERROR.exception("评卷分组不存在");
        }
        SubmitResult sr = null;
        try {
            lockService.watch(LockType.EXAM_SUBJECT, markUserGroup.getExamId(), markUserGroup.getPaperNumber());
            lockService.watch(LockType.GROUP, markUserGroup.getExamId(), markUserGroup.getPaperNumber(),
                    markUserGroup.getGroupNumber());
            lockService.watch(LockType.MARK_USER_GROUP, markUserGroup.getId());
            lockService.waitlock(LockType.STUDENT, result.getStudentId());
            sr = submitResult(markUserGroup, group, userId, result);
            if (sr.isSuccess()) {
                releaseTask(sr, markUserGroup);
            }
        } catch (Exception e) {
            log.error("save task error", e);
        } finally {
            lockService.unlock(LockType.STUDENT, result.getStudentId());
            lockService.unwatch(LockType.MARK_USER_GROUP, markUserGroup.getId());
            lockService.unwatch(LockType.GROUP, markUserGroup.getExamId(), markUserGroup.getPaperNumber(),
                    markUserGroup.getGroupNumber());
            lockService.unwatch(LockType.EXAM_SUBJECT, markUserGroup.getExamId(), markUserGroup.getPaperNumber());
        }

        if (sr == null || !sr.isSuccess()) {
            throw ExceptionResultEnum.ERROR.exception("评卷任务提交失败，请刷新页面");
        }
        return sr;
    }

    private SubmitResult submitResult(MarkUserGroup markUserGroup, MarkGroup group, Long userId, MarkResult result) {

        MarkTask task = markTaskService.getById(result.getTaskId());
        if (task != null && task.getExamId().equals(group.getExamId())
                && task.getPaperNumber().equals(group.getPaperNumber())
                && task.getGroupNumber().equals(group.getNumber())) {
            // 问题卷
            Long now = System.currentTimeMillis();
            if (result.isProblem()) {
                // 状态更新
                if (markTaskService.updateProblemResult(task.getId(), userId, now, result.getSpent())) {
                    saveProblemHistory(result, task, userId);
                    updateMarkedCount(markUserGroup.getExamId(), markUserGroup.getPaperNumber(),
                            markUserGroup.getGroupNumber());
                    // 未评完
                    resetStudentGroup(task.getStudentId(), task.getExamId(), task.getPaperNumber(),
                            task.getGroupNumber());
                    markTrackService.deleteByTaskId(task.getId());
                    markSpecialTagService.deleteByTaskId(task.getId());
                    return SubmitResult.success(task);
                }
            }
            if (result.getMarkerScore() <= group.getTotalScore()) {
                if (submitTask(task, userId, group, result)) {
                    updateMarkedCount(markUserGroup.getExamId(), markUserGroup.getPaperNumber(),
                            markUserGroup.getGroupNumber());
                    return SubmitResult.success(task);
                }
            }
        }
        return SubmitResult.faile();
    }

    private void saveProblemHistory(MarkResult result, MarkTask task, Long userId) {
        MarkProblemHistory history = markProblemHistoryService.findByTaskIdAndStatus(task.getId(),
                MarkProblemStatus.WAITING);
        if (history == null) {
            history = new MarkProblemHistory();
        }
        history.setId(SystemConstant.getDbUuid());
        history.setExamId(task.getExamId());
        history.setSecretNumber(task.getSecretNumber());
        history.setStudentCode(task.getStudentCode());
        history.setStudentId(task.getStudentId());
        history.setTaskId(task.getId());
        history.setPaperNumber(task.getPaperNumber());
        history.setGroupNumber(task.getGroupNumber());
        history.setType(result.getProblemType());
        if (MarkProblemType.OTHER.equals(result.getProblemType())) {
            history.setRemark(result.getProblemRemark());
        }
        history.setCreateTime(System.currentTimeMillis());
        history.setStatus(MarkProblemStatus.WAITING);
        history.setUserId(userId);
        markProblemHistoryService.save(history);
    }

    private boolean submitTask(MarkTask task, Long userId, MarkGroup group, MarkResult result) {
        MarkUserGroup markUserGroup = markUserGroupService.getByExamIdAndPaperNumberAndGroupNumberAndUserId(
                group.getExamId(), group.getPaperNumber(), group.getNumber(), userId);
        // 非本人领取的待评任务
        if ((task.getStatus() == MarkTaskStatus.WAITING || task.getStatus() == MarkTaskStatus.REJECTED)
                && !hasApplied(task, markUserGroup.getId())) {
            return false;
        }
        // 非本人的回评任务
        if ((task.getStatus() == MarkTaskStatus.MARKED) && !task.getUserId().equals(userId)) {
            return false;
        }
        // 是否多评情况下已处理过该考生评卷任务
        if (markTaskService.countByStudentIdAndMarkerIdAndIdNotEqual(task.getStudentId(), task.getExamId(),
                task.getPaperNumber(), task.getGroupNumber(), userId, task.getId()) > 0) {
            return false;
        }
        // 未选做
        // if (result.isUnselective() && group.isSelective()) {
        // result.setMarkerScore(UN_SELECTIVE_SCORE);
        // result.setScoreList(new Double[0]);
        // trackDao.deleteByLibraryId(library.getId());
        // specialTagDao.deleteByLibraryId(library.getId());
        // }
        // 尝试提交评卷结果
        Long now = System.currentTimeMillis();
        if (!markTaskService.updateMarkerResult(task.getId(), MarkTaskStatus.MARKED, userId, result.getMarkerScore(),
                result.getScoreList(), now, result.getSpent(), null, null, null, MarkTaskStatus.WAITING,
                MarkTaskStatus.MARKED, MarkTaskStatus.REJECTED)) {
            // 条件不符更新失败，直接返回
            return false;
        }
        // 保存阅卷轨迹
        // int unansweredCount = 0;
        if (result.getTrackList() != null && !result.isUnselective()) {
            markTrackService.deleteByTaskId(task.getId());
            markTrackService.saveBatch(result.getTrackList(task, userId));
            // for (MarkTrack markTrack : tracks) {
            // if (markTrack.isUnanswered()) {
            // unansweredCount++;
            // }
            // }
            // libraryDao.updateUnansweredCount(library.getId(), unansweredCount);
        }
        // 保存特殊标记
        if (result.getSpecialTagList() != null && !result.isUnselective()) {
            markSpecialTagService.deleteByTaskId(task.getId());
            markSpecialTagService.saveBatch(result.getSpecialTagList(task));
        }
        // 判断多评模式下是否需要仲裁
        MarkArbitrateHistory history = null;
        if (group.getArbitrateThreshold() != null && group.getArbitrateThreshold() > 0) {
            // 多评模式
            List<MarkTask> list = markTaskService.findByStudentIdAndGroupNumberAndStatus(task.getStudentId(),
                    task.getGroupNumber(), MarkTaskStatus.MARKED);
            for (MarkTask other : list) {
                // 本评卷任务或组长已打分，则跳过该任务
                if (other.getId().equals(task.getId()) || other.getHeaderScore() != null) {
                    continue;
                }
                // 均为未选做，跳过该任务
                // if (other.getMarkerScore() == UN_SELECTIVE_SCORE && result.getMarkerScore()
                // == UN_SELECTIVE_SCORE) {
                // continue;
                // }
                // 其中一个有分另一个未选做 直接进入仲裁
                // if ((other.getMarkerScore() != UN_SELECTIVE_SCORE && result.getMarkerScore()
                // == UN_SELECTIVE_SCORE)
                // || (other.getMarkerScore() == UN_SELECTIVE_SCORE && result.getMarkerScore()
                // != UN_SELECTIVE_SCORE)) {
                // history = buildArbitrateHistory(library, now);
                // break;
                // }
                // 分差超过阀值
                if (Math.abs(other.getMarkerScore() - result.getMarkerScore()) > group.getArbitrateThreshold()) {
                    // 开启三评
                    // if (group.getThirdPolicy().equals(ThirdPolicy.LOW_DIFF_HIGH_AVG)) {
                    // if (libraryDao.countByStudentIdAndGroupNumber(library.getStudentId(),
                    // library.getGroupNumber()) == 2) {
                    // buildThirdLibrary(library, group);
                    // } else {
                    // // 两两比较，触发仲裁
                    // List<MarkLibrary> libraries = libraryDao.findByStudentIdAndGroupNumber(
                    // library.getStudentId(), library.getGroupNumber());
                    // history = buildArbitrateHistory(libraries, group.getArbitrateThreshold(),
                    // now);
                    // }
                    // } else {
                    // 未开启三评，触发仲裁
                    history = buildArbitrateHistory(task, now);
                    break;
                    // }
                }
            }
        }
        if (history != null) {
            // 保存仲裁记录
            markArbitrateHistoryService.save(history);
            // 触发仲裁后续状态更新
            markTaskService.updateStatusByStudentIdAndGroupNumber(task.getStudentId(), task.getGroupNumber(),
                    MarkTaskStatus.WAIT_ARBITRATE);
            // 未评完
            resetStudentGroup(task.getStudentId(), task.getExamId(), task.getPaperNumber(), task.getGroupNumber());
        } else {
            // 判断当前分组是否已完成评卷
            checkStudentGroup(task.getStudentId(), group);
        }
        return true;
    }

    private MarkArbitrateHistory buildArbitrateHistory(MarkTask task, Long now) {
        MarkArbitrateHistory history = new MarkArbitrateHistory();
        history.setId(SystemConstant.getDbUuid());
        history.setExamId(task.getExamId());
        history.setPaperNumber(task.getPaperNumber());
        history.setGroupNumber(task.getGroupNumber());
        history.setStudentId(task.getStudentId());
        history.setStudentCode(task.getStudentCode());
        history.setSecretNumber(task.getSecretNumber());
        history.setStatus(MarkArbitrateStatus.WAITING);
        history.setCreateTime(now);
        return history;
    }

    @Override
    public void updateMarkGroupStatus(Long examId, String paperNumber) {
        // 更新MarkPaper中groupStatus
        long groupNumberIsNull = markQuestionService.countByExamIdAndPaperNumberAndObjectiveAndGroupNumberIsNull(examId,
                paperNumber, false);
        markPaperService.updateGroupStatusByExamIdAndPaperNumber(groupNumberIsNull == 0, examId, paperNumber);
    }

}
