package cn.com.qmth.stmms.admin.exam;

import java.io.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
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.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.aliyun.oss.common.utils.BinaryUtil;
import com.qmth.boot.tools.io.ZipReader;

import cn.com.qmth.stmms.admin.dto.ObjectiveQuestionDTO;
import cn.com.qmth.stmms.admin.dto.QuestionDTO;
import cn.com.qmth.stmms.admin.dto.SubjectQuestionDTO;
import cn.com.qmth.stmms.admin.dto.SubjectiveQuestionDTO;
import cn.com.qmth.stmms.admin.thread.QuestionDeleteThread;
import cn.com.qmth.stmms.admin.thread.ScoreCalculateThread;
import cn.com.qmth.stmms.admin.thread.ScoreReportThread;
import cn.com.qmth.stmms.admin.vo.StructFile;
import cn.com.qmth.stmms.admin.vo.StructQuestion;
import cn.com.qmth.stmms.biz.common.domain.card.*;
import cn.com.qmth.stmms.biz.exam.model.*;
import cn.com.qmth.stmms.biz.exam.service.*;
import cn.com.qmth.stmms.biz.exam.service.query.ExamQuestionSearchQuery;
import cn.com.qmth.stmms.biz.exam.service.query.ExamSubjectSearchQuery;
import cn.com.qmth.stmms.biz.file.enums.FormatType;
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.report.service.ReportService;
import cn.com.qmth.stmms.common.annotation.Logging;
import cn.com.qmth.stmms.common.annotation.RoleRequire;
import cn.com.qmth.stmms.common.domain.WebUser;
import cn.com.qmth.stmms.common.enums.*;
import cn.com.qmth.stmms.common.utils.ExportExcel;
import cn.com.qmth.stmms.common.utils.ImportExcel;
import cn.com.qmth.stmms.common.utils.RequestUtils;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

@Controller("examPaperController")
@RequestMapping("/admin/exam/paper")
public class PaperController extends BaseExamController {

    private static final String NULL_PAPER_TYPE_PLACEHOLDER = "#";

    private static final String[] SUBJECTIVE_EXCEL_HEADER = new String[] { "科目代码*", "科目名称", "大题名称*", "题目昵称",
            "大题号(只能用小写数字)*", "小题号(只能用小写数字)*", "小题满分*", "间隔分*", "评卷分组(只能用小写数字)*", "图片序号(用英文逗号分割)", "双评比例(0~1)", "仲裁阀值",
            "合分策略(1-平均，2-最高，3-最低)", "评卷模式(common-普通，track-轨迹)", "试评数量(0-跳过试评)", "选做题数量" };

    private static final String[] OBJECTIVE_EXCEL_HEADER = new String[] { "科目代码", "科目名称", "试卷类型", "大题名称", "题目昵称",
            "大题号(只能用小写数字)", "小题号(只能用小写数字)", "标准答案", "小题满分", "题型(1-单选，2-多选，3-判断)" };

    @Autowired
    private ExamSubjectService subjectService;

    @Autowired
    private ExamQuestionService questionService;

    @Autowired
    private MarkGroupService groupService;

    @Autowired
    private ExamService examService;

    @Autowired
    private FileService fileService;

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

    @Autowired
    private LockService lockService;

    @Autowired
    private ExamStudentService studentService;

    @Autowired
    private ReportService reportService;

    @Autowired
    private MarkService markService;

    @Autowired
    private MarkerService markerService;

    @Autowired
    private SelectiveGroupService selectiveGroupService;

    @Autowired
    private AnswerCardService answerCardService;

    @Autowired
    private InspectHistoryService inspectHistoryService;

    @Value("${file.temp}")
    private String tempFile;

    @Logging(menu = "试卷管理查询", type = LogType.QUERY)
    @RequestMapping
    public String list(Model model, HttpServletRequest request, ExamSubjectSearchQuery query,
            @RequestParam(required = false) Boolean upload) {
        WebUser wu = RequestUtils.getWebUser(request);
        int examId = getSessionExamId(request);
        query.setExamId(examId);
        if (upload != null) {
            if (upload.booleanValue()) {
                query.setUploadCountGt(0);
            } else {
                query.setUploadCountEqual(0);
            }
        }
        query = subjectService.findByQuery(query);
        for (ExamSubject subject : query.getResult()) {
            List<String> paperTypes = questionService.getPaperType(examId, subject.getCode());
            subject.setPaperType(StringUtils.join(paperTypes, ","));
            subject.setPaperAnswerUrl(fileService);
            subject.setLocked(lockService.isLocked(LockType.SCORE_CALCULATE, examId, subject.getCode()));
        }
        model.addAttribute("query", query);
        model.addAttribute("upload", upload);
        model.addAttribute("subjectList", getExamSubject(examId, wu));
        model.addAttribute("levelList", subjectService.listLevel(examId));
        model.addAttribute("categoryList", subjectService.listCategory(examId));
        model.addAttribute("fileServer", fileService.getFileServer());
        model.addAttribute("examLock", lockService.isLocked(LockType.SCORE_CALCULATE, examId));
        Exam exam = examService.findById(examId);
        model.addAttribute("examType", exam.getType());
        List<Double> totalScoreList = subjectService.distinctTotalScore(examId);
        List<Long> subjectCountList = new ArrayList<Long>();
        for (Double totalScore : totalScoreList) {
            ExamSubjectSearchQuery querys = new ExamSubjectSearchQuery();
            querys.setExamId(examId);
            querys.setTotalScore(totalScore);
            subjectCountList.add(subjectService.countByQuery(querys));
        }
        List<String> totalScores = totalScoreList.stream().map(m -> String.valueOf(m)).collect(Collectors.toList());
        List<String> subjectCounts = subjectCountList.stream().map(m -> String.valueOf(m)).collect(Collectors.toList());
        model.addAttribute("totalScoreList", String.join(",", totalScores));
        model.addAttribute("subjectCountList", String.join(",", subjectCounts));
        return "modules/exam/paperList";
    }

