package com.qmth.themis.business.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qmth.themis.business.bean.backend.*;
import com.qmth.themis.business.cache.ExamRecordCacheUtil;
import com.qmth.themis.business.cache.RedisKeyHelper;
import com.qmth.themis.business.cache.bean.*;
import com.qmth.themis.business.config.SystemConfig;
import com.qmth.themis.business.constant.SpringContextHolder;
import com.qmth.themis.business.constant.SystemConstant;
import com.qmth.themis.business.dao.TOeExamRecordMapper;
import com.qmth.themis.business.dto.MqDto;
import com.qmth.themis.business.dto.response.TEExamUnFinishDto;
import com.qmth.themis.business.entity.TOeExamAnswer;
import com.qmth.themis.business.entity.TOeExamRecord;
import com.qmth.themis.business.enums.*;
import com.qmth.themis.business.service.*;
import com.qmth.themis.business.util.OssUtil;
import com.qmth.themis.business.util.RedisUtil;
import com.qmth.themis.common.contanst.Constants;
import com.qmth.themis.common.util.FileUtil;
import com.qmth.themis.common.util.SimpleBeanUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.File;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @Description: 考试记录 服务实现类
 * @Param:
 * @return:
 * @Author: wangliang
 * @Date: 2020/6/25
 */
@Service
public class TOeExamRecordServiceImpl extends ServiceImpl<TOeExamRecordMapper, TOeExamRecord> implements TOeExamRecordService {
    private final static Logger log = LoggerFactory.getLogger(TOeExamRecordServiceImpl.class);

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");

    @Resource
    MqDtoService mqDtoService;

    @Resource
    TOeExamRecordMapper tOeExamRecordMapper;

    @Resource
    RedisUtil redisUtil;

    @Resource
    TEExamPaperService examPaperService;

    @Resource
    TEExamService examService;

    @Resource
    TOeExamAnswerService examAnswerService;

    @Resource
    TEExamActivityService examActivityService;

    /**
     * 获取考试未完列表
     *
     * @param studentId
     * @param examId
     * @param orgId
     * @return
     */
    @Override
    public Map getUnFinishExam(Long studentId, Long examId, Long orgId) {
        TEExamUnFinishDto teExamUnFinishDto = tOeExamRecordMapper.getUnFinishExam(studentId, examId, orgId);
        if (Objects.nonNull(teExamUnFinishDto)) {
            Map finalMap = new HashMap();
            Map<String, Object> waitingMap = new HashMap();
            waitingMap.put("id", teExamUnFinishDto.getId());
            waitingMap.put("examActivityId", teExamUnFinishDto.getExamActivityId());
            waitingMap.put("name", teExamUnFinishDto.getName());
            waitingMap.put("mode", teExamUnFinishDto.getMode());
            waitingMap.put("preNotice", teExamUnFinishDto.getPreNotice());
            waitingMap.put("preNoticeStaySeconds", teExamUnFinishDto.getPreNoticeStaySeconds());
            waitingMap.put("postNotice", teExamUnFinishDto.getPostNotice());
            finalMap.put("waiting", waitingMap);

            Map<String, Object> activityMap = new HashMap();
            activityMap.put("recordId", teExamUnFinishDto.getRecordId());
            activityMap.put("minDurationSeconds", teExamUnFinishDto.getMinDurationSeconds());
            activityMap.put("cameraPhotoUpload", teExamUnFinishDto.getCameraPhotoUpload());
            activityMap.put("courseCode", teExamUnFinishDto.getCourseCode());
            activityMap.put("examStudentId", teExamUnFinishDto.getExamStudentId());
            activityMap.put("inProcessLivenessVerify", teExamUnFinishDto.getInProcessLivenessVerify());
            activityMap.put("inProcessFaceStrangerIgnore", teExamUnFinishDto.getInProcessFaceStrangerIgnore());
            activityMap.put("inProcessFaceVerify", teExamUnFinishDto.getInProcessFaceVerify());
            activityMap.put("courseName", teExamUnFinishDto.getCourseName());
            activityMap.put("inProcessLivenessFixedRange", teExamUnFinishDto.getInProcessLivenessFixedRange());
            activityMap.put("prepareSeconds", teExamUnFinishDto.getPrepareSeconds());
            activityMap.put("fouceFinish", teExamUnFinishDto.getFouceFinish());
            activityMap.put("entryAuthenticationPolicy", teExamUnFinishDto.getEntryAuthenticationPolicy());
            activityMap.put("maxDurationSeconds", teExamUnFinishDto.getMaxDurationSeconds());
            activityMap.put("examCount", teExamUnFinishDto.getExamCount());
            activityMap.put("inProcessLivenessJudgePolicy", teExamUnFinishDto.getInProcessLivenessJudgePolicy());
            activityMap.put("id", teExamUnFinishDto.getExamActivityId());

            finalMap.put("activity", activityMap);
            return finalMap;
        } else {
            return null;
        }
    }

