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

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.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.MarkGroupService;
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.biz.report.utils.ReportContext;
import cn.com.qmth.stmms.biz.utils.ScoreCalculateUtil;
import cn.com.qmth.stmms.biz.utils.ScoreInfo;
import cn.com.qmth.stmms.common.enums.LockType;

public class ScoreCalculateThread implements Runnable {

    protected static Logger log = LoggerFactory.getLogger(ScoreCalculateThread.class);

    private ExamStudentService studentService;

    private ExamQuestionService questionService;

    private MarkService markService;

    private int examId;

    private LockService lockService;

    private ReportService reportService;

    private Map<String, List<ExamQuestion>> objectiveMap;

    private Map<String, List<ExamQuestion>> subjectiveMap;

    private Map<String, ExamSubject> subjectMap;

    private Map<String, List<MarkGroup>> groupMap;

    private MarkGroupService groupService;

    private ExamSubjectService subjectService;

    private ExamService examService;

    private ReportContext context;

    public ScoreCalculateThread(int examId, LockService lockService, ExamStudentService studentService,
            ExamQuestionService questionService, MarkService markService, ReportService reportService,
            ExamService examService, ExamSubjectService subjectService, MarkGroupService groupService) {
        this.examId = examId;
        this.lockService = lockService;
        this.studentService = studentService;
        this.questionService = questionService;
        this.markService = markService;
        this.reportService = reportService;
        this.examService = examService;
        this.subjectService = subjectService;
        this.groupService = groupService;
        this.objectiveMap = new HashMap<String, List<ExamQuestion>>();
        this.subjectiveMap = new HashMap<String, List<ExamQuestion>>();
        this.subjectMap = new HashMap<String, ExamSubject>();
        this.groupMap = new HashMap<String, List<MarkGroup>>();
    }

    @Override
    public void run() {
        // 尝试上锁
        if (!lockService.trylock(LockType.SCORE_CALCULATE, examId)) {
            log.info("calculate lock faile for examId=" + examId);
            return;
        }
        // 上锁成功正式开始
        log.info("start calculate for examId=" + examId);
        try {
            // 获取考试信息
            Exam exam = examService.findById(examId);
            // 删除原有统计数据
            reportService.deleteData(examId);
            context = new ReportContext(exam);
            // checkStudentService.deleteByExamId(examId);
            long totalCount = studentService.countByExamId(examId);

            int pageNumber = 1;
            int pageSize = 1000;
            List<ExamStudent> list = studentService.findByExamId(examId, pageNumber, pageSize);
            while (list != null && list.size() > 0) {
                for (ExamStudent student : list) {
                    // 先统分
                    calculate(student);
                    // 后统计
                    statistic(student);
                }
                double process = pageSize * pageNumber * 1.0 / totalCount;
                if (process >= 100) {
                    process = 99;
                }
                examService.updateProcess(examId, process);
                pageNumber++;
                list = studentService.findByExamId(examId, pageNumber, pageSize);
            }

            // 统计识别缺考的试卷
            // List<ExamStudent> students =
            // studentService.findByExamIdAndUploadAndAbsent(examId, true, true,
            // 1,
            // Integer.MAX_VALUE);
            // for (ExamStudent examStudent : students) {
            // CheckStudent student = new CheckStudent(examStudent.getId(),
            // examId, examStudent.getSubjectCode(),
            // CheckType.SCAN_ABSENT);
            // checkStudentService.save(student);
            // }

            // 生成报表数据
            // reportService.generateReportData(examId);
            context.save();
            // 统分结束修改标记
            examService.updateNeedCalculate(examId, false);
            examService.updateProcess(examId, null);
        } catch (Exception e) {
            log.error("calculate exception for examId=" + examId, e);
        } finally {
            lockService.unlock(LockType.SCORE_CALCULATE, examId);
            log.info("finish calculate for examId=" + examId);
        }
    }

    private void calculate(ExamStudent student) {
        // 未上传或缺考考生不统分
        if (!student.isUpload() || student.isAbsent()) {
            return;
        }
        try {
            ScoreCalculateUtil util = ScoreCalculateUtil.instance(student);
            ScoreInfo info = util.calculate(findQuestionList(student.getSubjectCode(), student.getPaperType(), true),
                    null);

            student.setObjectiveScore(info.getObjectiveScore());
            student.setScoreList(info.getScoreList(), true);

            studentService.save(student);

            // 增加主观题总分统计
            markService.scoreCalculate(student, findMarkGroup(student.getSubjectCode()));

            // 客观题0分主观题有分的考生
            // ExamSubject subject = findExamSubject(student.getSubjectCode()) ;
            // if (subject.getObjectiveScore()>0 && student.getObjectiveScore()
            // == 0 && student.getSubjectiveScore() != 0) {
            // CheckStudent cs = new CheckStudent(student.getId(), examId,
            // student.getSubjectCode(),
            // CheckType.EXCEPTION);
            // checkStudentService.save(cs);
            // }
        } catch (Exception e) {
            log.error("calculate error for studentId=" + student.getId(), e);
        }
    }

    private void statistic(ExamStudent student) {
        student.setSubject(findExamSubject(student.getSubjectCode()));
        student.setObjectiveQuestionList(findQuestionList(student.getSubjectCode(), student.getPaperType(), true));
        student.setSubjectiveQuestionList(findQuestionList(student.getSubjectCode(), student.getPaperType(), false));
        context.process(student);
    }

    private List<ExamQuestion> findQuestionList(String subjectCode, String paperType, boolean objective) {
        if (objective) {
            String key = subjectCode + "_" + StringUtils.trimToEmpty(paperType);
            List<ExamQuestion> list = objectiveMap.get(key);
            if (list == null) {
                list = questionService.findByExamAndSubjectAndObjectiveAndPaperType(examId, subjectCode, true,
                        paperType);
                objectiveMap.put(key, list);
            }
            return list;
        } else {
            List<ExamQuestion> list = subjectiveMap.get(subjectCode);
            if (list == null) {
                list = questionService.findByExamAndSubjectAndObjective(examId, subjectCode, false);
                subjectiveMap.put(subjectCode, list);
            }
            return list;
        }
    }

    private ExamSubject findExamSubject(String subjectCode) {
        ExamSubject subject = subjectMap.get(subjectCode);
        if (subject == null) {
            subject = subjectService.find(examId, subjectCode);
            subjectMap.put(subjectCode, subject);
        }
        return subject;
    }

    private List<MarkGroup> findMarkGroup(String subjectCode) {
        List<MarkGroup> list = groupMap.get(subjectCode);
        if (list == null) {
            list = groupService.findByExamAndSubject(examId, subjectCode);
            groupMap.put(subjectCode, list);
        }
        return list;
    }
}