    @Logging(menu = "科目详情", type = LogType.QUERY)
    @RequestMapping("/detail")
    public String detail(Model model, HttpServletRequest request, @RequestParam String subjectCode,
            @RequestParam(required = false) Boolean objective, @RequestParam(required = false) Boolean upload,
            @RequestParam(required = false) Integer mainNumber, ExamSubjectSearchQuery query) {
        int examId = getSessionExamId(request);
        ExamSubject subject = subjectService.find(examId, subjectCode);
        if (subject != null) {
            List<ExamQuestion> list = new LinkedList<ExamQuestion>();
            if (objective == null || objective) {
                List<ExamQuestion> objectiveList = mainNumber == null
                        ? questionService.findByExamAndSubjectAndObjective(examId, subjectCode, true)
                        : questionService.findByExamAndSubjectAndObjectiveAndMainNumber(examId, subjectCode, true,
                                mainNumber);
                list.addAll(objectiveList);
            }
            if (objective == null || !objective) {
                List<MarkGroup> groups = groupService.findByExamAndSubject(examId, subjectCode);
                Map<Integer, MarkGroup> maps = new HashMap<Integer, MarkGroup>();
                for (MarkGroup markGroup : groups) {
                    maps.put(markGroup.getNumber(), markGroup);
                }
                List<SelectiveGroup> selectiveGroups = selectiveGroupService.findByExamIdAndSubjectCode(examId,
                        subjectCode);
                Map<Integer, SelectiveGroup> selectiveMap = new HashMap<Integer, SelectiveGroup>();
                for (SelectiveGroup selectiveGroup : selectiveGroups) {
                    selectiveMap.put(selectiveGroup.getMainNumber(), selectiveGroup);
                }
                List<ExamQuestion> subjectiveList = mainNumber == null
                        ? questionService.findByExamAndSubjectAndObjective(examId, subjectCode, false)
                        : questionService.findByExamAndSubjectAndObjectiveAndMainNumber(examId, subjectCode, false,
                                mainNumber);
                for (ExamQuestion examQuestion : subjectiveList) {
                    if (examQuestion.getGroupNumber() != null
                            && maps.get(examQuestion.getGroupNumber()).getMarkedCount() > 0) {
                        examQuestion.setEnableUpdate(false);
                    } else {
                        examQuestion.setEnableUpdate(true);
                    }
                    // 选做题判分策略
                    if (selectiveMap.containsKey(examQuestion.getMainNumber())) {
                        examQuestion.setSelective(true);
                        examQuestion
                                .setSelectiveIndex(selectiveMap.get(examQuestion.getMainNumber()).getSelectiveIndex());
                        examQuestion
                                .setSelectivePart(selectiveMap.get(examQuestion.getMainNumber()).getSelectivePart());
                        examQuestion.setScorePolicy(
                                selectiveMap.get(examQuestion.getMainNumber()).getScorePolicy().getValue());
                    } else {
                        examQuestion.setSelective(false);
                    }
                }
                list.addAll(subjectiveList);
            }
            for (ExamQuestion examQuestion : list) {
                examQuestion.setDeleting(lockService.isLocked(LockType.QUESTION_DELETE, examQuestion.getExamId(),
                        examQuestion.getSubjectCode(), examQuestion.getMainNumber(), examQuestion.getSubNumber()));
            }
            List<ExamQuestion> mainList = new LinkedList<ExamQuestion>();
            mainList.addAll(questionService.findMainByExamAndSubjectAndObjective(examId, subjectCode, true));
            mainList.addAll(questionService.findMainByExamAndSubjectAndObjective(examId, subjectCode, false));
            model.addAttribute("questionList", list);
            model.addAttribute("mainList", mainList);
            model.addAttribute("subject", subject);
            model.addAttribute("objective", objective);
            model.addAttribute("mainNumber", mainNumber);
            model.addAttribute("upload", upload);
            model.addAttribute("query", query);
            return "modules/exam/paperDetail";
        } else {
            return "redirect:/admin/exam/paper";
        }
    }

    @RequestMapping(value = "/template")
    public String importTemplate(HttpServletResponse response, @RequestParam Boolean objective,
            RedirectAttributes redirectAttributes) {
        try {
            String fileName = objective ? "客观题导入模板.xlsx" : "主观题导入模板.xlsx";
            String title = objective ? "客观题数据" : "主观题数据";
            List<QuestionDTO> list = new LinkedList<QuestionDTO>();
            list.add(objective ? new ObjectiveQuestionDTO() : new SubjectiveQuestionDTO());
            new ExportExcel(title, objective ? ObjectiveQuestionDTO.class : SubjectiveQuestionDTO.class, 2)
                    .setDataList(list).write(response, fileName).dispose();
            return null;
        } catch (Exception e) {
            addMessage(redirectAttributes, "导入模板下载失败！失败信息：" + e.getMessage());
        }
        return "redirect:/admin/exam/paper";
    }