    @Transactional
    @Override
    public Long saveByPrepare(Long examId, Long examActivityId, Long examStudentId, Long paperId,
                              Integer serialNumber) {
        ExamActivityCacheBean ac = examActivityService.getExamActivityCacheBean(examActivityId);
        ExamCacheBean exam = examService.getExamCacheBean(examId);
        TOeExamRecord er = new TOeExamRecord();
        er.setId(Constants.idGen.next());
        er.setExamId(examId);
        er.setExamActivityId(examActivityId);
        er.setExamStudentId(examStudentId);
        er.setPaperId(paperId);
        er.setSerialNumber(serialNumber);
        er.setFirstPrepareTime(System.currentTimeMillis());
        er.setStatus(ExamRecordStatusEnum.FIRST_PREPARE);
        er.setObjectiveScore(0.0);
        er.setMonitorKey(String.valueOf(redisUtil.getRedisSequence()));
        er.setAlreadyBreakCount(0);
        er.setStartTime(ac.getStartTime());
        er.setEndTime(ac.getFinishTime());
        er.setOpeningSeconds(ac.getOpeningSeconds());
        er.setMinDurationSeconds(exam.getMinDurationSeconds());
        er.setMaxDurationSeconds(ac.getMaxDurationSeconds());
        er.setForceFinish(exam.getForceFinish());
        Map<String, Object> map = SimpleBeanUtil.objectToMap(er);
        redisUtil.setForHash(RedisKeyHelper.examRecordCacheKey(er.getId()), map);
        dataInitMq(map);
        return er.getId();
    }

