package cn.com.qmth.stmms.api.controller;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import cn.com.qmth.stmms.admin.thread.MarkGroupDeleteThread;
import cn.com.qmth.stmms.admin.thread.ScoreCalculateThread;
import cn.com.qmth.stmms.api.dto.GroupDTO;
import cn.com.qmth.stmms.api.dto.GroupQuestionDTO;
import cn.com.qmth.stmms.api.dto.MarkerClassDTO;
import cn.com.qmth.stmms.api.dto.PaperDTO;
import cn.com.qmth.stmms.api.dto.PaperStructureDTO;
import cn.com.qmth.stmms.api.dto.QuestionDTO;
import cn.com.qmth.stmms.api.exception.ApiException;
import cn.com.qmth.stmms.biz.exam.model.Exam;
import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
import cn.com.qmth.stmms.biz.exam.model.Marker;
import cn.com.qmth.stmms.biz.exam.model.SubjectUser;
import cn.com.qmth.stmms.biz.exam.query.ExamStudentSearchQuery;
import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
import cn.com.qmth.stmms.biz.exam.service.ExamService;
import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
import cn.com.qmth.stmms.biz.exam.service.InspectHistoryService;
import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
import cn.com.qmth.stmms.biz.exam.service.MarkerClassService;
import cn.com.qmth.stmms.biz.exam.service.MarkerService;
import cn.com.qmth.stmms.biz.exam.service.SubjectUserService;
import cn.com.qmth.stmms.biz.exam.service.query.ExamQuestionSearchQuery;
import cn.com.qmth.stmms.biz.file.service.FileService;
import cn.com.qmth.stmms.biz.lock.LockService;
import cn.com.qmth.stmms.biz.mark.service.MarkService;
import cn.com.qmth.stmms.biz.school.model.School;
import cn.com.qmth.stmms.biz.school.service.SchoolService;
import cn.com.qmth.stmms.biz.user.model.User;
import cn.com.qmth.stmms.biz.user.service.UserService;
import cn.com.qmth.stmms.biz.utils.ScoreItem;
import cn.com.qmth.stmms.common.annotation.RoleRequire;
import cn.com.qmth.stmms.common.domain.ApiUser;
import cn.com.qmth.stmms.common.enums.ExamStatus;
import cn.com.qmth.stmms.common.enums.ExamType;
import cn.com.qmth.stmms.common.enums.LockType;
import cn.com.qmth.stmms.common.enums.ObjectiveStatus;
import cn.com.qmth.stmms.common.enums.QuestionType;
import cn.com.qmth.stmms.common.enums.Role;
import cn.com.qmth.stmms.common.enums.SubjectiveStatus;
import cn.com.qmth.stmms.common.enums.UserSource;
import cn.com.qmth.stmms.common.utils.DateUtils;
import cn.com.qmth.stmms.common.utils.EncryptUtils;
import cn.com.qmth.stmms.common.utils.RequestUtils;

@Controller("coreApiController")
@RequestMapping("/api")
public class CoreController extends BaseApiController {

    private static final int MAX_QUERY_PAGE_SIZE = 100;

    private static final String NULL_PAPER_TYPE_PLACEHOLDER = "#";

    private static final String PAPER_TYPES_REGEX = "[a-zA-Z#]";

    public static final int UN_SELECTIVE_SCORE = -1;

    @Autowired
    private ExamService examService;

    @Autowired
    private ExamStudentService studentService;

    @Autowired
    private ExamQuestionService questionService;

    @Autowired
    private FileService fileService;

    @Autowired
    private ExamSubjectService subjectService;

    @Autowired
    private UserService userService;

    @Autowired
    private MarkGroupService groupService;

    @Autowired
    private MarkerService markerService;

    @Autowired
    private MarkService markService;

    @Autowired
    private LockService lockService;

    @Qualifier("task-executor")
    @Autowired
    private AsyncTaskExecutor taskExecutor;

    @Autowired
    private SubjectUserService subjectUserService;

    @Autowired
    private InspectHistoryService inspectHistoryService;

    @Autowired
    private SchoolService schoolService;

    @Autowired
    private MarkerClassService markerClassService;

    public static final String USER_PASSWORD = "123456";

