package cn.com.qmth.stmms.biz.mark.service.Impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cn.com.qmth.stmms.biz.campus.model.Campus;
import cn.com.qmth.stmms.biz.exam.dao.ExamQuestionDao;
import cn.com.qmth.stmms.biz.exam.dao.MarkGroupDao;
import cn.com.qmth.stmms.biz.exam.dao.MarkerDao;
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.MarkGroup;
import cn.com.qmth.stmms.biz.exam.model.Marker;
import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
import cn.com.qmth.stmms.biz.mark.dao.ArbitrateHistoryDao;
import cn.com.qmth.stmms.biz.mark.dao.MarkLibraryDao;
import cn.com.qmth.stmms.biz.mark.dao.MarkSpecialTagDao;
import cn.com.qmth.stmms.biz.mark.dao.MarkTrackDao;
import cn.com.qmth.stmms.biz.mark.model.ArbitrateHistory;
import cn.com.qmth.stmms.biz.mark.model.MarkLibrary;
import cn.com.qmth.stmms.biz.mark.model.MarkSpecialTag;
import cn.com.qmth.stmms.biz.mark.model.MarkTrack;
import cn.com.qmth.stmms.biz.mark.service.MarkService;
import cn.com.qmth.stmms.biz.user.model.User;
import cn.com.qmth.stmms.biz.utils.CurrentTaskUtil;
import cn.com.qmth.stmms.biz.utils.ScoreItem;
import cn.com.qmth.stmms.common.enums.HistoryStatus;
import cn.com.qmth.stmms.common.enums.LibraryStatus;
import cn.com.qmth.stmms.common.enums.ScorePolicy;

/**
 * 与评卷相关的所有修改操作(非传播性的新增操作除外)，全部汇总到这里进行集中控制
 * 
 * @author luoshi
 *
 */
@Service("markService")
public class MarkServiceImpl implements MarkService {

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

    @Autowired
    private ExamStudentService studentService;

    @Autowired
    private ExamSubjectService subjectService;

    @Autowired
    private MarkGroupDao groupDao;

    @Autowired
    private ExamQuestionDao questionDao;

    @Autowired
    private MarkerDao markerDao;

    @Autowired
    private MarkLibraryDao libraryDao;

    @Autowired
    private ArbitrateHistoryDao arbitrateDao;

    @Autowired
    private MarkTrackDao trackDao;

    @Autowired
    private MarkSpecialTagDao specialTagDao;

    @Autowired
    private MarkLockService lockService;

    @Value("${mark.cleanTimeoutMinute}")
    private long timeoutMinute;

    /**
     * 某个大题已申请的评卷任务数量
     * 
     * @param group
     * @return
     */
    @Override
    public int applyCount(MarkGroup group) {
        return CurrentTaskUtil.count(group.getExamId(), group.getSubjectCode(), group.getNumber());
    }

    /**
     * 某个评卷员已申请的评卷任务数量
     * 
     * @param marker
     * @return
     */
    @Override
    public int applyCount(Marker marker) {
        return CurrentTaskUtil.count(marker);
    }

    /**
     * 释放某个大题的锁定任务
     * 
     * @param group
     */
    @Override
    public void releaseByGroup(MarkGroup group) {
        CurrentTaskUtil.clear(group.getExamId(), group.getSubjectCode(), group.getNumber());
    }

    /**
     * 重置某个大题的所有评卷任务
     * 
     * @param group
     */
    @Override
    @Transactional
    public void resetByGroup(MarkGroup group) {
        try {
            lockService.lockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
            resetGroup(group);
        } catch (Exception e) {
            throw e;
        } finally {
            lockService.unlockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
        }
    }