    /**
     * 计算客观分
     */
    @Override
    public void calculateObjectiveScore(Map<String, Object> param) {
        Long recordId = (Long) param.get("recordId");
        Integer mainNumber = (Integer) param.get("mainNumber");
        Integer subNumber = (Integer) param.get("subNumber");
        Integer subIndex = (Integer) param.get("subIndex");
        Long paperId = ExamRecordCacheUtil.getPaperId(recordId);
        Map<String, ObjectiveAnswerCacheBean> map = examPaperService.getObjectiveAnswerCacheBean(paperId);
        if (map == null || map.size() == 0) {
            log.error("no ObjectiveAnswerCacheBean for calculateObjectiveScore");
            return;
        }
        String key = RedisKeyHelper.examAnswerHashKey(mainNumber, subNumber, subIndex);
        ObjectiveAnswerCacheBean cb = map.get(key);
        if (cb == null) {
            return;
        }
        ExamStudentAnswerCacheBean answer = (ExamStudentAnswerCacheBean) redisUtil.get(
                RedisKeyHelper.examAnswerKey(recordId),
                RedisKeyHelper.examAnswerHashKey(mainNumber, subNumber, subIndex));
        if (answer == null) {
            log.error("no ExamStudentAnswerCacheBean for calculateObjectiveScore");
            return;
        }

        if (cb.getStructType().intValue() == 1) {
            if (checkSingleChoice(answer.getAnswer(), cb.getChoiceAnswer())) {
                answer.setScore(cb.getScore());
            } else {
                answer.setScore(0.0);
            }
        }
        if (cb.getStructType().intValue() == 2) {
            ExamCacheBean ec = examService.getExamCacheBean(ExamRecordCacheUtil.getExamId(recordId));
            int r = checkMultipleChoice(answer.getAnswer(), cb.getChoiceAnswer());
            if (ec.getObjectiveScorePolicy().equals(ObjectiveScorePolicyEnum.EQUAL)) {// 全对给分
                if (r == 1) {
                    answer.setScore(cb.getScore());
                } else {
                    answer.setScore(0.0);
                }
            }
            if (ec.getObjectiveScorePolicy().equals(ObjectiveScorePolicyEnum.PARTIAL)) {// 漏选半分
                if (r == 1) {
                    answer.setScore(cb.getScore());
                } else if (r == 0) {
                    BigDecimal b = new BigDecimal(cb.getScore()).divide(new BigDecimal("2"), 1, BigDecimal.ROUND_UP);
                    answer.setScore(b.doubleValue());
                } else {
                    answer.setScore(0.0);
                }
            }
        }
        if (cb.getStructType().intValue() == 3) {
            if (Boolean.parseBoolean(answer.getAnswer()) == cb.getBoolAnswer().booleanValue()) {
                answer.setScore(cb.getScore());
            } else {
                answer.setScore(0.0);
            }
        }

        if (answer.getScore() == null) {
            log.debug("小题分数为空，得分置为0，" + "recordId:" + recordId + " questionKey:" + key);
            answer.setScore(0.0);
        }
        // 更新分数
        redisUtil.set(RedisKeyHelper.examAnswerKey(recordId),
                RedisKeyHelper.examAnswerHashKey(mainNumber, subNumber, subIndex), answer);

        // 计算客观分总分
        calculateTotalObjectiveScore(recordId, map);
    }

    @SuppressWarnings("unchecked")
    private void calculateTotalObjectiveScore(Long recordId, Map<String, ObjectiveAnswerCacheBean> map) {
        String lockKey = SystemConstant.REDIS_LOCK_TOTAL_OBJECTIVE_SCORE_PREFIX + recordId;
        try {
            Boolean lock = redisUtil.lock(lockKey, SystemConstant.REDIS_CACHE_TIME_OUT);
            if (lock) {
                Map<String, ExamStudentAnswerCacheBean> as = redisUtil.getHashEntries(RedisKeyHelper.examAnswerKey(recordId));
                if (as != null && as.size() > 0) {
                    Double total = 0.0;
                    for (ExamStudentAnswerCacheBean sa : as.values()) {
                        if (sa.getObjective() != null && sa.getObjective()) {//是客观题
                            if (sa.getScore() != null) {//有分值
                                total = total + sa.getScore();
                            } else {
                                total = null;
                                break;
                            }
                        }
                    }
                    if (total != null) {
                        ExamRecordCacheUtil.setObjectiveScore(recordId, total, true);
                    }
                }
            }
        } finally {
            redisUtil.releaseLock(lockKey);
        }
    }

    private boolean checkSingleChoice(String answerArray, JSONArray ar) {
        JSONArray an = JSONArray.parseArray(answerArray);
        if (an.size() == 0) {
            return false;
        }
        int a = an.getIntValue(0);
        return checkSingleChoice(a, ar);
    }