    @RequestMapping(value = "/exam/save", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV })
    public JSONObject examSave(HttpServletRequest request, @RequestParam(required = false) Integer id,
            @RequestParam(required = false) String code, @RequestParam String name, @RequestParam String examTime,
            @RequestParam String type, @RequestParam(required = false) String schoolCode) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONObject result = new JSONObject();
        // 输入字段预处理并初步校验
        code = validate("code", code, false, 32);
        name = validate("name", name, true, 32);
        Date time = DateUtils.parseDate(examTime);
        Exam current = null;
        School school = validate(user.getSchoolId(), schoolCode);
        if (id != null) {
            // 根据id查找考试并校验
            current = examService.findById(id);
            if (current == null) {
                throw ApiException.QUERY_PARAM_ERROR.replaceMessage("id invalid");
            }
        } else if (code != null) {
            // 根据code查找考试并校验
            current = examService.findBySchoolAndCode(school.getId(), code);
        } else {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("id/code both unexists");
        }
        if (current == null) {
            // 新建考试并校验考试时间
            if (time == null) {
                throw ApiException.QUERY_PARAM_ERROR.replaceMessage("examTime invalid");
            }
            current = new Exam();
            current.setCode(code);
            current.setSchoolId(school.getId());
            current.setType(ExamType.SCAN_IMAGE);
            current.setStatus(ExamStatus.START);
            current.setForbiddenInfo(false);
            current.setObjectiveStatus(ObjectiveStatus.WAITING);
            current.setCreateTime(new Date());
            current.setType(ExamType.valueOf(type));
        } else if (!current.getSchoolId().equals(school.getId()) || current.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        current.setName(name);
        if (time != null) {
            current.setExamTime(time);
        }
        current = examService.save(current);
        result.accumulate("id", current.getId());
        result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
        return result;
    }

    @RequestMapping(value = "/exam/student/save", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV })
    public JSONObject examStudentSave(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String examNumber, @RequestParam String studentCode, @RequestParam String name,
            @RequestParam String college, @RequestParam String className, @RequestParam String teacher,
            @RequestParam String subjectCode, @RequestParam String subjectName,
            @RequestParam(required = false) String packageCode, @RequestParam(required = false) String paperType,
            @RequestParam(required = false) String examSite, @RequestParam(required = false) String examRoom) {
        ApiUser user = RequestUtils.getApiUser(request);
        Exam exam = examService.findById(examId);
        if (exam == null || !validateSchool(user.getSchoolId(), exam.getSchoolId())
                || exam.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        examNumber = validate("examNumber", examNumber, true, 64);
        ExamStudent student = studentService.findByExamIdAndExamNumber(examId, examNumber);
        if (student == null) {
            // 新建考生
            student = new ExamStudent();
            student.setExamId(examId);
            student.setSchoolId(exam.getSchoolId());
            student.setExamNumber(examNumber);
            student.setPackageCode(validate("packageCode", packageCode, false, 64));
            student.setStudentCode(validate("studentCode", studentCode, true, 64));
            student.setName(validate("name", name, true, 64));
            student.setSubjectCode(validate("subjectCode", subjectCode, true, 32));
            student.setSubjectName(validate("subjectName", subjectName, true, 32));
            student.setCollege(validate("college", college, true, 32));
            student.setClassName(validate("className", className, true, 32));
            student.setTeacher(validate("teacher", teacher, true, 32));
            validate("paperType", paperType, false, 16);
            if (paperType != null && paperType.matches(PAPER_TYPES_REGEX)) {
                student.setPaperType(paperType);
            } else {
                student.setPaperType(NULL_PAPER_TYPE_PLACEHOLDER);
            }
            student.setExamSite(validate("examSite", examSite, false, 32));
            student.setExamRoom(validate("examRoom", examRoom, false, 32));
            student.setAbsent(false);
            student.setManualAbsent(false);
            student.setUpload(false);
            student.setException(false);
            student.setBreach(false);
            student.setSliceCount(0);
            student.setSheetCount(0);
            student.setObjectiveScore(0d);
            student.setSubjectiveScore(0d);
            student.setInspectCount(0);
            student.setSubjectiveStatus(SubjectiveStatus.UNMARK);
        } else {
            // 更新现有考生
            name = validate(name, true, 32);
            if (name != null) {
                student.setName(name);
            }
            studentCode = validate(studentCode, true, 64);
            if (studentCode != null) {
                student.setStudentCode(studentCode);
            }
            subjectCode = validate(subjectCode, true, 32);
            subjectName = validate(subjectName, true, 32);
            if (subjectCode != null && subjectName != null) {
                student.setSubjectCode(subjectCode);
                student.setSubjectName(subjectName);
            }
            paperType = validate(paperType, true, 16);
            if (paperType != null && paperType.matches(PAPER_TYPES_REGEX)) {
                student.setPaperType(paperType);
            }
            college = validate(college, true, 32);
            if (college != null) {
                student.setCollege(college);
            }
            className = validate(className, true, 32);
            if (className != null) {
                student.setClassName(className);
            }
            teacher = validate(teacher, true, 32);
            if (teacher != null) {
                student.setTeacher(teacher);
            }
            examSite = validate(examSite, false, 32);
            if (examSite != null) {
                student.setExamSite(examSite);
            }
            examRoom = validate(examRoom, false, 32);
            if (examRoom != null) {
                student.setExamRoom(examRoom);
            }
        }
        studentService.save(student);
        JSONObject result = new JSONObject();
        result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
        return result;
    }

    @RequestMapping(value = "/student/query", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV })
    public JSONArray studentQuery(HttpServletRequest request, @RequestParam String studentCode,
            @RequestParam(required = false) String subjectCode, @RequestParam(required = false) Date minExamTime,
            @RequestParam(required = false) String schoolCode) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONArray array = new JSONArray();
        studentCode = StringUtils.trimToNull(studentCode);
        subjectCode = StringUtils.trimToNull(subjectCode);
        if (studentCode == null) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("studentCode invalid");
        }
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        School school = validate(user.getSchoolId(), schoolCode);
        query.setSchoolId(school.getId());
        query.setStudentCode(studentCode);
        query.setSubjectCode(subjectCode);
        query.orderByExamIdDesc();
        query = studentService.findByQuery(query);
        for (ExamStudent student : query.getResult()) {
            Exam exam = examService.findById(student.getExamId());
            if (minExamTime != null && minExamTime.before(exam.getExamTime())) {
                JSONObject obj = new JSONObject();
                obj.accumulate("examId", exam.getId());
                obj.accumulate("examCode", exam.getCode());
                obj.accumulate("examTime", DateUtils.formatDateTime(exam.getExamTime()));
                obj.accumulate("examNumber", student.getExamNumber());
                obj.accumulate("name", student.getName());
                obj.accumulate("subjectCode", student.getSubjectCode());
                obj.accumulate("subjectName", student.getSubjectName());
                obj.accumulate("examSite", StringUtils.trimToEmpty(student.getExamSite()));
                obj.accumulate("examRoom", StringUtils.trimToEmpty(student.getExamRoom()));
                array.add(obj);
            }
        }
        return array;
    }

    @RequestMapping(value = "/exam/student/score", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV })
    public JSONArray examStudentScore(HttpServletRequest request, @RequestParam(required = false) Integer examId,
            @RequestParam(required = false) String examCode, @RequestParam(required = false) String examNumber,
            @RequestParam(required = false) String studentCode, @RequestParam(required = false) String subjectCode,
            @RequestParam(required = false) String college, @RequestParam(required = false) String className,
            @RequestParam(required = false) String teacher, @RequestParam(required = false) Boolean withMarkTrack,
            @RequestParam(required = false) String schoolCode,
            @RequestParam(required = false, defaultValue = "1") Integer pageNumber,
            @RequestParam(required = false, defaultValue = "20") Integer pageSize) {
        ApiUser user = RequestUtils.getApiUser(request);
        if (examId == null && examCode == null) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("examId or examCode is required");
        }
        Exam exam = null;
        if (examId != null) {
            exam = examService.findById(examId);
        }
        School school = validate(user.getSchoolId(), schoolCode);
        if (examCode != null) {
            exam = examService.findBySchoolAndCode(school.getId(), examCode);
        }
        if (exam == null) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("exam unexists");
        }
        if (!validateSchool(school.getId(), exam.getSchoolId())) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        JSONArray array = new JSONArray();
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(exam.getId());
        query.setExamNumber(StringUtils.trimToNull(examNumber));
        query.setStudentCode(StringUtils.trimToNull(studentCode));
        query.setSubjectCode(StringUtils.trimToNull(subjectCode));
        query.setCollege(StringUtils.trimToNull(college));
        query.setClassName(StringUtils.trimToNull(className));
        query.setTeacher(StringUtils.trimToNull(teacher));
        query.setPageNumber(Math.max(1, pageNumber));
        query.setPageSize(Math.min(MAX_QUERY_PAGE_SIZE, pageSize));
        query = studentService.findByQuery(query);
        DecimalFormat df = new DecimalFormat("####.###");
        for (ExamStudent student : query.getResult()) {
            JSONObject obj = new JSONObject();
            obj.accumulate("examId", exam.getId());
            obj.accumulate("examCode", StringUtils.trimToEmpty(exam.getCode()));
            obj.accumulate("examNumber", student.getExamNumber());
            obj.accumulate("studentCode", student.getStudentCode());
            obj.accumulate("name", student.getName());
            obj.accumulate("subjectCode", student.getSubjectCode());
            obj.accumulate("subjectName", student.getSubjectName());
            obj.accumulate("paperType", StringUtils.trimToEmpty(student.getPaperType()));
            obj.accumulate("className", student.getClassName());
            obj.accumulate("college", student.getCollege());
            obj.accumulate("teacher", student.getTeacher());
            obj.accumulate("remark", StringUtils.trimToEmpty(student.getRemark()));
            obj.accumulate("status", getStatus(student));
            obj.accumulate("totalScore", getStatus(student) == 1 ? df.format(student.getTotalScore()) : 0);
            obj.accumulate("objectiveScore", getStatus(student) == 1 ? df.format(student.getObjectiveScore()) : 0);
            obj.accumulate("subjectiveScore", getStatus(student) == 1 ? df.format(student.getSubjectiveScore()) : 0);
            // obj.accumulate("objectiveScoreDetail",
            // StringUtils.trimToEmpty(student.getObjectiveScoreList()));
            // obj.accumulate("subjectiveScoreDetail",
            // StringUtils.trimToEmpty(student.getSubjectiveScoreList()));
            JSONArray objective = new JSONArray();
            JSONArray subjective = new JSONArray();
            if (getStatus(student) == 1) {
                // 构造客观题得分明细
                List<ScoreItem> scoreList = student.getScoreList(true);
                List<ExamQuestion> questionList = questionService.findByExamAndSubjectAndObjectiveAndPaperType(
                        student.getExamId(), student.getSubjectCode(), true, student.getPaperType());
                int i = 0;
                for (ScoreItem item : scoreList) {
                    i++;
                    if (questionList.size() < i) {
                        break;
                    }
                    ExamQuestion question = questionList.get(i - 1);
                    if (question.getTotalScore() == null || question.getTotalScore() == 0) {
                        continue;
                    }
                    JSONObject detail = new JSONObject();
                    detail.accumulate("mainNumber", question.getMainNumber());
                    detail.accumulate("subNumber", question.getSubNumber());
                    detail.accumulate("score", df.format(item.getScore()));
                    detail.accumulate("answer", StringUtils.trimToEmpty(item.getAnswer()));
                    objective.add(detail);
                }
                // 构造主观题得分明细
                scoreList = student.getScoreList(false);
                questionList = questionService.findByExamAndSubjectAndObjective(student.getExamId(),
                        student.getSubjectCode(), false);
                i = 0;
                for (ScoreItem item : scoreList) {
                    i++;
                    if (questionList.size() < i) {
                        break;
                    }
                    ExamQuestion question = questionList.get(i - 1);
                    if (question.getTotalScore() == null || question.getTotalScore() == 0) {
                        continue;
                    }
                    if (item.getScore() == UN_SELECTIVE_SCORE) {
                        continue;
                    }
                    JSONObject detail = new JSONObject();
                    detail.accumulate("mainNumber", question.getMainNumber());
                    detail.accumulate("subNumber", question.getSubNumber());
                    detail.accumulate("score", df.format(item.getScore()));
                    subjective.add(detail);
                }
            }
            obj.accumulate("objectiveScoreDetail", objective);
            obj.accumulate("subjectiveScoreDetail", subjective);
            // 构造给分轨迹
            if (withMarkTrack != null && withMarkTrack.booleanValue()) {
                obj.accumulate("markTags", studentService.buildSheetTags(student, false, false));
            }
            // 构造原图下载地址
            if (student.isUpload()) {
                obj.accumulate(
                        "sheetUrls",
                        fileService
                                .getSheetUris(student.getExamId(), student.getExamNumber(), 1, student.getSheetCount())
                                .stream().map(uri -> fileService.getFileServer().concat(uri))
                                .collect(Collectors.toList()));
            } else {
                obj.accumulate("sheetUrls", new JSONArray());
            }
            array.add(obj);
        }
        return array;
    }

    private int getStatus(ExamStudent student) {
        if (student.isBreach()) {
            return 3;
        }
        if (!student.isUpload() || student.isAbsent() || student.isManualAbsent()) {
            return 2;
        }
        return 1;
    }

    @RequestMapping(value = "/exam/student/count", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV })
    public JSONObject examStudentCount(HttpServletRequest request, @RequestParam(required = false) Integer examId,
            @RequestParam(required = false) String examCode, @RequestParam(required = false) String examNumber,
            @RequestParam(required = false) String studentCode, @RequestParam(required = false) String subjectCode,
            @RequestParam(required = false) String college, @RequestParam(required = false) String className,
            @RequestParam(required = false) String teacher, @RequestParam(required = false) String schoolCode) {
        ApiUser user = RequestUtils.getApiUser(request);
        if (examId == null && examCode == null) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("examId or examCode is required");
        }
        Exam exam = null;
        if (examId != null) {
            exam = examService.findById(examId);
        }
        School school = validate(user.getSchoolId(), schoolCode);
        if (examCode != null) {
            exam = examService.findBySchoolAndCode(school.getId(), examCode);
        }
        if (exam == null) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("exam unexists");
        }
        if (!validateSchool(school.getId(), exam.getSchoolId())) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(exam.getId());
        query.setExamNumber(StringUtils.trimToNull(examNumber));
        query.setStudentCode(StringUtils.trimToNull(studentCode));
        query.setSubjectCode(StringUtils.trimToNull(subjectCode));
        query.setCollege(StringUtils.trimToNull(college));
        query.setClassName(StringUtils.trimToNull(className));
        query.setTeacher(StringUtils.trimToNull(teacher));
        long count = studentService.countByQuery(query);
        JSONObject result = new JSONObject();
        result.accumulate("totalCount", count);
        return result;
    }

    @RequestMapping(value = "/exam/subject/save", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV })
    public JSONObject subjectSave(HttpServletRequest request, @RequestParam Integer examId, @RequestParam String code,
            @RequestParam String name, @RequestParam(required = false) String remark) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONObject result = new JSONObject();
        // 输入字段预处理并初步校验
        code = validate("code", code, true, 32);
        name = validate("name", name, true, 32);
        remark = validate("remark", remark, false, 64);
        Exam exam = examService.findById(examId);
        if (exam == null) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("examId invalid");
        } else if (!validateSchool(user.getSchoolId(), exam.getSchoolId()) || exam.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        ExamSubject subject = subjectService.find(examId, code);
        if (subject == null) {
            subject = new ExamSubject();
            subject.setCode(code);
            subject.setExamId(examId);
            subject.setObjectiveScore(0d);
            subject.setSubjectiveScore(0d);
            subject.setTotalScore(0d);
            subject.setUploadCount(0);
            subject.setAutoScroll(exam.isAutoScroll());
            subject.setEnableSplit(exam.isEnableSplit());
            subject.setDisplayQuestionName(false);
            subject.setInspectRound(1);
            subject.setSelective(false);
        }
        subject.setName(name);
        subject.setRemark(remark);
        subject = subjectService.save(subject);
        result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
        return result;
    }

    @RequestMapping(value = "/user/external/save", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV })
    public JSONObject userSave(HttpServletRequest request, @RequestParam String account,
            @RequestParam(required = false) String name, @RequestParam(required = false) String password,
            @RequestParam(required = false) Role role, @RequestParam boolean enable,
            @RequestParam(required = false) String schoolCode) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONObject result = new JSONObject();
        // 输入字段预处理并初步校验
        account = validate("account", account, true, 32);
        name = validate("name", name, true, 32);
        password = validate("password", password, false, 32);
        School school = validate(user.getSchoolId(), schoolCode);
        User u = userService.externalSaveAndUpdate(school.getId(), account, name, password, role);
        if (u.isEnable() != enable) {
            u.setEnable(enable);
            userService.save(u);
        }
        result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
        return result;
    }

    @RequestMapping(value = "/exam/paper/save", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV })
    public JSONObject paperSave(HttpServletRequest request, @RequestBody PaperDTO paper) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONObject result = new JSONObject();
        // 输入字段预处理并初步校验
        Exam exam = examService.findById(paper.getExamId());
        if (exam == null) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("examId invalid");
        } else if (!validateSchool(user.getSchoolId(), exam.getSchoolId()) || exam.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        ExamSubject subject = subjectService.find(paper.getExamId(), paper.getSubjectCode());
        if (subject == null) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("subjectCode invalid");
        }
        validate("paperType", paper.getPaperType(), false, 16);
        if (StringUtils.isNotBlank(paper.getPaperType()) && !paper.getPaperType().matches(PAPER_TYPES_REGEX)) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("paperType invalid");
        }
        // 客观题更新
        if (paper.getObjective()) {
            List<ExamQuestion> oList = parseQustionList(paper.getQuestions(), paper.getExamId(),
                    paper.getSubjectCode(), paper.getPaperType(), paper.getObjective());
            if (0 != questionService.countByExamIdAndSubjectAndObjectiveAndGroupNumberIsNull(paper.getExamId(),
                    subject.getCode(), true)) {
                questionService.deleteByExamAndSubjectAndObjective(paper.getExamId(), subject.getCode(), true);
            }
            questionService.save(oList);
            examService.updateObjectiveStatus(paper.getExamId(), ObjectiveStatus.WAITING);
            subjectService.updateScore(paper.getExamId(), subject.getCode(), true,
                    questionService.sumTotalScore(paper.getExamId(), subject.getCode(), true));
        } else {
            List<MarkGroup> groups = groupService.findByExamAndSubject(paper.getExamId(), subject.getCode());
            List<ExamQuestion> sList = parseQustionList(paper.getQuestions(), paper.getExamId(),
                    paper.getSubjectCode(), paper.getPaperType(), paper.getObjective());
            if (groups == null || groups.size() == 0) {
                if (0 != questionService.countByExamIdAndSubjectAndObjectiveAndGroupNumberIsNull(paper.getExamId(),
                        subject.getCode(), false)) {
                    questionService.deleteByExamAndSubjectAndObjective(paper.getExamId(), subject.getCode(), false);
                }
                questionService.save(sList);
                subjectService.updateScore(paper.getExamId(), subject.getCode(), false,
                        questionService.sumTotalScore(paper.getExamId(), subject.getCode(), false));
            }
        }
        result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
        return result;
    }

    private List<ExamQuestion> parseQustionList(List<QuestionDTO> questions, Integer examId, String subjectCode,
            String paperType, boolean objective) {
        List<ExamQuestion> list = new ArrayList<ExamQuestion>();
        for (QuestionDTO dto : questions) {
            ExamQuestion q = dto.transform();
            int score = (int) (q.getTotalScore() * 100);
            if ((q.getTotalScore() * 100) - score > 0) {
                throw ApiException.QUERY_PARAM_ERROR.replaceMessage(":qurstion totalScore invalid");
            }
            validate("mainTitle", q.getMainTitle(), true, 32);
            if (objective) {
                validate("answer", q.getAnswer(), true, 16);
                q.setAnswer(q.getAnswer().toUpperCase());
                if (q.getType() == null && q.getAnswer().length() > 1) {
                    q.setType(QuestionType.MULTIPLE);
                }
                if (q.getType() == null && q.getAnswer().length() == 1) {
                    q.setType(QuestionType.SINGLE);
                }
                if (QuestionType.TRUE_OR_FALSE.equals(q.getType())
                        && (!q.getAnswer().contains("A") && !q.getAnswer().contains("B"))) {
                    throw ApiException.QUERY_PARAM_ERROR.replaceMessage(":qurstion type TRUR_OR_FALSE invalid");
                }
                if (QuestionType.SINGLE.equals(q.getType()) && q.getAnswer().length() > 1) {
                    throw ApiException.QUERY_PARAM_ERROR.replaceMessage(":qurstion type SINGLE but answer>1 ");
                }
            } else {
                q.setType(null);
                q.setObjectivePolicy(null);
            }
            q.setExamId(examId);
            q.setSubjectCode(subjectCode);
            if (StringUtils.isNotBlank(paperType)) {
                q.setPaperType(paperType);
            } else {
                q.setPaperType(NULL_PAPER_TYPE_PLACEHOLDER);
            }
            q.setObjective(objective);
            list.add(q);
        }
        return list;
    }

    @RequestMapping(value = "/exam/paper/query", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public JSONArray paperQuery(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String subjectCode, @RequestParam(required = false, defaultValue = "#") String paperType) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONArray result = new JSONArray();
        // 输入字段预处理并初步校验
        Exam exam = examService.findById(examId);
        if (exam == null || !validateSchool(user.getSchoolId(), exam.getSchoolId())
                || exam.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        subjectCode = validate("subjectCode", subjectCode, true, 64);
        ExamSubject subject = subjectService.find(examId, subjectCode);
        if (subject == null) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": subjectCode error");
        }
        if (StringUtils.isNotBlank(paperType)) {
            List<String> paperTypes = questionService.getPaperType(examId, subjectCode);
            if (!contains(subject.getObjectiveScore() > 0, paperTypes, paperType)) {
                throw ApiException.QUERY_PARAM_ERROR.appendMessage(": paperType error");
            }
        }
        List<ExamQuestion> list = new ArrayList<ExamQuestion>();
        list.addAll(questionService.findByExamAndSubjectAndObjectiveAndPaperType(examId, subjectCode, true, paperType));
        list.addAll(questionService.findByExamAndSubjectAndObjective(examId, subjectCode, false));
        for (ExamQuestion q : list) {
            JSONObject value = new JSONObject();
            value.accumulate("objective", q.isObjective());
            value.accumulate("mainNumber", q.getMainNumber());
            value.accumulate("subNumber", q.getSubNumber());
            value.accumulate("mainTitle", q.getMainTitle());
            value.accumulate("totalScore", q.getTotalScore());
            value.accumulate("answer", q.getAnswer() == null ? "" : q.getAnswer());
            value.accumulate("type", q.getType() == null ? "" : q.getType().toString());
            result.add(value);
        }
        return result;
    }

    private boolean contains(boolean objective, List<String> paperTypes, String paperType) {
        if (NULL_PAPER_TYPE_PLACEHOLDER.equals(paperType)) {
            if (!objective) {
                // 纯主观题
                return true;
            }
        }
        for (String s : paperTypes) {
            if (paperType.equalsIgnoreCase(s)) {
                return true;
            }
        }
        return false;
    }

    @RequestMapping(value = "/exam/mark_group/delete", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public JSONObject groupDelete(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String subjectCode) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONObject result = new JSONObject();
        // 输入字段预处理并初步校验
        Exam exam = examService.findById(examId);
        if (exam == null || !validateSchool(user.getSchoolId(), exam.getSchoolId())
                || exam.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        subjectCode = validate("subjectCode", subjectCode, true, 64);
        ExamSubject subject = subjectService.find(examId, subjectCode);
        if (subject == null) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": subjectCode error");
        }
        List<MarkGroup> groups = groupService.findByExamAndSubject(examId, subjectCode);
        for (MarkGroup markGroup : groups) {
            if (markGroup.getMarkedCount() > 0) {
                throw ApiException.QUERY_PARAM_ERROR.appendMessage(": subject is marking");
            }
        }
        for (MarkGroup group : groups) {
            if (lockService
                    .trylock(LockType.GROUP_DELETE, group.getExamId(), group.getSubjectCode(), group.getNumber())) {
                taskExecutor.submit(new MarkGroupDeleteThread(group, markService, lockService));
            }
        }
        result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
        return result;
    }

    @RequestMapping(value = "/exam/mark_group/count", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public JSONObject groupCount(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String subjectCode) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONObject result = new JSONObject();
        // 输入字段预处理并初步校验
        Exam exam = examService.findById(examId);
        if (exam == null || !validateSchool(user.getSchoolId(), exam.getSchoolId())
                || exam.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        subjectCode = validate("subjectCode", subjectCode, true, 64);
        ExamSubject subject = subjectService.find(examId, subjectCode);
        if (subject == null) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": subjectCode error");
        }
        result.accumulate("totalCount", groupService.countByExamAndSubject(examId, subjectCode));
        return result;
    }

    @RequestMapping(value = "/exam/mark_group/save", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public JSONObject groupSave(HttpServletRequest request, @RequestBody PaperStructureDTO dto) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONObject result = new JSONObject();
        // 输入字段预处理并初步校验
        Exam exam = examService.findById(dto.getExamId());
        if (exam == null || !validateSchool(user.getSchoolId(), exam.getSchoolId())
                || exam.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        String subjectCode = validate("subjectCode", dto.getSubjectCode(), true, 64);
        ExamSubject subject = subjectService.find(dto.getExamId(), subjectCode);
        if (subject == null) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": subjectCode error");
        }
        long count = groupService.countByExamAndSubject(dto.getExamId(), subjectCode);
        if (count != 0) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": subjectCode error");
        }
        for (GroupDTO g : dto.getGroups()) {
            List<ExamQuestion> questionGroup = new ArrayList<ExamQuestion>();
            BigDecimal totalScore = BigDecimal.ZERO;
            for (GroupQuestionDTO q : g.getQuestions()) {
                ExamQuestion old = questionService.findByExamAndSubjectAndObjectiveAndMainNumberAndSubNumber(
                        dto.getExamId(), dto.getSubjectCode(), false, q.getMainNumber(), q.getSubNumber());
                if (old == null) {
                    throw ApiException.QUERY_PARAM_ERROR.appendMessage(": question error");
                }
                totalScore = totalScore.add(BigDecimal.valueOf(old.getTotalScore()));
                old.setGroupNumber(g.getNumber());
                questionGroup.add(old);
            }
            MarkGroup group = new MarkGroup(dto.getExamId(), dto.getSubjectCode(), g.getNumber(), g.getPicConfig(),
                    totalScore.doubleValue(), g.getDoubleRate(), g.getArbitrateThreshold(), g.getScorePolicy(),
                    g.getMarkMode(), dto.getTrialCount(), false, g.getEnableAllZero() == null ? false
                            : g.getEnableAllZero(), null, false);
            if (!questionGroup.isEmpty()) {
                questionService.save(questionGroup);
                groupService.save(group);
            }
        }
        subjectService.updateTrialCount(dto.getExamId(), dto.getSubjectCode(), dto.getTrialCount());
        studentService.updateSubjectiveStatusAndScore(dto.getExamId(), dto.getSubjectCode(), SubjectiveStatus.UNMARK,
                0, null);
        inspectHistoryService.deleteByExamIdAndSubjectCode(dto.getExamId(), dto.getSubjectCode());
        result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
        return result;
    }

    @RequestMapping(value = "/exam/marker/save", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public JSONObject markerSave(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String subjectCode, @RequestParam Integer groupNumber, @RequestParam String account) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONObject result = new JSONObject();
        // 输入字段预处理并初步校验
        account = validate("account", account, true, 32);
        Exam exam = examService.findById(examId);
        if (exam == null || !validateSchool(user.getSchoolId(), exam.getSchoolId())
                || exam.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        subjectCode = validate("subjectCode", subjectCode, true, 64);
        ExamSubject subject = subjectService.find(examId, subjectCode);
        if (subject == null) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": subjectCode error");
        }
        MarkGroup group = groupService.findOne(examId, subjectCode, groupNumber);
        if (group == null) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": groupNumber error");
        }
        User u = userService.findByAccount(exam.getSchoolId(), account);
        if (u == null || !Role.MARKER.equals(u.getRole())) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": account error");
        }
        Marker marker = markerService.findByExamAndSubjectAndNumberAndUserId(examId, subjectCode, groupNumber,
                u.getId());
        if (marker == null) {
            marker = new Marker();
            marker.setExamId(examId);
            marker.setSubjectCode(subjectCode);
            marker.setGroupNumber(groupNumber);
            marker.setUserId(u.getId());
            marker.setEnable(true);
            markerService.save(marker);
        }
        result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
        return result;
    }

    @RequestMapping(value = "/exam/subject_header/save", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.COLLEGE_ADMIN })
    public JSONObject subjectHeaderSave(HttpServletRequest request, @RequestParam String subjectCode,
            @RequestParam String account, @RequestParam(required = false) String schoolCode) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONObject result = new JSONObject();
        // 输入字段预处理并初步校验
        account = validate("account", account, true, 32);
        subjectCode = validate("subjectCode", subjectCode, true, 64);
        School school = validate(user.getSchoolId(), schoolCode);
        User u = userService.findByAccount(school.getId(), account);
        if (u == null || !Role.SUBJECT_HEADER.equals(u.getRole())) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": account error");
        }
        SubjectUser subjectUser = subjectUserService.findBySubjectCodeAndUserId(subjectCode, u.getId());
        if (subjectUser == null) {
            subjectUser = new SubjectUser();
            subjectUser.setSubjectCode(subjectCode);
            subjectUser.setUserId(u.getId());
            subjectUserService.save(subjectUser);
        }
        result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
        return result;
    }

    @RequestMapping(value = "/exam/question/list", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public JSONArray questionList(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String subjectCode) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONArray result = new JSONArray();
        // 输入字段预处理并初步校验
        Exam exam = examService.findById(examId);
        if (exam == null || !validateSchool(user.getSchoolId(), exam.getSchoolId())
                || exam.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        subjectCode = validate("subjectCode", subjectCode, true, 64);
        ExamSubject subject = subjectService.find(examId, subjectCode);
        if (subject == null) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": subjectCode error");
        }
        ExamQuestionSearchQuery query = new ExamQuestionSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setSort(new Sort(Direction.ASC, "subjectCode", "paperType", "mainNumber", "subNumber"));
        query.setPageNumber(1);
        query.setPageSize(Integer.MAX_VALUE);
        query = questionService.findByQuery(query);
        for (ExamQuestion q : query.getResult()) {
            JSONObject value = new JSONObject();
            value.accumulate("paperType", q.getPaperType());
            value.accumulate("objective", q.isObjective());
            value.accumulate("mainNumber", q.getMainNumber());
            value.accumulate("subNumber", q.getSubNumber());
            value.accumulate("mainTitle", q.getMainTitle());
            value.accumulate("totalScore", q.getTotalScore());
            value.accumulate("answer", q.getAnswer() == null ? "" : q.getAnswer());
            value.accumulate("type", q.getType() == null ? "" : q.getType().toString());
            result.add(value);
        }
        return result;
    }

    @RequestMapping(value = "/exam/student/objective/save", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV })
    public JSONObject objectiveSave(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String examNumber, @RequestParam Double objectiveScore) {
        ApiUser user = RequestUtils.getApiUser(request);
        Exam exam = examService.findById(examId);
        if (exam == null || !validateSchool(user.getSchoolId(), exam.getSchoolId())
                || exam.getStatus() != ExamStatus.START || exam.getType() != ExamType.MULTI_MEDIA) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        examNumber = validate("examNumber", examNumber, true, 64);
        if (objectiveScore == null) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("objectiveScore is required");
        }
        ExamStudent student = studentService.findByExamIdAndExamNumber(examId, examNumber);
        if (student != null) {
            studentService.updateObjectiveScore(student.getId(), objectiveScore, objectiveScore.toString());
        }
        JSONObject result = new JSONObject();
        result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
        return result;
    }

    @RequestMapping(value = "/school/sub/save", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_DEV })
    public JSONObject schoolSave(HttpServletRequest request, @RequestParam String code, @RequestParam String name) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONObject result = new JSONObject();
        // 输入字段预处理并初步校验
        code = validate("code", code, false, 32);
        name = validate("name", name, true, 32);
        School parent = schoolService.findById(user.getSchoolId());
        String subCode = parent.getCode() + "-" + code;
        School school = schoolService.findByParentIdAndSubCode(parent.getId(), subCode);
        if (school == null) {
            school = new School();
            school.setParentId(parent.getId());
            school.setSubCode(subCode);
            school.setEnable(true);
            school.setDoubleTrack(false);
            school.setGroupDeleteCheck(false);
            school.setProvince(parent.getProvince());
            school.setCity(parent.getCity());
            school.setAddress(parent.getAddress());
            school.setDescription(parent.getDescription());
            school.resetAccessKeyAndSecret();
        }
        school.setName(name);
        school = schoolService.save(school);
        User admin = userService.findSchoolAdmin(school.getId());
        if (admin == null) {
            admin = new User();
            admin.setSchoolId(school.getId());
            admin.setEnable(true);
            admin.setRole(Role.SCHOOL_ADMIN);
            admin.setSource(UserSource.INTERNAL);
            admin.setLoginName("admin-" + school.getSubCode());
            admin.setName(admin.getLoginName());
            admin.setPassword(EncryptUtils.md5(USER_PASSWORD));
            admin.setCreatedTime(new Date());
            userService.save(admin);
        }
        result.accumulate("id", school.getId());
        result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
        return result;
    }

    @RequestMapping(value = "/user/class/save", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public JSONObject userClassSave(HttpServletRequest request, @RequestBody MarkerClassDTO markerClass) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONObject result = new JSONObject();
        // 输入字段预处理并初步校验
        String account = validate("account", markerClass.getAccount(), true, 32);
        Exam exam = examService.findById(markerClass.getExamId());
        if (exam == null || !validateSchool(user.getSchoolId(), exam.getSchoolId())
                || exam.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        User u = userService.findByAccount(exam.getSchoolId(), account);
        if (u == null || !Role.MARKER.equals(u.getRole())) {
            throw ApiException.QUERY_PARAM_ERROR.appendMessage(": account error");
        }
        markerClassService.save(u.getId(), markerClass.getExamId(),
                markerClass.getClassName().stream().toArray(String[]::new));
        result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
        return result;
    }

    @RequestMapping(value = "/exam/subject/calculate", method = RequestMethod.POST)
    @ResponseBody
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCHOOL_DEV })
    public JSONObject calculate(HttpServletRequest request, @RequestParam Integer examId,
            @RequestParam String subjectCode) {
        ApiUser user = RequestUtils.getApiUser(request);
        JSONObject result = new JSONObject();
        // 输入字段预处理并初步校验
        Exam exam = examService.findById(examId);
        if (exam == null) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("examId invalid");
        } else if (!validateSchool(user.getSchoolId(), exam.getSchoolId()) || exam.getStatus() != ExamStatus.START) {
            throw ApiException.EXAM_NOT_ACCESSIBLED;
        }
        ExamSubject subject = subjectService.find(examId, subjectCode);
        if (subject == null) {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("subjectCode invalid");
        }
        Set<String> subjectSet = new HashSet<String>();
        if (!lockService.isLocked(LockType.SCORE_CALCULATE, exam.getId(), subjectCode)) {
            subjectSet.add(subjectCode);
        }
        if (!subjectSet.isEmpty()) {
            ScoreCalculateThread thread = new ScoreCalculateThread(exam.getId(), subjectSet, lockService,
                    studentService, questionService, examService, subjectService, fileService, false);
            taskExecutor.submit(thread);
            result.accumulate("updateTime", DateUtils.formatDateTime(new Date()));
            return result;
        } else {
            throw ApiException.QUERY_PARAM_ERROR.replaceMessage("subject is calculate");
        }
    }
}