    /**
     * 删除某个大题
     * 
     * @param group
     */
    @Override
    @Transactional
    public void deleteGroup(MarkGroup group) {
        try {
            lockService.lockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
            questionDao.deleteByExamIdAndSubjectCodeAndObjectiveAndMainNumber(group.getExamId(), group.getSubjectCode(),
                    false, group.getNumber());
            libraryDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
                    group.getNumber());
            arbitrateDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
                    group.getNumber());
            markerDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
                    group.getNumber());
            subjectService.updateScore(group.getExamId(), group.getSubjectCode());

            releaseByGroup(group);
            groupDao.delete(group);
        } catch (Exception e) {
            throw e;
        } finally {
            lockService.unlockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
        }
    }

    /**
     * 修改某个大题给分步骤，并重置评卷任务
     * 
     * @param group
     */
    @Override
    @Transactional
    public void updateGroup(MarkGroup group, List<Double> scores, ScorePolicy policy) {
        try {
            lockService.lockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());

            questionDao.deleteByExamIdAndSubjectCodeAndObjectiveAndMainNumber(group.getExamId(), group.getSubjectCode(),
                    false, group.getNumber());
            int i = 0;
            double totalScore = 0d;
            for (Double score : scores) {
                i++;
                totalScore += score;
                ExamQuestion question = new ExamQuestion();
                question.setExamId(group.getExamId());
                question.setSubjectCode(group.getSubjectCode());
                question.setMainTitle(group.getTitle());
                question.setMainNumber(group.getNumber());
                question.setSubNumber(i);
                question.setObjective(false);
                question.setTotalScore(score);
                question.setIntervalScore(1d);
                questionDao.saveAndFlush(question);
            }
            groupDao.updateTotalScore(group.getExamId(), group.getSubjectCode(), group.getNumber(), totalScore);
            groupDao.updateScorePolicy(group.getExamId(), group.getSubjectCode(), group.getNumber(), policy);
            subjectService.updateScore(group.getExamId(), group.getSubjectCode());
            resetGroup(group);
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        } finally {
            lockService.unlockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
        }
    }

    /**
     * 评卷员申请领取某个评卷任务
     * 
     * @param library
     * @param marker
     */
    @Override
    public boolean applyLibrary(MarkLibrary library, Marker marker) {
        // 查询待领取任务时，已经做了多评同一studentId互斥处理
        // 首先判断多评情况下，同一个studentId是否已被该评卷员处理过
        // if (libraryDao.countByStudentIdAndMarkerId(library.getStudentId(),
        // marker.getId()) > 0) {
        // return false;
        // }
        return CurrentTaskUtil.add(marker, getApplyTaskId(library));
    }

    /**
     * 评卷员是否已领取了某个评卷任务
     * 
     * @param library
     * @param marker
     * @return
     */
    @Override
    public boolean hasApplied(MarkLibrary library, Marker marker) {
        return CurrentTaskUtil.exists(marker, getApplyTaskId(library));
    }

    /**
     * 释放某个评卷员的所有锁定任务
     * 
     * @param marker
     */
    @Override
    public void releaseByMarker(Marker marker) {
        CurrentTaskUtil.clear(marker);
    }

    /**
     * 重置某个评卷员
     * 
     * @param marker
     */
    @Override
    @Transactional
    public void resetMarker(Marker marker) {
        try {
            lockService.waitUnlockGroup(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
            lockService.lockMarker(marker.getId());
            // 仲裁和等待仲裁的任务不被重置
            trackDao.deleteByMarkerId(marker.getId(), LibraryStatus.ARBITRATED, LibraryStatus.WAIT_ARBITRATE);
            specialTagDao.deleteByMarkerId(marker.getId(), LibraryStatus.ARBITRATED, LibraryStatus.WAIT_ARBITRATE);
            libraryDao.resetByMarkerId(marker.getId(), LibraryStatus.WAITING, LibraryStatus.ARBITRATED,
                    LibraryStatus.WAIT_ARBITRATE);
            updateLibraryCount(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
            releaseByMarker(marker);
        } catch (Exception e) {
            throw e;
        } finally {
            lockService.unlockMarker(marker.getId());
        }
    }

    /**
     * 根据考生ID删除评卷任务
     * 
     * @param studentId
     */
    @Override
    @Transactional
    public void deleteByStudent(ExamStudent student) {
        try {
            lockService.lockStudent(student.getId());
            trackDao.deleteByStudentId(student.getId());
            specialTagDao.deleteByStudentId(student.getId());
            libraryDao.deleteByStudentId(student.getId());
            arbitrateDao.deleteByStudentId(student.getId());
            updateLibraryCount(student.getExamId(), student.getSubjectCode());
        } catch (Exception e) {
            throw e;
        } finally {
            lockService.unlockStudent(student.getId());
        }
    }

    /**
     * 评卷员提交某个评卷任务
     * 
     * @param library
     * @param trackMap
     * @param tagList
     */
    @Override
    @Transactional
    public boolean submitLibrary(MarkLibrary library, Marker marker, List<MarkTrack> trackList,
            List<MarkSpecialTag> tagList) {
        // 判断评卷任务记录是否存在，以及评卷员是否有权限操作
        if (library == null || !library.getExamId().equals(marker.getExamId())
                || !library.getSubjectCode().equals(marker.getSubjectCode())
                || !library.getGroupNumber().equals(marker.getGroupNumber())) {
            return false;
        }
        // 判断大题是否存在
        MarkGroup group = groupDao.findOne(library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
        if (group == null) {
            return false;
        }
        // 等待大题释放锁定
        lockService.waitUnlockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
        // 等待考生释放锁定
        lockService.waitUnlockStudent(library.getStudentId());
        // 尝试锁定评卷员
        lockService.lockMarker(library.getMarkerId());
        // 本人是否已领取该任务
        boolean applied = hasApplied(library, marker);

        try {
            // 非本人领取的待评任务
            if (library.getStatus() == LibraryStatus.WAITING && !applied) {
                return false;
            }
            // 非本人的回评任务
            if (library.getStatus() == LibraryStatus.MARKED && !library.getMarkerId().equals(marker.getId())) {
                return false;
            }
            // 校验总分是否超标
            if (library.getMarkerScore() > group.getTotalScore() || StringUtils.isBlank(library.getMarkerScoreList())) {
                return false;
            }
            // 是否多评情况下已处理过该考生评卷任务
            if (libraryDao.countByStudentIdAndMarkerIdAndIdNotEqual(library.getStudentId(), library.getMarkerId(),
                    library.getId()) > 0) {
                return false;
            }
            // 尝试提交评卷结果
            Date now = new Date();
            if (libraryDao.updateMarkerResult(library.getId(), LibraryStatus.MARKED, library.getMarkerId(),
                    library.getMarkerScore(), library.getMarkerScoreList(), now, library.getTags(),
                    LibraryStatus.WAITING, LibraryStatus.MARKED) == 0) {
                // 条件不符更新失败，直接返回
                return false;
            }
            // 保存阅卷轨迹
            if (trackList != null) {
                trackDao.deleteByLibraryId(library.getId());
                trackDao.save(trackList);
            }
            // 保存特殊标记
            if (tagList != null) {
                specialTagDao.deleteByLibraryId(library.getId());
                specialTagDao.save(tagList);
            }
            // 判断多评模式下是否需要仲裁
            ArbitrateHistory history = null;
            if (group.getArbitrateThreshold() != null && group.getArbitrateThreshold() > 0) {
                // 多评模式
                List<MarkLibrary> list = libraryDao.findByStudentIdAndGroupNumberAndStatus(library.getStudentId(),
                        library.getGroupNumber(), LibraryStatus.MARKED);
                for (MarkLibrary other : list) {
                    if (other.getId().equals(library.getId()) || other.getHeaderScore() != null) {
                        // 本评卷任务或组长已打分，则跳过该任务
                        continue;
                    }
                    if (Math.abs(other.getMarkerScore() - library.getMarkerScore()) > group.getArbitrateThreshold()) {
                        // 分差超过阀值，触发仲裁
                        history = new ArbitrateHistory();
                        history.setExamId(library.getExamId());
                        history.setSubjectCode(library.getSubjectCode());
                        history.setGroupNumber(library.getGroupNumber());
                        history.setStudentId(library.getStudentId());
                        history.setExamNumber(library.getExamNumber());
                        history.setStatus(HistoryStatus.WAITING);
                        history.setCreateTime(now);
                        arbitrateDao.saveAndFlush(history);
                        break;
                    }
                }
            }
            if (history != null) {
                // 触发仲裁后续状态更新
                libraryDao.updateByStudentIdAndGroupNumber(library.getStudentId(), library.getGroupNumber(),
                        LibraryStatus.WAIT_ARBITRATE);
            }
            return true;
        } catch (Exception e) {
            throw e;
        } finally {
            // 只有确定本人已领取该任务时，才进行释放操作
            if (applied) {
                CurrentTaskUtil.remove(marker, getApplyTaskId(library));
            }
            lockService.unlockMarker(library.getMarkerId());
        }
    }

    /**
     * 管理员/组长打回某个评卷任务<br>
     * 暂时不用到BACKED状态，直接等同于重置该评卷任务
     * 
     * @param library
     */
    @Override
    @Transactional
    public boolean backLibrary(MarkLibrary library) {
        lockService.waitUnlockGroup(library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
        if (libraryDao.resetById(library.getId(), LibraryStatus.WAITING, LibraryStatus.MARKED) > 0) {
            trackDao.deleteByLibraryId(library.getId());
            specialTagDao.deleteByLibraryId(library.getId());
            updateLibraryCount(library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
            return true;
        } else {
            return false;
        }
    }

    /**
     * 管理员/组长直接对考生打分
     * 
     * @param student
     * @param user
     */
    @Override
    @Transactional
    public void submitStudent(ExamStudent student, User user) {
        // TODO - 下个版本添加此功能
    }

    /**
     * 管理员/组长处理仲裁卷
     * 
     * @param history
     */
    @Override
    @Transactional
    public void processArbitrate(ArbitrateHistory history) {
        lockService.waitUnlockGroup(history.getExamId(), history.getSubjectCode(), history.getGroupNumber());
        arbitrateDao.saveAndFlush(history);
        libraryDao.updateHeaderResult(history.getStudentId(), history.getGroupNumber(), history.getUserId(),
                history.getTotalScore(), history.getScoreList(), history.getUpdateTime(), LibraryStatus.ARBITRATED);
        updateLibraryCount(history.getExamId(), history.getSubjectCode(), history.getGroupNumber());
        scoreCalculate(history.getExamId(), history.getSubjectCode(), history.getStudentId());
    }

    /**
     * 对某个考生某个科目进行主观题统分
     * 
     * @param examId
     * @param subjectCode
     * @param studentId
     */
    @Override
    @Transactional
    public void scoreCalculate(Integer examId, String subjectCode, Integer studentId) {
        List<MarkGroup> groups = groupDao.findByExamIdAndSubjectCode(examId, subjectCode);
        List<ScoreItem> scoreList = new ArrayList<ScoreItem>();
        double totalScore = 0.0;
        // 循环所有大题
        for (MarkGroup group : groups) {
            if (calculateGroup(group, studentId)) {
                totalScore += group.getMarkScore();
                scoreList.addAll(group.getMarkScoreDetail());
            } else {
                // 未评完直接返回
                return;
            }
        }
        // 全部评完，更新考生主观题得分
        studentService.updateSubjectiveScore(studentId, totalScore, ExamStudent.buildScoreList(scoreList));
    }

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

    /**
     * 更新某个大题评卷任务数量
     * 
     * @param examId
     * @param subjectCode
     * @param groupNumber
     */
    @Override
    public void updateLibraryCount(Integer examId, String subjectCode, Integer groupNumber) {
        groupDao.updateLibraryCount(examId, subjectCode, groupNumber);
        groupDao.updateMarkedCount(examId, subjectCode, groupNumber, LibraryStatus.MARKED, LibraryStatus.ARBITRATED);
        groupDao.updateLeftCount(examId, subjectCode, groupNumber);
    }

    /**
     * 更新某个科目所有大题评卷任务数量
     * 
     * @param examId
     * @param subjectCode
     * @param groupNumber
     */
    @Transactional
    @Override
    public void updateLibraryCount(Integer examId, String subjectCode) {
        groupDao.updateLibraryCount(examId, subjectCode);
        groupDao.updateMarkedCount(examId, subjectCode, LibraryStatus.MARKED, LibraryStatus.ARBITRATED);
        groupDao.updateLeftCount(examId, subjectCode);
    }

    /**
     * 根据考生、学习中心、大题构造评卷任务
     * 
     * @param student
     * @param campus
     * @param group
     */
    @Override
    @Transactional
    public void buildLibrary(ExamStudent student, Campus campus, MarkGroup group) {
        // 等待大题释放锁定
        lockService.waitUnlockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
        // 等待考生释放锁定
        lockService.waitUnlockStudent(student.getId());
        // 查询是否已创建评卷任务
        if (libraryDao.countByStudentIdAndGroupNumber(student.getId(), group.getNumber()) == 0) {
            MarkLibrary library = new MarkLibrary();
            library.setExamId(student.getExamId());
            library.setSubjectCode(student.getSubjectCode());
            library.setGroupNumber(group.getNumber());
            library.setCampusId(campus.getId());
            library.setStudentId(student.getId());
            library.setExamNumber(student.getExamNumber());
            library.setTaskNumber(1);
            library.setStatus(LibraryStatus.WAITING);
            libraryDao.saveAndFlush(library);
            // 开启双评，且随机数超过双评比例，生成第二份评卷任务
            if (group.getDoubleRate() != null && group.getDoubleRate() > Math.random()) {
                library = new MarkLibrary();
                library.setExamId(student.getExamId());
                library.setSubjectCode(student.getSubjectCode());
                library.setGroupNumber(group.getNumber());
                library.setCampusId(campus.getId());
                library.setStudentId(student.getId());
                library.setExamNumber(student.getExamNumber());
                library.setTaskNumber(2);
                library.setStatus(LibraryStatus.WAITING);
                libraryDao.saveAndFlush(library);
            }
        }
        group.setBuildTime(student.getUploadTime());
        groupDao.updateBuildTime(group.getExamId(), group.getSubjectCode(), group.getNumber(), student.getUploadTime());
        updateLibraryCount(group.getExamId(), group.getSubjectCode(), group.getNumber());
    }

    /**
     * 领取评卷任务时，用来区分的唯一标识<br/>
     * 多评时同一个考生的多份任务不能被同一位评卷员领取
     * 
     * @param library
     * @return
     */
    private String getApplyTaskId(MarkLibrary library) {
        return library.getStudentId() + "_" + library.getGroupNumber();
    }

    /**
     * 重置评卷分组的连带操作
     * 
     * @param group
     */
    private void resetGroup(MarkGroup group) {
        groupDao.resetCount(group.getExamId(), group.getSubjectCode(), group.getNumber());
        libraryDao.resetByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
                group.getNumber(), LibraryStatus.WAITING);
        arbitrateDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
                group.getNumber());
        trackDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
                group.getNumber());
        specialTagDao.deleteByExamAndSubjectAndGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
        releaseByGroup(group);
    }

}