    private boolean checkSingleChoice(int answer, JSONArray ar) {
        for (int i = 0; i < ar.size(); i++) {
            if (answer == ar.getIntValue(i)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param answer
     * @param ar
     * @return -1 有错选项，0-无措选项 未答全 1-全对
     */
    private int checkMultipleChoice(String answer, JSONArray ar) {
        int yes = 0;
        int no = 0;
        JSONArray an = JSONArray.parseArray(answer);
        for (int i = 0; i < an.size(); i++) {
            if (checkSingleChoice(an.getIntValue(i), ar)) {
                yes++;
                continue;
            } else {
                no++;
            }
        }
        if (yes == ar.size()) {
            return 1;
        }
        if (yes != 0 && no == 0) {
            return 0;
        }
        return -1;
    }

    /**
     * 考试记录数据持久化
     */
    @SuppressWarnings("unchecked")
    @Transactional
    @Override
    public void saveDataByCache(Long recordId) {
        String tempDir = SystemConstant.TEMP_FILES_DIR;
        String dir = tempDir + "/" + uuid() + "/";
        File dfile = new File(dir);
        try {
            dfile.mkdirs();
            String structFilePath = sdf.format(new Date()) + "/" + uuid() + ".json";
            ExamStudentPaperStructCacheBean struct = (ExamStudentPaperStructCacheBean) redisUtil.get(RedisKeyHelper.studentPaperStructKey(recordId));

            //保存考试记录
            Map<String, Object> record = redisUtil.getHashEntries(RedisKeyHelper.examRecordCacheKey(recordId));
            TOeExamRecord er = SimpleBeanUtil.mapToObject(record, TOeExamRecord.class);
            er.setStatus(ExamRecordStatusEnum.PERSISTED);
            if (struct != null) {
                er.setPaperStructPath(structFilePath);
            }
            er.setPaperStructUpload(1);
            saveOrUpdate(er);
            //保存作答
            Map<String, ExamStudentAnswerCacheBean> answerMap = redisUtil.getHashEntries(RedisKeyHelper.examAnswerKey(recordId));
            for (ExamStudentAnswerCacheBean answerCache : answerMap.values()) {
                TOeExamAnswer answer = new TOeExamAnswer();
                BeanUtils.copyProperties(answerCache, answer);
                answer.setId(Constants.idGen.next());
                answer.setExamRecordId(recordId);
                examAnswerService.saveOrUpdate(answer);
            }
//            //更新考生信息
//            ExamStudentCacheBean examStudentCache = examStudentService.getExamStudentCacheBean(er.getExamStudentId());
//            TEExamStudent examStudent = new TEExamStudent();
//            BeanUtils.copyProperties(examStudentCache, examStudent);
//            examStudentService.saveOrUpdate(examStudent);
            //上传个人试卷结构
            if (struct != null) {
                File structFile = new File(dir + "struct.json");
                FileUtil.saveAsFile(structFile.getAbsolutePath(), struct.getContent());
                SystemConfig systemConfig = SpringContextHolder.getBean(SystemConfig.class);
                OssUtil.ossUpload(systemConfig.getOssEnv(3), structFilePath, structFile);
            }
        } finally {
            FileUtil.deleteFolder(dir);
        }
    }

    private String uuid() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }


    @Override
    public void saveFaceVerify(ExamTypeEnum type, Long recordId, Long entryAuthenticationId, VerifyExceptionEnum entryAuthenticationResult) {
        if (ExamTypeEnum.FIRST_START.equals(type)) {
            ExamRecordCacheUtil.setEntryAuthenticationId(recordId, entryAuthenticationId, false);
            ExamRecordCacheUtil.setEntryAuthenticationResult(recordId, entryAuthenticationResult, false);
            String[] columns = new String[]{ExamRecordFieldEnum.entry_authentication_id.name(), ExamRecordFieldEnum.entry_authentication_result.name()};
            Object[] values = new Object[]{entryAuthenticationId, entryAuthenticationResult};
            this.dataUpdatesMq(recordId, columns, values);
        } else if (ExamTypeEnum.IN_PROCESS.equals(type)) {

        }
    }

    @Override
    public void saveLivenessVerify(LivenessTypeEnum type, Long recordId, Long entryAuthenticationId, VerifyExceptionEnum entryAuthenticationResult) {
        if (LivenessTypeEnum.FIRST_START.equals(type)) {
            ExamRecordCacheUtil.setEntryAuthenticationId(recordId, entryAuthenticationId, false);
            ExamRecordCacheUtil.setEntryAuthenticationResult(recordId, entryAuthenticationResult, false);
            String[] columns = new String[]{ExamRecordFieldEnum.entry_authentication_id.name(), ExamRecordFieldEnum.entry_authentication_result.name()};
            Object[] values = new Object[]{entryAuthenticationId, entryAuthenticationResult};
            this.dataUpdatesMq(recordId, columns, values);
        } else if (LivenessTypeEnum.IN_PROCESS.equals(type)) {
            Integer count = ExamRecordCacheUtil.getInProcessLivenessVerifyCount(recordId);
            ExamRecordCacheUtil.setInProcessLivenessVerifyCount(recordId, (count == null ? 0 : count + 1), true);
        }
    }

    @Override
    public void dataUpdateMq(Long recordId, String colName, Object colValue) {
        Map<String, Object> transMap = new HashMap<String, Object>();
        transMap.put("recordId", recordId);
        transMap.put("colName", colName);
        transMap.put("colValue", colValue);
        //mq发送消息start
        MqDto mqDto = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXAM_RECORD_UPDATE.name(), transMap, MqTagEnum.EXAM_RECORD_UPDATE, recordId.toString(), colName);
        mqDtoService.assembleSendOneWayMsg(mqDto);
    }

    /**
     * 单字段更新
     *
     * @param recordId
     * @param colName
     * @param colValue
     */
    @Transactional
    @Override
    public void dataUpdate(Long recordId, String colName, Object colValue) {
        tOeExamRecordMapper.dataUpdate(recordId, colName, colValue);
    }

    /**
     * 多字段更新
     *
     * @param recordId
     * @param colNames
     * @param colValues
     */
    @Override
    @Transactional
    public void dataUpdatesMq(Long recordId, String[] colNames, Object[] colValues) {
        Map<String, Object> transMap = new HashMap<String, Object>();
        transMap.put("recordId", recordId);
        transMap.put("colNames", colNames);
        transMap.put("colValues", colValues);
        //mq发送消息start
        MqDto mqDto = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXAM_RECORD_UPDATE_COLUMNS.name(), transMap, MqTagEnum.EXAM_RECORD_UPDATE_COLUMNS, recordId.toString(), recordId.toString());
        mqDtoService.assembleSendOneWayMsg(mqDto);
    }