    @Logging(menu = "导出客/主观题", type = LogType.EXPORT)
    @RequestMapping(value = "/export")
    public String exportFile(HttpServletRequest request, HttpServletResponse response, @RequestParam Boolean objective,
            RedirectAttributes redirectAttributes) {
        int examId = getSessionExamId(request);

        Map<String, ExamSubject> subjectMap = new HashMap<String, ExamSubject>();
        List<ExamSubject> subjectList = subjectService.list(examId);
        for (ExamSubject subject : subjectList) {
            subjectMap.put(subject.getCode(), subject);
        }

        Map<String, MarkGroup> groupMap = new HashMap<String, MarkGroup>();
        List<MarkGroup> groupList = groupService.findByExam(examId);
        for (MarkGroup group : groupList) {
            groupMap.put(group.getSubjectCode() + "_" + group.getNumber(), group);
        }

        List<QuestionDTO> list = new LinkedList<QuestionDTO>();
        ExamQuestionSearchQuery query = new ExamQuestionSearchQuery();
        query.setExamId(examId);
        query.setObjective(objective);
        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()) {
            list.add(objective ? new ObjectiveQuestionDTO(q, subjectMap.get(q.getSubjectCode()))
                    : new SubjectiveQuestionDTO(q, subjectMap.get(q.getSubjectCode()),
                            groupMap.get(q.getSubjectCode() + "_" + q.getGroupNumber())));
        }
        try {
            String fileName = objective ? "客观题数据.xlsx" : "主观题数据.xlsx";
            String title = objective ? "客观题数据" : "主观题数据";
            new ExportExcel(title, objective ? ObjectiveQuestionDTO.class : SubjectiveQuestionDTO.class, 2)
                    .setDataList(list).write(response, fileName).dispose();
            return null;
        } catch (Exception e) {
            addMessage(redirectAttributes, "导出数据失败！失败信息：" + e.getMessage());
        }
        return "redirect:/admin/exam/paper";
    }

    @Logging(menu = "导入客/主观题", type = LogType.IMPORT_FILE)
    @RequestMapping(value = "/import", method = RequestMethod.POST)
    @RoleRequire(Role.SCHOOL_ADMIN)
    public String importFile(HttpServletRequest request, MultipartFile file, @RequestParam Boolean objective,
            RedirectAttributes redirectAttributes) {
        int examId = getSessionExamId(request);

        List<String> error = new LinkedList<String>();
        Map<String, SubjectQuestionDTO> map = parseQuestion(file, examId, objective, error);
        if (error.isEmpty()) {
            int success = 0;
            for (SubjectQuestionDTO dto : map.values()) {
                // 每次导入都需要重新统分
                examService.updateObjectiveStatus(examId, ObjectiveStatus.WAITING);
                ExamSubject subject = subjectService.find(examId, dto.getSubjectCode());
                if (subject != null) {
                    Map<Integer, String> titleMap = new HashMap<>();
                    List<ExamQuestion> current = questionService.findByExamAndSubjectAndObjective(examId,
                            subject.getCode(), objective);
                    for (ExamQuestion question : current) {
                        titleMap.put(question.getMainNumber(), question.getMainTitle());
                    }
                    if (dto.validate(error, titleMap)) {
                        if (objective) {
                            questionService.deleteByExamAndSubjectAndObjective(examId, dto.getSubjectCode(), objective);
                            for (ExamQuestion question : dto.getQuestionList()) {
                                questionService.save(question);
                            }
                            subjectService.updateScore(examId, subject.getCode(), objective, dto.getTotalScore());
                        } else {
                            int questionCount = 0;
                            List<SelectiveGroup> list = selectiveGroupService.findByExamIdAndSubjectCode(examId,
                                    subject.getCode());
                            Map<Integer, SelectiveGroup> selectiveMap = new HashMap<Integer, SelectiveGroup>();
                            for (SelectiveGroup selectiveGroup : list) {
                                selectiveMap.put(selectiveGroup.getMainNumber(), selectiveGroup);
                            }
                            for (ExamQuestion question : dto.getQuestionList()) {
                                ExamQuestion old = questionService
                                        .findByExamAndSubjectAndObjectiveAndMainNumberAndSubNumber(examId,
                                                subject.getCode(), objective, question.getMainNumber(),
                                                question.getSubNumber());
                                if (old != null) {
                                    if (old.getGroupNumber() != null) {
                                        error.add("[" + subject.getCode() + "] 大题号" + question.getMainNumber() + " 小题号"
                                                + question.getSubNumber() + "已有分组");
                                        continue;
                                    }
                                    if (selectiveMap.containsKey(question.getMainNumber())) {
                                        error.add("[" + subject.getCode() + "] 大题号" + question.getMainNumber() + " 小题号"
                                                + question.getSubNumber() + "已有选做题分组");
                                        continue;
                                    }
                                    old.setName(question.getName());
                                    old.setTotalScore(question.getTotalScore());
                                    old.setIntervalScore(question.getIntervalScore());
                                    questionService.save(old);
                                } else {
                                    question.setGroupNumber(null);
                                    questionService.save(question);
                                    questionCount++;
                                }
                            }
                            if (questionCount > 0) {
                                studentService.updateSubjectiveStatusAndScore(examId, subject.getCode(),
                                        SubjectiveStatus.UNMARK, 0, null);
                                inspectHistoryService.deleteByExamIdAndSubjectCode(examId, subject.getCode());
                            }
                            subjectService.updateScore(examId, subject.getCode(), objective,
                                    questionService.sumTotalScore(examId, subject.getCode(), objective));
                        }
                        success++;
                    }
                } else {
                    error.add("[" + dto.getSubjectCode() + "] 科目代码不存在；");
                }
            }
            error.add(0, success + "个科目导入成功；");
            if (success > 0) {
                subjectService.updateTotalScore(examId);
            }
            RequestUtils.setLog(request, success + "个科目导入成功；");
        }
        if (error.size() > 0) {
            error.add("<br\\> 导入完毕后请重新统分！");
            addMessage(redirectAttributes, StringUtils.join(error, "<br\\>"));
        }
        return "redirect:/admin/exam/paper";
    }

    @Logging(menu = "导入主观题分组", type = LogType.IMPORT_FILE)
    @RequestMapping(value = "/importGroup", method = RequestMethod.POST)
    @RoleRequire(Role.SCHOOL_ADMIN)
    public String importGroupFile(HttpServletRequest request, MultipartFile file,
            RedirectAttributes redirectAttributes) {
        int examId = getSessionExamId(request);
        List<String> error = new LinkedList<String>();
        Map<String, SubjectQuestionDTO> map = parseQuestion(file, examId, false, error);

        if (error.isEmpty()) {
            int success = 0;
            for (SubjectQuestionDTO dto : map.values()) {
                // 每次导入都需要重新统分
                examService.updateObjectiveStatus(examId, ObjectiveStatus.WAITING);
                ExamSubject subject = subjectService.find(examId, dto.getSubjectCode());
                if (subject != null) {
                    // 验证是否存在分组
                    List<MarkGroup> groups = groupService.findByExamAndSubject(examId, subject.getCode());
                    if (groups != null && groups.size() > 0) {
                        error.add("[" + subject.getCode() + "] 已存在分组");
                        continue;
                    }
                    if (subject.isSelective()) {
                        error.add("[" + subject.getCode() + "] 已存在选做题分组");
                        continue;
                    }
                    Map<Integer, String> titleMap = new HashMap<>();
                    List<ExamQuestion> current = questionService.findByExamAndSubjectAndObjective(examId,
                            subject.getCode(), false);
                    for (ExamQuestion question : current) {
                        titleMap.put(question.getMainNumber(), question.getMainTitle());
                    }
                    if (dto.validate(error, titleMap) && dto.validateGroupNumber(error)
                            && dto.getQuestionList().size() == current.size()) {
                        int successGroup = 0;
                        for (MarkGroup group : dto.getGroups().values()) {
                            if (group.getImportQuestionList() != null) {
                                boolean validate = true;
                                List<ExamQuestion> questionGroup = new ArrayList<ExamQuestion>();
                                for (ExamQuestion question : group.getImportQuestionList()) {
                                    ExamQuestion old = questionService
                                            .findByExamAndSubjectAndObjectiveAndMainNumberAndSubNumber(examId,
                                                    subject.getCode(), false, question.getMainNumber(),
                                                    question.getSubNumber());
                                    if (old == null) {
                                        error.add(
                                                "[" + group.getSubjectCode() + "_" + group.getNumber() + "] 分组有小题不存在");
                                        validate = false;
                                        break;
                                    }
                                    old.setGroupNumber(group.getNumber());
                                    questionGroup.add(old);
                                }
                                // 校验通过小题才保存
                                if (validate && !questionGroup.isEmpty()) {
                                    questionService.save(questionGroup);
                                }
                                // 有题目的分组才保存
                                if (questionService.countByExamAndSubjectAndObjectiveAndGroupNumber(examId,
                                        group.getSubjectCode(), false, group.getNumber()) != 0) {
                                    BigDecimal totalScore = BigDecimal.ZERO;
                                    for (ExamQuestion q : questionGroup) {
                                        totalScore = totalScore.add(BigDecimal.valueOf(q.getTotalScore()));
                                    }
                                    group.setTotalScore(totalScore.doubleValue());
                                    groupService.save(group);
                                    studentService.updateSubjectiveStatusAndScore(examId, group.getSubjectCode(),
                                            SubjectiveStatus.UNMARK, 0, null);
                                    inspectHistoryService.deleteByExamIdAndSubjectCode(examId, subject.getCode());
                                    successGroup++;
                                }
                            }
                        }
                        if (successGroup > 0) {
                            success++;
                            subjectService.updateTrialCount(examId, subject.getCode(), dto.getTrialCount());
                        }
                    }
                } else {
                    error.add("[" + dto.getSubjectCode() + "] 科目代码不存在；");
                }
            }
            error.add(0, success + "个科目导入成功；");
            RequestUtils.setLog(request, success + "个科目导入成功；");
        }
        if (error.size() > 0) {
            error.add("<br\\> 导入完毕后请重新统分！");
            addMessage(redirectAttributes, StringUtils.join(error, "<br\\>"));
        }
        return "redirect:/admin/exam/paper";
    }

    private Map<String, SubjectQuestionDTO> parseQuestion(MultipartFile file, int examId, boolean objective,
            List<String> error) {
        Map<String, SubjectQuestionDTO> map = new HashMap<String, SubjectQuestionDTO>();
        try {
            ImportExcel excel = new ImportExcel(file, 1, 0);
            Class<? extends QuestionDTO> clazz = objective ? ObjectiveQuestionDTO.class : SubjectiveQuestionDTO.class;
            if (!objective && !Arrays.equals(SUBJECTIVE_EXCEL_HEADER, excel.getHeaderName())) {
                error.add("Excel表头错误");
                return map;
            }
            if (objective && !Arrays.equals(OBJECTIVE_EXCEL_HEADER, excel.getHeaderName())) {
                error.add("Excel表头错误");
                return map;
            }
            List<? extends QuestionDTO> list = excel.getDataList(clazz);
            if (list != null) {
                for (QuestionDTO dto : list) {
                    String subjectCode = StringUtils.trimToNull(dto.getSubjectCode());
                    if (subjectCode != null) {
                        SubjectQuestionDTO subject = map.get(subjectCode);
                        if (subject == null) {
                            subject = new SubjectQuestionDTO(examId, subjectCode, objective);
                            map.put(subjectCode, subject);
                        }
                        subject.addQuestion(dto.transform());
                    }
                }
            }
        } catch (Exception e) {
            log.error("parse question excel error", e);
            error.add("Excel文件解析失败");
        }
        return map;
    }

    @RequestMapping("/getPaperType")
    @ResponseBody
    public JSONArray query(HttpServletRequest request, @RequestParam String subjectCode) {
        int examId = getSessionExamId(request);
        JSONArray array = new JSONArray();
        List<String> list = this.questionService.getPaperType(examId, subjectCode);
        for (String paperType : list) {
            JSONObject obj = new JSONObject();
            obj.accumulate("paperType", paperType);
            array.add(obj);
        }
        return array;
    }

    @RequestMapping(value = "/question-edit/{questionId}", method = RequestMethod.GET)
    @RoleRequire(Role.SCHOOL_ADMIN)
    public String edit(Model model, @PathVariable Integer questionId, ExamSubjectSearchQuery query,
            @RequestParam(required = false) Boolean upload) {
        ExamQuestion examQuestion = questionService.findById(questionId);
        model.addAttribute("examQuestion", examQuestion);
        model.addAttribute("objectivePolicyList", ObjectivePolicy.values());
        model.addAttribute("questionTypeList", QuestionType.values());
        model.addAttribute("query", query);
        model.addAttribute("upload", upload);
        return "modules/exam/questionEdit";
    }

    @Logging(menu = "编辑题目", type = LogType.UPDATE)
    @RequestMapping(value = "/question-edit", method = RequestMethod.POST)
    @RoleRequire(Role.SCHOOL_ADMIN)
    public String update(HttpServletRequest request, RedirectAttributes redirectAttributes, @RequestParam Integer id,
            ExamQuestion question, ExamSubjectSearchQuery query, @RequestParam(required = false) Boolean upload) {
        if (question.getCustomizePolicy() != null) {
            question.setCustomizePolicy(StringEscapeUtils.unescapeHtml(question.getCustomizePolicy()));
        }
        int examId = getSessionExamId(request);
        ExamQuestion old = questionService.findById(id);
        String u = upload == null ? "" : upload.toString();
        String t = query.getTotalScoreNotEqual() == null ? "" : query.getTotalScoreNotEqual().toString();
        ExamQuestion newQ = questionService.findByExamAndSubjectAndObjectiveAndMainNumberAndSubNumber(examId,
                question.getSubjectCode(), question.isObjective(), question.getMainNumber(), question.getSubNumber());
        if (old == null || ((old.getMainNumber() != question.getMainNumber()
                || !old.getSubNumber().equals(question.getSubNumber())) && newQ != null)) {
            addMessage(redirectAttributes, "编辑失败，编辑题目不存在或编辑后的题号已存在");
            return "redirect:/admin/exam/paper/detail?subjectCode=" + question.getSubjectCode() + "&pageNumber="
                    + query.getPageNumber() + "&code=" + query.getCode() + "&category=" + query.getCategory()
                    + "&level=" + query.getLevel() + "&upload=" + u + "&totalScoreNotEqual=" + t;
        }
        if (!old.isObjective() && old.getGroupNumber() != null) {
            MarkGroup group = groupService.findOne(examId, old.getSubjectCode(), old.getGroupNumber());
            if (group.getMarkedCount() > 0) {
                addMessage(redirectAttributes, "编辑失败，已经开始评卷");
                return "redirect:/admin/exam/paper/detail?subjectCode=" + question.getSubjectCode() + "&pageNumber="
                        + query.getPageNumber() + "&code=" + query.getCode() + "&category=" + query.getCategory()
                        + "&level=" + query.getLevel() + "&upload=" + u + "&totalScoreNotEqual=" + t;
            }
        }
        SelectiveGroup selectiveGroup = selectiveGroupService.findOne(examId, old.getSubjectCode(),
                old.getMainNumber());
        if (!old.isObjective() && selectiveGroup != null) {
            addMessage(redirectAttributes, "编辑失败，已经存在选做题分组");
            return "redirect:/admin/exam/paper/detail?subjectCode=" + question.getSubjectCode() + "&pageNumber="
                    + query.getPageNumber() + "&code=" + query.getCode() + "&category=" + query.getCategory()
                    + "&level=" + query.getLevel() + "&upload=" + u + "&totalScoreNotEqual=" + t;
        }
        List<String> error = new LinkedList<String>();
        if (validate(question, error, new HashMap<Integer, String>())) {
            old.setMainNumber(question.getMainNumber());
            old.setSubNumber(question.getSubNumber());
            old.setMainTitle(question.getMainTitle());
            old.setName(question.getName());
            old.setTotalScore(question.getTotalScore());
            old.setIntervalScore(question.getIntervalScore());
            old.setObjectivePolicy(question.getObjectivePolicy());
            old.setAnswer(question.getAnswer().toUpperCase());
            old.setPaperType(question.getPaperType());
            old.setType(question.getType());
            old.setCustomizePolicy(question.getCustomizePolicy());
            questionService.saveAndFlush(old);
            questionService.updateMainTitle(examId, question.getSubjectCode(), question.isObjective(),
                    question.getMainNumber(), question.getMainTitle());
            subjectService.updateScore(examId, question.getSubjectCode(), question.isObjective(),
                    questionService.sumTotalScore(examId, question.getSubjectCode(), question.isObjective()));
            // 更新分组总分
            if (!old.isObjective() && old.getGroupNumber() != null) {
                markerService.logoutByExamIdAndSubjectCodeAndGroupNumber(examId, old.getSubjectCode(),
                        old.getGroupNumber());
                MarkGroup group = groupService.findOne(examId, old.getSubjectCode(), old.getGroupNumber());
                List<ExamQuestion> questionGroup = questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(
                        examId, question.getSubjectCode(), false, old.getGroupNumber());
                BigDecimal totalScore = BigDecimal.ZERO;
                for (ExamQuestion q : questionGroup) {
                    totalScore = totalScore.add(BigDecimal.valueOf(q.getTotalScore()));
                }
                group.setTotalScore(totalScore.doubleValue());
                groupService.save(group);
            }
        }
        if (error.size() > 0) {
            addMessage(redirectAttributes, StringUtils.join(error, "<br\\>"));
        }
        return "redirect:/admin/exam/paper/detail?subjectCode=" + question.getSubjectCode() + "&pageNumber="
                + query.getPageNumber() + "&code=" + query.getCode() + "&category=" + query.getCategory() + "&level="
                + query.getLevel() + "&upload=" + u + "&totalScoreNotEqual=" + t;
    }

    @Logging(menu = "删除题目", type = LogType.DELETE)
    @RequestMapping(value = "/question-delete/{questionId}", method = RequestMethod.GET)
    @RoleRequire(Role.SCHOOL_ADMIN)
    public String delete(HttpServletRequest request, RedirectAttributes redirectAttributes,
            @PathVariable Integer questionId, ExamSubjectSearchQuery query,
            @RequestParam(required = false) Boolean upload) {
        ExamQuestion question = questionService.findById(questionId);
        String subjectCode = question.getSubjectCode();
        SelectiveGroup selectiveGroup = selectiveGroupService.findOne(question.getExamId(), question.getSubjectCode(),
                question.getMainNumber());
        if (selectiveGroup != null) {
            addMessage(redirectAttributes, "删除失败，该题目已有分组或选做题存在选做题分组");
        }
        if (!question.isObjective() && question.getGroupNumber() != null) {
            addMessage(redirectAttributes, "删除失败，该题目已有分组或选做题存在选做题分组");
        }
        if (lockService.trylock(LockType.QUESTION_DELETE, question.getExamId(), question.getSubjectCode(),
                question.getMainNumber(), question.getSubNumber())) {
            List<ExamQuestion> list = new ArrayList<ExamQuestion>();
            list.add(question);
            taskExecutor.submit(new QuestionDeleteThread(list, markService, lockService));
            RequestUtils.setLog(request, "开始删除题目，subjectCode：" + subjectCode + " main number:"
                    + question.getMainNumber() + " sub number:" + question.getSubNumber());
        }
        String u = upload == null ? "" : upload.toString();
        String t = query.getTotalScoreNotEqual() == null ? "" : query.getTotalScoreNotEqual().toString();
        return "redirect:/admin/exam/paper/detail?subjectCode=" + subjectCode + "&pageNumber=" + query.getPageNumber()
                + "&code=" + query.getCode() + "&category=" + query.getCategory() + "&level=" + query.getLevel()
                + "&upload=" + u + "&totalScoreNotEqual=" + t;
    }

    @Logging(menu = "客观题统分", type = LogType.UPDATE)
    @RequestMapping("/calculate")
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public ModelAndView calculate(HttpServletRequest request, RedirectAttributes redirectAttributes,
            @RequestParam(required = false) String subjectCode) {
        WebUser wu = RequestUtils.getWebUser(request);
        Exam exam = examService.findById(getSessionExamId(request));
        if (exam == null || !exam.getSchoolId().equals(wu.getUser().getSchoolId())) {
            addMessage(redirectAttributes, "请选择正确的考试");
        } else {
            Set<String> subjectSet = new HashSet<String>();
            // 整个考试加锁
            boolean examLock = false;
            if (subjectCode == null) {
                subjectSet = getCalculateSubjectCodes(exam.getId());
                examLock = true;
            } else 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, examLock);
                taskExecutor.submit(thread);
            }
        }
        return new ModelAndView("redirect:/admin/exam/paper");
    }

    private Set<String> getCalculateSubjectCodes(Integer examId) {
        List<ExamSubject> list = subjectService.list(examId);
        Set<String> set = new HashSet<>();
        for (ExamSubject subject : list) {
            if (!lockService.isLocked(LockType.SCORE_CALCULATE, subject.getExamId(), subject.getCode())) {
                set.add(subject.getCode());
            }
        }
        return set;
    }

    @Logging(menu = "科目成绩分析计算", type = LogType.UPDATE)
    @RequestMapping("/report")
    @RoleRequire(Role.SCHOOL_ADMIN)
    public ModelAndView report(HttpServletRequest request, RedirectAttributes redirectAttributes,
            @RequestParam(required = false) String subjectCode) {
        WebUser wu = RequestUtils.getWebUser(request);
        Exam exam = examService.findById(getSessionExamId(request));
        if (exam == null || !exam.getSchoolId().equals(wu.getUser().getSchoolId())) {
            addMessage(redirectAttributes, "请选择正确的考试");
        } else {
            Set<String> subjectSet = new HashSet<String>();
            // 整个考试加锁
            boolean examLock = false;
            if (subjectCode == null) {
                examLock = true;
                List<ExamSubject> list = subjectService.list(exam.getId());
                for (ExamSubject subject : list) {
                    if (!lockService.isLocked(LockType.SCORE_CALCULATE, subject.getExamId(), subject.getCode())) {
                        subjectSet.add(subject.getCode());
                    }
                }
            } else if (!lockService.isLocked(LockType.SCORE_CALCULATE, exam.getId(), subjectCode)) {
                subjectSet.add(subjectCode);
            }
            if (!subjectSet.isEmpty()) {
                Map<Integer, Set<String>> map = new HashMap<Integer, Set<String>>();
                map.put(exam.getId(), subjectSet);
                ScoreReportThread thread = new ScoreReportThread(map, reportService, examLock);
                taskExecutor.submit(thread);
            }
        }
        return new ModelAndView("redirect:/admin/exam/paper");
    }

    @RequestMapping("/question/query")
    @ResponseBody
    public JSONArray questionQuery(HttpServletRequest request, @RequestParam String subjectCode) {
        int examId = getSessionExamId(request);
        JSONArray array = new JSONArray();
        List<ExamQuestion> list = questionService.findMainByExamAndSubjectAndObjective(examId, subjectCode, false);
        for (ExamQuestion question : list) {
            JSONObject obj = new JSONObject();
            obj.accumulate("mainNumber", question.getMainNumber());
            obj.accumulate("mainTitle", question.getMainTitle());
            array.add(obj);
        }
        return array;
    }

    @Logging(menu = "批量删除题目", type = LogType.DELETE)
    @RequestMapping("/question/delete")
    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER, Role.COLLEGE_ADMIN })
    public String delete(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes,
            @RequestParam Integer[] ids, ExamSubjectSearchQuery query, @RequestParam(required = false) Boolean upload) {
        List<ExamQuestion> list = new ArrayList<ExamQuestion>();
        List<ExamQuestion> error = new ArrayList<ExamQuestion>();
        for (Integer id : ids) {
            ExamQuestion question = questionService.findById(id);
            if (question != null) {
                SelectiveGroup selectiveGroup = selectiveGroupService.findOne(question.getExamId(),
                        question.getSubjectCode(), question.getMainNumber());
                if (selectiveGroup != null) {
                    error.add(question);
                    continue;
                }
                if (!question.isObjective() && question.getGroupNumber() != null) {
                    error.add(question);
                    continue;
                }
                if (lockService.trylock(LockType.QUESTION_DELETE, question.getExamId(), question.getSubjectCode(),
                        question.getMainNumber(), question.getSubNumber())) {
                    list.add(question);
                    RequestUtils.setLog(request, "开始删除题目，subjectCode：" + question.getSubjectCode() + " main number:"
                            + question.getMainNumber() + " sub number:" + question.getSubNumber());
                }
            }
        }
        if (!list.isEmpty()) {
            taskExecutor.submit(new QuestionDeleteThread(list, markService, lockService));
        }
        if (!error.isEmpty()) {
            addMessage(redirectAttributes, "部分题目已有分组或选做题存在选做题分组，无法被删除");
        }
        String u = upload == null ? "" : upload.toString();
        String t = query.getTotalScoreNotEqual() == null ? "" : query.getTotalScoreNotEqual().toString();
        return "redirect:/admin/exam/paper/detail?subjectCode=" + query.getCode() + "&pageNumber="
                + query.getPageNumber() + "&code=" + query.getCode() + "&category=" + query.getCategory() + "&level="
                + query.getLevel() + "&upload=" + u + "&totalScoreNotEqual=" + t;
    }

    @RequestMapping(value = "/question/add", method = RequestMethod.GET)
    @RoleRequire(Role.SCHOOL_ADMIN)
    public String add(Model model, @RequestParam String subjectCode, ExamSubjectSearchQuery query,
            @RequestParam(required = false) Boolean upload) {
        model.addAttribute("examQuestion", new ExamQuestion());
        model.addAttribute("objectivePolicyList", ObjectivePolicy.values());
        model.addAttribute("questionTypeList", QuestionType.values());
        model.addAttribute("query", query);
        model.addAttribute("upload", upload);
        model.addAttribute("subjectCode", subjectCode);
        return "modules/exam/questionAdd";
    }

    @Logging(menu = "新增题目", type = LogType.ADD)
    @RequestMapping(value = "/question/save", method = RequestMethod.POST)
    @RoleRequire(Role.SCHOOL_ADMIN)
    public String save(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes,
            ExamQuestion question, ExamSubjectSearchQuery query, @RequestParam(required = false) Boolean upload) {
        if (question.getCustomizePolicy() != null) {
            question.setCustomizePolicy(StringEscapeUtils.unescapeHtml(question.getCustomizePolicy()));
        }
        int examId = getSessionExamId(request);
        ExamQuestion old = questionService.findByExamAndSubjectAndObjectiveAndMainNumberAndSubNumber(examId,
                question.getSubjectCode(), question.isObjective(), question.getMainNumber(), question.getSubNumber());
        if (old != null) {
            addMessage(redirectAttributes, "新增失败，题号已存在");
            String u = upload == null ? "" : upload.toString();
            String t = query.getTotalScoreNotEqual() == null ? "" : query.getTotalScoreNotEqual().toString();
            return "redirect:/admin/exam/paper/detail?subjectCode=" + question.getSubjectCode() + "&pageNumber="
                    + query.getPageNumber() + "&code=" + query.getCode() + "&category=" + query.getCategory()
                    + "&level=" + query.getLevel() + "&upload=" + u + "&totalScoreNotEqual=" + t;
        }
        question.setExamId(examId);
        if (StringUtils.isBlank(question.getPaperType())) {
            question.setPaperType(NULL_PAPER_TYPE_PLACEHOLDER);
        }
        if (question.isObjective()) {
            question.setGroupNumber(0);
        } else {
            question.setObjectivePolicy(null);
            question.setAnswer(null);
            question.setType(null);
        }
        Map<Integer, String> titleMap = new HashMap<>();
        List<ExamQuestion> current = questionService.findByExamAndSubjectAndObjective(examId, question.getSubjectCode(),
                question.isObjective());
        for (ExamQuestion q : current) {
            titleMap.put(q.getMainNumber(), q.getMainTitle());
        }
        List<String> error = new LinkedList<String>();
        if (validate(question, error, titleMap)) {
            questionService.save(question);
            subjectService.updateScore(examId, question.getSubjectCode(), question.isObjective(),
                    questionService.sumTotalScore(examId, question.getSubjectCode(), question.isObjective()));
            if (!question.isObjective()) {
                studentService.updateSubjectiveStatusAndScore(examId, question.getSubjectCode(),
                        SubjectiveStatus.UNMARK, 0, null);
                inspectHistoryService.deleteByExamIdAndSubjectCode(examId, question.getSubjectCode());
            }
        }
        if (error.size() > 0) {
            addMessage(redirectAttributes, StringUtils.join(error, "<br\\>"));
        }
        String u = upload == null ? "" : upload.toString();
        String t = query.getTotalScoreNotEqual() == null ? "" : query.getTotalScoreNotEqual().toString();
        return "redirect:/admin/exam/paper/detail?subjectCode=" + question.getSubjectCode() + "&pageNumber="
                + query.getPageNumber() + "&code=" + query.getCode() + "&category=" + query.getCategory() + "&level="
                + query.getLevel() + "&upload=" + u + "&totalScoreNotEqual=" + t;
    }

    private boolean validate(ExamQuestion question, List<String> error, Map<Integer, String> titleMap) {
        if (countDecimalPlaces(question.getTotalScore()) > 2) {
            error.add("有满分为小数超2位的记录");
            return false;
        }
        if (question.isObjective() && StringUtils.isBlank(question.getAnswer())) {
            error.add("有答案为空的记录");
            return false;
        }
        if (question.isObjective() && question.getType() == null) {
            error.add("有题型为空的记录");
            return false;
        }
        if (QuestionType.TRUE_OR_FALSE.equals(question.getType())
                && (!question.getAnswer().contains("A") && !question.getAnswer().contains("B"))) {
            error.add("有判断题标答不为A或B的记录");
            return false;
        }
        if (QuestionType.SINGLE.equals(question.getType()) && question.getAnswer().length() > 1) {
            error.add("有单选题标答大于一个的记录");
            return false;
        }
        String title = titleMap.get(question.getMainNumber());
        if (title != null && !title.equals(question.getMainTitle())) {
            error.add("有名称不一致的记录");
            return false;
        }
        if (question.isObjective()) {
            question.setAnswer(question.getAnswer().toUpperCase());
        }
        return true;
    }

    public int countDecimalPlaces(double number) {
        String num = Double.toString(number);
        int decimalPointIndex = num.indexOf('.');
        if (decimalPointIndex == -1) {
            return 0;
        }
        return num.length() - decimalPointIndex - 1;
    }

    @Logging(menu = "导入数据包", type = LogType.IMPORT_FILE)
    @RequestMapping(value = "/importData", method = RequestMethod.POST)
    @RoleRequire(Role.SCHOOL_ADMIN)
    public String importData(HttpServletRequest request, MultipartFile file, RedirectAttributes redirectAttributes) {
        int examId = getSessionExamId(request);
        List<String> error = new ArrayList<String>();
        try {
            byte[] data = IOUtils.toByteArray(file.getInputStream());
            File target = new File(tempFile + File.separator + "temp.zip");
            target.getParentFile().mkdirs();
            FileOutputStream ous = new FileOutputStream(target);
            ous.write(data);
            IOUtils.closeQuietly(ous);

            ZipReader zipReader = new ZipReader(target);
            for (String subjectCode : zipReader.list(false)) {
                try {
                    if (subjectCode.equalsIgnoreCase("cards")) {
                        error = this.parseCard(examId, error, zipReader);
                    } else {
                        error = this.parseSubject(examId, subjectCode, error, zipReader);
                    }
                } catch (Exception e) {
                    log.error("parse zip data error", e);
                    error.add("[" + subjectCode + "] 科目代码导入失败；");
                }
            }
            target.delete();
        } catch (Exception e) {
            log.error("parse question excel error", e);
            error.add("zip文件解析失败");
        }
        if (error.size() > 0) {
            addMessage(redirectAttributes, StringUtils.join(error, "<br\\>"));
        }
        return "redirect:/admin/exam/paper";
    }

    private List<String> parseCard(int examId, List<String> error, ZipReader zipReader) throws IOException {
        InputStream ins = zipReader.read("cards", "description.json");
        List<AnswerCardSubjectFile> courseList = AnswerCardSubjectFile.parse(ins);
        for (AnswerCardSubjectFile answerCardSubjectFile : courseList) {
            try {
                InputStream cardIns = zipReader.read("cards", answerCardSubjectFile.getCode() + ".json");
                CardFile cardFile = CardFile.parse(cardIns);
                if (cardFile.getPages().size() != 1 && cardFile.getPages().size() % 2 != 0) {
                    error.add("[" + answerCardSubjectFile.getCode() + "] 科目代码卡格式解析有误；");
                    return error;
                }
                byte[] out = cardFile.output();
                String md5 = BinaryUtil.encodeMD5(out);
                AnswerCard card = answerCardService.findByExamIdAndCode(examId, answerCardSubjectFile.getCode());
                if (card == null) {
                    card = new AnswerCard();
                    card.setExamId(examId);
                    Integer number = answerCardService.findMaxNumberByExamId(examId) + 1;
                    card.setNumber(number);
                    card.setCode(answerCardSubjectFile.getCode());
                }
                card.setMd5(md5);
                card.setNeedAdapte(true);
                card.setSliceConfig(cardFile.getSliceConfig().toString());
                card.setSinglePage(cardFile.getPages().size() == 1);
                card.setPaperCount(card.getSinglePage() ? 1 : cardFile.getPages().size() / 2);
                card.setSource(CardSource.WEB);
                card.setUpdateTime(new Date());
                List<String> asList = new ArrayList<String>();
                List<ExamSubject> subjects = new ArrayList<ExamSubject>();
                List<ExamQuestion> questions = new ArrayList<ExamQuestion>();
                List<MarkGroup> groups = new ArrayList<MarkGroup>();
                for (AnswerCardSubjectItem item : answerCardSubjectFile.getCourses()) {
                    ExamSubject subject = subjectService.find(examId, item.getCode());
                    if (subject == null) {
                        error.add("[" + item.getCode() + "] 科目代码不存在；");
                        return error;
                    }
                    // if (answerCardService.hasSubject(examId, item.getCode())) {
                    // error.add("[" + item.getCode() + "] 科目代码已经绑定卡格式；");
                    // return error;
                    // }
                    if (!cardFile.getPaperStruct().isEmpty() && subject.getSubjectiveScore() > 0) {
                        error.add("[" + item.getCode() + "] 科目代码已经有试卷结构或分组；");
                        return error;
                    }
                    if (!cardFile.getPaperStruct().isEmpty() && subject.getSubjectiveScore() > 0) {
                        error.add("[" + item.getCode() + "] 科目代码已经有试卷结构或分组；");
                        return error;
                    } else {
                        asList.add(item.getCode());
                        subject.setSliceConfig(cardFile.getSliceConfig().toString());
                        subject.setCardType(FormatType.JSON);
                        subjects.add(subject);
                        for (CardQuestion q : cardFile.getPaperStruct()) {
                            ExamQuestion e = new ExamQuestion();
                            e.setExamId(examId);
                            e.setSubjectCode(subject.getCode());
                            e.setMainNumber(q.getMainNumber());
                            e.setMainTitle(q.getMainTitle());
                            e.setSubNumber(q.getSubNumber().toString());
                            QuestionType questionType = QuestionType.findByValue(q.getType());
                            e.setType(questionType);
                            if (questionType == null) {
                                e.setObjective(false);
                                e.setGroupNumber(q.getGroupNumber());
                            } else {
                                e.setObjective(true);
                                e.setAnswer("#");
                            }
                            e.setPaperType("#");
                            e.setIntervalScore(q.getIntervalScore());
                            e.setTotalScore(q.getScore());
                            questions.add(e);
                        }
                        for (CardGroup g : cardFile.getGroupInfos()) {
                            MarkGroup group = new MarkGroup(examId, subject.getCode(), g.getGroupNumber(),
                                    g.getSliceConfig(), g.getTotalScore(), 0d, null, null, MarkMode.TRACK.toString(), 0,
                                    false, false, null, false);
                            groups.add(group);
                        }
                    }
                }
                answerCardService.save(card, asList);
                fileService.uploadAnswerCard(new ByteArrayInputStream(out), md5, examId, card.getNumber());
                questionService.save(questions);
                for (MarkGroup group : groups) {
                    groupService.save(group);
                }
                for (ExamSubject examSubject : subjects) {
                    subjectService.save(examSubject);
                    subjectService.updateScore(examId, examSubject.getCode(), false,
                            questionService.sumTotalScore(examId, examSubject.getCode(), false));
                    subjectService.updateScore(examId, examSubject.getCode(), true,
                            questionService.sumTotalScore(examId, examSubject.getCode(), true));
                }
            } catch (Exception e) {
                e.printStackTrace();
                log.error("parse zip data error", e);
                error.add("[" + answerCardSubjectFile.getCode() + "] 科目代码卡格式解析有误；");
                return error;
            }
        }
        return error;
    }

    private List<String> parseSubject(int examId, String subjectCode, List<String> error, ZipReader zipReader)
            throws Exception {
        ExamSubject subject = subjectService.find(examId, subjectCode);
        if (subject == null) {
            error.add("[" + subjectCode + "] 科目代码不存在；");
            return error;
        }
        if (subject.getTotalScore() != 0) {
            error.add("[" + subjectCode + "] 科目代码已经存在题目；");
            return error;
        }
        InputStream cardIns = zipReader.read(subjectCode, "card.json");
        CardFile cardFile = CardFile.parse(cardIns);
        if (cardFile.getPages().size() != 1 && cardFile.getPages().size() % 2 != 0) {
            error.add("[" + subjectCode + "] 科目代码卡格式解析有误；");
            return error;
        }
        byte[] out = cardFile.output();
        String md5 = BinaryUtil.encodeMD5(out);
        AnswerCard card = answerCardService.findByExamIdAndSubjectCode(examId, subject.getCode(), CardSource.WEB);
        if (card == null) {
            card = new AnswerCard();
            card.setExamId(examId);
            Integer number = answerCardService.findMaxNumberByExamId(examId) + 1;
            card.setNumber(number);
        }
        card.setMd5(md5);
        card.setNeedAdapte(true);
        card.setSliceConfig(cardFile.getSliceConfig().toString());
        card.setSinglePage(cardFile.getPages().size() == 1);
        card.setPaperCount(card.getSinglePage() ? 1 : cardFile.getPages().size() / 2);
        card.setSource(CardSource.WEB);
        card.setUpdateTime(new Date());
        answerCardService.save(card, subjectCode);

        fileService.uploadAnswerCard(new ByteArrayInputStream(out), md5, examId, card.getNumber());
        subject.setSliceConfig(cardFile.getSliceConfig().toString());
        subject.setCardType(FormatType.JSON);
        subjectService.save(subject);

        InputStream structIns = zipReader.read(subjectCode, "struct.json");
        StructFile structFile = StructFile.parse(structIns);
        if (!structFile.getObjective().isEmpty()) {
            List<ExamQuestion> oList = new ArrayList<ExamQuestion>();
            for (StructQuestion structQuestion : structFile.getObjective()) {
                ExamQuestion q = structQuestion.transform();
                q.setPaperType(structFile.getPaperType());
                q.setExamId(examId);
                q.setSubjectCode(subject.getCode());
                q.setObjective(true);
                oList.add(q);
            }
            questionService.save(oList);
            examService.updateObjectiveStatus(examId, ObjectiveStatus.WAITING);
            subjectService.updateScore(examId, subject.getCode(), true,
                    questionService.sumTotalScore(examId, subject.getCode(), true));
        }
        if (!structFile.getSubjective().isEmpty()) {
            List<ExamQuestion> sList = new ArrayList<ExamQuestion>();
            Map<Integer, Double> mainMap = new HashMap<Integer, Double>();
            for (StructQuestion structQuestion : structFile.getSubjective()) {
                ExamQuestion q = structQuestion.transform();
                q.setPaperType(structFile.getPaperType());
                q.setExamId(examId);
                q.setSubjectCode(subject.getCode());
                q.setObjective(false);
                q.setGroupNumber(q.getMainNumber());
                sList.add(q);
                Double totalScore = mainMap.get(q.getMainNumber());
                if (totalScore == null) {
                    mainMap.put(q.getMainNumber(), q.getTotalScore());
                } else {
                    mainMap.put(q.getMainNumber(), totalScore + q.getTotalScore());
                }
            }
            questionService.save(sList);
            for (Integer number : mainMap.keySet()) {
                MarkGroup group = new MarkGroup(examId, subject.getCode(), number, null, mainMap.get(number), null,
                        null, null, null, 0, false, false, null, false);
                group.setPicList(cardFile.getMarkConfigAdd(cardFile.getMarkConfig(number), 0.015));
                groupService.save(group);
            }
            subjectService.updateScore(examId, subject.getCode(), false,
                    questionService.sumTotalScore(examId, subject.getCode(), false));
        }
        return error;
    }

}