    @Override
    @Transactional
    public void dataInitMq(Map<String, Object> param) {
        Long id = (Long) param.get("id");
        //mq发送消息start
        MqDto mqDto = new MqDto(MqTopicEnum.THEMIS_TOPIC.getCode(), MqTagEnum.EXAM_RECORD_INIT.name(), param, MqTagEnum.EXAM_RECORD_INIT, id.toString(), id.toString());
        mqDtoService.assembleSendOneWayMsg(mqDto);
    }

    @Transactional
    @Override
    public void dataInit(Map<String, Object> param) {
        TOeExamRecord tr = new TOeExamRecord();
        tr.setId((Long) param.get("id"));
        tr.setExamId((Long) param.get("examId"));
        tr.setExamActivityId((Long) param.get("examActivityId"));
        tr.setExamStudentId((Long) param.get("examStudentId"));
        tr.setPaperId((Long) param.get("paperId"));
        tr.setSerialNumber((Integer) param.get("serialNumber"));
        tr.setFirstPrepareTime((Long) param.get("firstPrepareTime"));
        tr.setStatus(ExamRecordStatusEnum.valueOf((String) param.get("status")));
        tr.setObjectiveScore(0.0);
        tr.setAlreadyBreakCount(0);
        tr.setStartTime((Long) param.get("startTime"));
        tr.setEndTime((Long) param.get("endTime"));
        tr.setOpeningSeconds((Integer) param.get("openingSeconds"));
        tr.setMinDurationSeconds((Integer) param.get("minDurationSeconds"));
        tr.setMaxDurationSeconds((Integer) param.get("maxDurationSeconds"));
        tr.setForceFinish((Integer) param.get("forceFinish"));
        saveOrUpdate(tr);
    }

    /**
     * 查询实时监控台列表
     *
     * @param iPage
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param paperDownload
     * @param status
     * @param name
     * @param identity
     * @param minWarningCount
     * @param maxWarningCount
     * @param clientWebsocketStatus
     * @param monitorStatusSource
     * @param userId
     * @return
     */
    @Override
    public IPage<InvigilateListBean> invigilatePageList(IPage<Map> iPage, Long examId,
                                                        Long examActivityId,
                                                        String roomCode,
                                                        Integer paperDownload,
                                                        String status,
                                                        String name,
                                                        String identity,
                                                        Integer minWarningCount,
                                                        Integer maxWarningCount,
                                                        String clientWebsocketStatus,
                                                        String monitorStatusSource,
                                                        Long userId) {
        return tOeExamRecordMapper.invigilatePageList(iPage, examId, examActivityId, roomCode, paperDownload, status, name, identity, minWarningCount, maxWarningCount, clientWebsocketStatus, monitorStatusSource, userId);
    }

    /**
     * 查询实时监控台视频列表
     *
     * @param iPage
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param paperDownload
     * @param status
     * @param name
     * @param identity
     * @param minWarningCount
     * @param maxWarningCount
     * @param clientWebsocketStatus
     * @param monitorStatusSource
     * @param userId
     * @return
     */
    @Override
    public IPage<InvigilateListVideoBean> invigilatePageListVideo(IPage<Map> iPage, Long examId, Long examActivityId, String roomCode, Integer paperDownload, String status, String name, String identity, Integer minWarningCount, Integer maxWarningCount, String clientWebsocketStatus, String monitorStatusSource, Long userId) {
        return tOeExamRecordMapper.invigilatePageListVideo(iPage, examId, examActivityId, roomCode, paperDownload, status, name, identity, minWarningCount, maxWarningCount, clientWebsocketStatus, monitorStatusSource, userId);
    }

    /**
     * 实时监控台视频随机列表
     *
     * @param examId
     * @param userId
     * @param randomNum
     * @return
     */
    @Override
    public List<InvigilateListVideoBean> invigilatePageListVideoRandom(Long examId, Long userId, Integer randomNum) {
        return tOeExamRecordMapper.invigilatePageListVideoRandom(examId, userId, randomNum);
    }

    /**
     * 查询在线巡考列表
     *
     * @param iPage
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param status
     * @param name
     * @param identity
     * @param minMultipleFaceCount
     * @param maxMultipleFaceCount
     * @param minExceptionCount
     * @param maxExceptionCount
     * @param minWarningCount
     * @param maxWarningCount
     * @param clientWebsocketStatus
     * @param monitorStatusSource
     * @param userId
     * @return
     */
    @Override
    public IPage<InvigilateListPatrolBean> invigilatePagePatrolList(IPage<Map> iPage, Long examId, Long examActivityId, String roomCode, String status, String name, String identity, Integer minMultipleFaceCount, Integer maxMultipleFaceCount, Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount, String clientWebsocketStatus, String monitorStatusSource, Long userId) {
        return tOeExamRecordMapper.invigilatePagePatrolList(iPage, examId, examActivityId, roomCode, status, name, identity, minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount, minWarningCount, maxWarningCount, clientWebsocketStatus, monitorStatusSource, userId);
    }

    /**
     * 预警提醒列表
     *
     * @param iPage
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param approveStatus
     * @param name
     * @param identity
     * @param minMultipleFaceCount
     * @param maxMultipleFaceCount
     * @param minExceptionCount
     * @param maxExceptionCount
     * @param minWarningCount
     * @param maxWarningCount
     * @param clientWebsocketStatus
     * @param monitorStatusSource
     * @param userId
     * @return
     */
    @Override
    public IPage<InvigilateListWarningBean> invigilatePageWarningList(IPage<Map> iPage, Long examId, Long examActivityId, String roomCode, Integer approveStatus, String name, String identity, Integer minMultipleFaceCount, Integer maxMultipleFaceCount, Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount, String clientWebsocketStatus, String monitorStatusSource, Long userId) {
        return tOeExamRecordMapper.invigilatePageWarningList(iPage, examId, examActivityId, roomCode, approveStatus, name, identity, minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount, minWarningCount, maxWarningCount, clientWebsocketStatus, monitorStatusSource, userId);
    }

    /**
     * 进度查询列表
     *
     * @param iPage
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param courseCode
     * @param name
     * @param identity
     * @param userId
     * @return
     */
    @Override
    public IPage<InvigilateListProgressBean> invigilatePageProgressList(IPage<Map> iPage, Long examId, Long examActivityId, String roomCode, String courseCode, String name, String identity, Long userId) {
        return tOeExamRecordMapper.invigilatePageProgressList(iPage, examId, examActivityId, roomCode, courseCode, name, identity, userId);
    }

    /**
     * 进度查询列表导出
     *
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param courseCode
     * @param name
     * @param identity
     * @param userId
     * @return
     */
    @Override
    public List<InvigilateListProgressExcelBean> invigilatePageProgressListExport(Long examId, Long examActivityId, String roomCode, String courseCode, String name, String identity, Long userId) {
        return tOeExamRecordMapper.invigilatePageProgressListExport(examId, examActivityId, roomCode, courseCode, name, identity, userId);
    }

    /**
     * 监考明细管理列表
     *
     * @param iPage
     * @param examId
     * @param examActivityId
     * @param roomCode
     * @param courseCode
     * @param status
     * @param breachStatus
     * @param finishType
     * @param name
     * @param identity
     * @param minMultipleFaceCount
     * @param maxMultipleFaceCount
     * @param minExceptionCount
     * @param maxExceptionCount
     * @param minWarningCount
     * @param maxWarningCount
     * @param clientWebsocketStatus
     * @param monitorStatusSource
     * @param userId
     * @return
     */
    @Override
    public IPage<InvigilateListHistoryBean> invigilatePageListHistory(IPage<Map> iPage, Long examId, Long examActivityId, String roomCode, String courseCode, String status, Integer breachStatus, String finishType, String name, String identity, Integer minMultipleFaceCount, Integer maxMultipleFaceCount, Integer minExceptionCount, Integer maxExceptionCount, Integer minWarningCount, Integer maxWarningCount, String clientWebsocketStatus, String monitorStatusSource, Long userId) {
        return tOeExamRecordMapper.invigilatePageListHistory(iPage, examId, examActivityId, roomCode, courseCode, status, breachStatus, finishType, name, identity, minMultipleFaceCount, maxMultipleFaceCount, minExceptionCount, maxExceptionCount, minWarningCount, maxWarningCount, clientWebsocketStatus, monitorStatusSource, userId);
    }

    /**
     * 重新算分
     */
    @Transactional
    @Override
    public void calculateScore(Long recordId) {

        TOeExamRecord rec = this.getById(recordId);

        Map<String, ObjectiveAnswerCacheBean> map = examPaperService.getObjectiveAnswerCacheBean(rec.getPaperId());
        if (map == null || map.size() == 0) {
            log.debug("no ObjectiveAnswerCacheBean for calculateObjectiveScore recordId:" + recordId);
            return;
        }
        QueryWrapper<TOeExamAnswer> wp = new QueryWrapper<>();
        wp.lambda().eq(TOeExamAnswer::getExamRecordId, recordId);
        List<TOeExamAnswer> answers = examAnswerService.list(wp);
        if (answers == null || answers.size() == 0) {
            log.debug("no ObjectiveAnswer for calculateObjectiveScore recordId:" + recordId);
            return;
        }
        BigDecimal tatolscore = new BigDecimal(0.0);
        for (TOeExamAnswer answer : answers) {
            Double score = calculateScoreForQuestion(answer, map);
            if (score != null) {
                tatolscore = tatolscore.add(new BigDecimal(score));
            }
        }

        // 设置客观分总分
        tOeExamRecordMapper.updateObjectiveScore(recordId, tatolscore.doubleValue());
    }

    private Double calculateScoreForQuestion(TOeExamAnswer answer, Map<String, ObjectiveAnswerCacheBean> map) {
        Long recordId = answer.getExamRecordId();
        Integer mainNumber = answer.getMainNumber();
        Integer subNumber = answer.getSubNumber();
        Integer subIndex = answer.getSubIndex();
        String key = RedisKeyHelper.examAnswerHashKey(mainNumber, subNumber, subIndex);
        ObjectiveAnswerCacheBean cb = map.get(key);
        if (cb == null) {
            log.debug("not a objective question  recordId:" + answer.getExamRecordId() + " key:" + key);
            return null;
        }

        if (cb.getStructType().intValue() == 1) {
            if (checkSingleChoice(answer.getAnswer(), cb.getChoiceAnswer())) {
                answer.setScore(cb.getScore());
            } else {
                answer.setScore(0.0);
            }
        }
        if (cb.getStructType().intValue() == 2) {
            ExamCacheBean ec = examService.getExamCacheBean(ExamRecordCacheUtil.getExamId(recordId));
            int r = checkMultipleChoice(answer.getAnswer(), cb.getChoiceAnswer());
            if (ec.getObjectiveScorePolicy().equals(ObjectiveScorePolicyEnum.EQUAL)) {// 全对给分
                if (r == 1) {
                    answer.setScore(cb.getScore());
                } else {
                    answer.setScore(0.0);
                }
            }
            if (ec.getObjectiveScorePolicy().equals(ObjectiveScorePolicyEnum.PARTIAL)) {// 漏选半分
                if (r == 1) {
                    answer.setScore(cb.getScore());
                } else if (r == 0) {
                    BigDecimal b = new BigDecimal(cb.getScore()).divide(new BigDecimal("2"), 1, BigDecimal.ROUND_UP);
                    answer.setScore(b.doubleValue());
                } else {
                    answer.setScore(0.0);
                }
            }
        }
        if (cb.getStructType().intValue() == 3) {
            if (Boolean.parseBoolean(answer.getAnswer()) == cb.getBoolAnswer().booleanValue()) {
                answer.setScore(cb.getScore());
            } else {
                answer.setScore(0.0);
            }
        }

        examAnswerService.saveOrUpdate(answer);

        return answer.getScore();
    }

    /**
     * 获取考试记录数
     *
     * @param examId
     * @return
     */
    @Override
    public Long getCountByExamId(Long examId) {
        return tOeExamRecordMapper.getCountByExamId(examId);
    }

    /**
     * 批量获取考试记录
     *
     * @param examId
     * @param startId
     * @return
     */
    @Override
    public List<TOeExamRecord> getListByExamIdAndStartId(Long examId, Long startId) {
        return tOeExamRecordMapper.getListByExamIdAndStartId(examId, startId);
    }

    @Override
    public TOeExamRecord findOneByPaperId(Long paperId) {
        return tOeExamRecordMapper.findOneByPaperId(paperId);
    }

    @Override
    public TOeExamRecord findOneByExamId(Long examId, ExamRecordStatusEnum status) {
        return tOeExamRecordMapper.findOneByExamId(examId, status.name());
    }
}
