package cn.com.qmth.stmms.biz.exam.service.impl;

import cn.com.qmth.stmms.biz.common.BaseQueryService;
import cn.com.qmth.stmms.biz.exam.dao.ExamStudentDao;
import cn.com.qmth.stmms.biz.exam.model.*;
import cn.com.qmth.stmms.biz.exam.query.ExamStudentSearchQuery;
import cn.com.qmth.stmms.biz.exam.service.*;
import cn.com.qmth.stmms.biz.mark.dao.HeaderTagDao;
import cn.com.qmth.stmms.biz.mark.dao.HeaderTrackDao;
import cn.com.qmth.stmms.biz.mark.model.*;
import cn.com.qmth.stmms.biz.mark.service.MarkLibraryService;
import cn.com.qmth.stmms.biz.mark.service.MarkSpecialTagService;
import cn.com.qmth.stmms.biz.mark.service.MarkTrackService;
import cn.com.qmth.stmms.biz.utils.OriginTag;
import cn.com.qmth.stmms.biz.utils.PictureConfigTransform;
import cn.com.qmth.stmms.biz.utils.PictureTag;
import cn.com.qmth.stmms.biz.utils.ScoreItem;
import cn.com.qmth.stmms.common.enums.Role;
import cn.com.qmth.stmms.common.enums.SubjectiveStatus;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.hibernate.SQLQuery;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.*;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;

@Service
public class ExamStudentServiceImpl extends BaseQueryService<ExamStudent> implements ExamStudentService {

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    private ExamStudentDao studentDao;

    @Autowired
    private ExamPackageService packageService;

    @Autowired
    private ExamSubjectService subjectService;

    @Autowired
    private ExamQuestionService questionService;

    @Autowired
    private ExamService examService;

    @Autowired
    private MarkLibraryService libraryService;

    @Autowired
    private MarkGroupService groupService;

    @Autowired
    private MarkTrackService trackService;

    @Autowired
    private MarkSpecialTagService specialTagService;

    @Autowired
    private HeaderTagDao headerTagDao;

    @Autowired
    private HeaderTrackDao headerTrackDao;

    @Autowired
    private CheckStudentService checkStudentService;

    @Autowired
    private AnswerCardService cardService;

    public static final String LOGINNAME_SPLITE = "-";

    public static final String USER_PASSWORD = "123456";

    public ExamStudent findById(int id) {
        return studentDao.findOne(id);
    }

    /**
     * 批量添加考生
     *
     * @param list
     * @return
     */
    @Transactional
    public int batchSave(List<ExamStudent> list) {
        if (list == null || list.isEmpty()) {
            return 0;
        }
        int success = 0;
        int examId = list.get(0).getExamId();
        Map<String, ExamSubject> subjectMap = new HashMap<String, ExamSubject>();
        Set<String> packageSet = new HashSet<String>();
        Set<String> secretNumberSet = new HashSet<>();

        for (ExamStudent student : list) {
            ExamSubject subject = null;
            student.setSubjectCode(StringUtils.trimToNull(student.getSubjectCode()));
            student.setSubjectName(StringUtils.trimToNull(student.getSubjectName()));
            if (!subjectMap.containsKey(student.getSubjectCode())) {
                subject = new ExamSubject();
                subject.setCode(student.getSubjectCode());
                subject.setName(student.getSubjectName());
                subject.setLevel(StringUtils.trimToNull(student.getSubjectLevel()));
                subject.setCategory(StringUtils.trimToNull(student.getSubjectCategory()));
                subject.setRemark(StringUtils.trimToNull(student.getSubjectRemark()));
                subject.setDisplayQuestionName(false);
                subject.setInspectRound(1);
            } else {
                subject = subjectMap.get(student.getSubjectCode());
                subject.setLevel(StringUtils.trimToNull(student.getSubjectLevel()));
                subject.setCategory(StringUtils.trimToNull(student.getSubjectCategory()));
                subject.setRemark(StringUtils.trimToNull(student.getSubjectRemark()));
            }
            subjectMap.put(subject.getCode(), subject);
            if (StringUtils.isNotBlank(student.getPackageCode())) {
                packageSet.add(student.getPackageCode());
            }
            success++;
        }

        for (ExamSubject es : subjectMap.values()) {
            ExamSubject subject = subjectService.find(examId, es.getCode());
            if (subject == null) {
                subject = new ExamSubject();
                subject.setExamId(examId);
                subject.setCode(es.getCode());
                subject.setName(es.getName());
                subject.setLevel(es.getLevel());
                subject.setCategory(es.getCategory());
                subject.setObjectiveScore(0d);
                subject.setSubjectiveScore(0d);
                subject.setTotalScore(0d);
                subject.setUploadCount(0);
                subject.setRemark(es.getRemark());
                subject.setDisplayQuestionName(false);
                subject.setInspectRound(1);
                subject.setSelective(false);
            } else {
                subject.setLevel(es.getLevel());
                subject.setCategory(es.getCategory());
                subject.setRemark(es.getRemark());
            }
            subjectService.save(subject);
        }

        for (String code : packageSet) {
            ExamPackage examPackage = packageService.find(examId, code);
            if (examPackage == null) {
                examPackage = new ExamPackage();
                examPackage.setExamId(examId);
                examPackage.setCode(code);
                examPackage.setPicCount(0);
                packageService.save(examPackage);
            }
        }

        for (ExamStudent student : list) {
            ExamSubject subject = subjectMap.get(student.getSubjectCode());
            student.setSubjectName(subject.getName());
            student.setSubjectLevel(subject.getLevel());
            student.setSubjectCategory(subject.getCategory());
            if (student.getSecretNumber() == null) {
                student.randomSecretNumber();
                while (secretNumberSet.contains(student.getSecretNumber())
                        || studentDao.countByExamIdAndSecretNumber(student.getExamId(), student.getSecretNumber()) > 0) {
                    student.randomSecretNumber();
                }
            }
            secretNumberSet.add(student.getSecretNumber());
        }
        studentDao.save(list);
        return success;
    }

    @Transactional
    public ExamStudent save(ExamStudent student) {
        student.setSubjectCode(StringUtils.trimToNull(student.getSubjectCode()));
        student.setSubjectName(StringUtils.trimToNull(student.getSubjectName()));
        ExamSubject subject = subjectService.find(student.getExamId(), student.getSubjectCode());
        if (subject == null) {
            subject = new ExamSubject();
            subject.setExamId(student.getExamId());
            subject.setCode(student.getSubjectCode());
            subject.setName(student.getSubjectName());
            subject.setLevel(StringUtils.trimToNull(student.getSubjectLevel()));
            subject.setCategory(StringUtils.trimToNull(student.getSubjectCategory()));
            subject.setObjectiveScore(0d);
            subject.setSubjectiveScore(0d);
            subject.setTotalScore(0d);
            subject.setUploadCount(0);
            subject.setRemark(StringUtils.trimToNull(student.getSubjectRemark()));
            subject.setDisplayQuestionName(false);
            subject.setInspectRound(1);
            subject.setSelective(false);
            subjectService.save(subject);
        } else {
            student.setSubjectName(subject.getName());
            student.setSubjectLevel(subject.getLevel());
            student.setSubjectCategory(subject.getCategory());
        }

        if (StringUtils.isNotBlank(student.getPackageCode())) {
            ExamPackage examPackage = packageService.find(student.getExamId(), student.getPackageCode());
            if (examPackage == null) {
                examPackage = new ExamPackage();
                examPackage.setExamId(student.getExamId());
                examPackage.setCode(student.getPackageCode());
                examPackage.setPicCount(0);
                packageService.save(examPackage);
            }
        }

        if (student.getSecretNumber() == null) {
            student.randomSecretNumber();
            while (studentDao.countByExamIdAndSecretNumber(student.getExamId(), student.getSecretNumber()) > 0) {
                student.randomSecretNumber();
            }
        }
        return studentDao.save(student);
    }

    @Transactional
    public void deleteById(int id) {
        checkStudentService.deleteByStudentId(id);
        ExamStudent student = findById(id);
        if (student != null) {
            delete(student);
        }
    }

    @Transactional
    public void delete(ExamStudent student) {
        studentDao.delete(student);
    }

    @Transactional
    public void deleteByExamId(int examId) {
        studentDao.deleteByExamId(examId);
    }

    public ExamStudentSearchQuery findByQuery(final ExamStudentSearchQuery query) {
        checkQuery(query);
        query.addSort("examNumber", Direction.ASC);
        if (query.getSheetCount() != null) {
            query.setSheetCount(query.getSheetCount() * 2);
        }
        Page<ExamStudent> result = studentDao.findAll(buildSpecification(query), query);
        fillResult(result, query);
        if (query.getSheetCount() != null) {
            query.setSheetCount(query.getSheetCount() / 2);
        }
        return query;
    }

    public long countByQuery(final ExamStudentSearchQuery query) {
        return studentDao.count(buildSpecification(query));
    }

    @Override
    public List<ExamStudent> findByExamId(int examId) {
        return studentDao.findByExamId(examId, null);
    }

    // @Override
    // public List<ExamStudent> findByExamId(int examId, int pageNumber, int
    // pageSize) {
    // ExamStudentSearchQuery query = new ExamStudentSearchQuery();
    // query.setPageNumber(pageNumber);
    // query.setPageSize(pageSize);
    // query.addSort("id", Direction.ASC);
    // return studentDao.findByExamId(examId, query);
    // }

    @Override
    public List<ExamStudent> findByExamIdAndSubjectCode(int examId, String subjectCode, int pageNumber, int pageSize) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setPageNumber(pageNumber);
        query.setPageSize(pageSize);
        query.addSort("examNumber", Direction.ASC);
        return studentDao.findByExamIdAndSubjectCode(examId, subjectCode, query);
    }

    @Override
    public List<ExamStudent> findByExamIdAndUploadAndAbsent(int examId, boolean upload, boolean absent, int pageNumber,
            int pageSize) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setPageNumber(pageNumber);
        query.setPageSize(pageSize);
        query.addSort("examNumber", Direction.ASC);
        return studentDao.findByExamIdAndUploadAndAbsent(examId, upload, absent, query);
    }

    // @Override
    // public List<ExamStudent> findByExamIdAndCampusName(int examId, String
    // campusName, int pageNumber, int pageSize) {
    // ExamStudentSearchQuery query = new ExamStudentSearchQuery();
    // query.setPageNumber(pageNumber);
    // query.setPageSize(pageSize);
    // query.addSort("id", Direction.ASC);
    // return studentDao.findByExamIdAndCampusName(examId, campusName, query);
    // }

    @Override
    public ExamStudent findByExamIdAndExamNumber(int examId, String examNumber) {
        List<ExamStudent> list = studentDao.findByExamIdAndExamNumber(examId, examNumber);
        return list != null && list.size() > 0 ? list.get(0) : null;
    }

    @Override
    public ExamStudent findByExamIdAndSecretNumber(int examId, String secretNumber) {
        List<ExamStudent> list = studentDao.findByExamIdAndSecretNumber(examId, secretNumber);
        return list != null && list.size() > 0 ? list.get(0) : null;
    }

    public long countByExamId(int examId) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        return countByQuery(query);
    }

    @Override
    public long countByExamIdAndUpload(int examId, boolean upload) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setUpload(upload);
        return countByQuery(query);
    }

    @Override
    public long countUploadedByExamIdAndSubjectCode(Integer examId, String subjectCode) {
        return studentDao.countUploaded(examId, subjectCode);
    }

    @Override
    public long countUploadedByExamId(Integer examId) {
        return studentDao.countUploaded(examId);
    }

    public long countByExamIdAndSubjectCode(int examId, String subjectCode) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        return countByQuery(query);
    }

    public long countByExamIdAndSubjectCode(int examId, String subjectCode, boolean upload) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setUpload(upload);
        return countByQuery(query);
    }

    public long countByExamIdAndSubjectCode(int examId, String subjectCode, boolean upload, boolean absent) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setUpload(upload);
        query.setAbsent(absent);
        return countByQuery(query);
    }

    @Override
    public long countByExamIdAndCampusName(int examId, String campusName) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setCampusName(campusName);
        return countByQuery(query);
    }

    @Override
    public long countByExamIdAndCampusName(int examId, String campusName, boolean upload) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setCampusName(campusName);
        query.setUpload(upload);
        return countByQuery(query);
    }

    @Override
    public long countCampusByExam(int examId) {
        return studentDao.countCampusNameByExamId(examId);
    }

    @Override
    public List<String> findDistinctCampusName(int examId) {
        return studentDao.findDistinctCampusName(examId);
    }

    @Override
    public List<String> findDistinctPackageCode(int examId) {
        return studentDao.findDistinctPackageCode(examId);
    }

    @Override
    @Transactional
    public boolean updateScanInfo(ExamStudent student) {
        student.setUploadTime(new Date());
        return studentDao.updateScanInfo(student.getId(), student.getSheetCount(), student.getSliceCount(),
                student.getAnswers(), student.getBatchCode(), student.getPaperType(), student.isAbsent(),
                student.getUploadTime(), student.getObjectiveScore(), student.getObjectiveScoreList(),
                student.getCardNumber()) > 0;
    }

    @Override
    @Transactional
    public boolean resetScanInfo(ExamStudent student) {
        return studentDao.resetScanInfo(student.getId(), student.getPaperType()) > 0;
    }

    @Override
    @Transactional
    public void updateManualAbsent(int id, boolean manualAbsent) {
        studentDao.updateManualAbsent(id, manualAbsent);
    }

    @Override
    @Transactional
    public boolean updateBreach(int id, boolean breach) {
        if (breach == true) {
            studentDao.updateObjectiveScore(id, 0, null);
            studentDao.updateSubjectiveStatusAndScore(id, SubjectiveStatus.UNMARK, 0, null);
        }
        return studentDao.updateBreach(id, breach) > 0;
    }

    @Override
    @Transactional
    public void updateManualAbsent(int examId, String examNumber, boolean manualAbsent) {
        studentDao.updateManualAbsent(examId, examNumber, manualAbsent);
    }

    @Override
    @Transactional
    public void clearManualAbsent(int examId) {
        studentDao.clearManualAbsent(examId);
    }

    @Override
    @Transactional
    public void updateSubjectInfo(ExamSubject subject) {
        if (subject != null) {
            studentDao.updateSubjectInfo(subject.getExamId(), subject.getCode(), subject.getName(), subject.getLevel(),
                    subject.getCategory());
        }
    }

    @Override
    @Transactional
    public void updateSubjectiveStatusAndScore(Integer examId, String subjectCode, SubjectiveStatus status,
            double score, String scoreList) {
        studentDao.updateSubjectiveStatusAndScore(examId, subjectCode, status, score, scoreList);
    }

    @Override
    @Transactional
    public void updateSubjectiveStatusAndScore(Integer id, SubjectiveStatus status, double score, String scoreList) {
        studentDao.updateSubjectiveStatusAndScore(id, status, score, scoreList);
    }

    @Override
    @Transactional
    public void resetTrial(Integer examId, String subjectCode, boolean trial) {
        studentDao.updateTrial(examId, subjectCode, trial);
    }

    @Override
    @Transactional
    public void resetSubjectiveStatusByGroupStatus(Integer examId, String subjectCode) {
        studentDao.updateSubjectiveStatusFromGroupStatus(examId, subjectCode, SubjectiveStatus.UNMARK, 0, null,
                SubjectiveStatus.UNMARK);
    }

    @Override
    @Transactional
    public void updateObjectiveScore(int id, double score, String scoreList) {
        studentDao.updateObjectiveScore(id, score, scoreList);
    }

    @Override
    @Transactional
    public void updateException(int id, boolean exception) {
        studentDao.updateException(id, exception);
    }

    private Specification<ExamStudent> buildSpecification(final ExamStudentSearchQuery query) {
        return new Specification<ExamStudent>() {

            @Override
            public Predicate toPredicate(Root<ExamStudent> root, CriteriaQuery<?> cQuery, CriteriaBuilder cb) {
                List<Predicate> predicates = new LinkedList<Predicate>();
                Expression<Double> evaluationItemSum = cb.sum(root.get("objectiveScore").as(Double.class),
                        root.get("subjectiveScore").as(Double.class));
                if (query.getStartScore() != null) {
                    Predicate predicate1 = cb.ge(evaluationItemSum, query.getStartScore());
                    Predicate predicate2 = cb.le(evaluationItemSum, query.getEndScore());
                    if (query.getStartScore() == 0) {
                        Predicate predicate = cb.or(cb.equal(root.get("absent"), true),
                                cb.equal(root.get("breach"), true));
                        Predicate predicate3 = cb.and(predicate1, predicate2);
                        predicates.add(cb.or(predicate, predicate3));
                    } else {
                        predicates.add(cb.equal(root.get("absent"), false));
                        predicates.add(cb.equal(root.get("breach"), false));
                        predicates.add(cb.and(predicate1, predicate2));
                    }
                }
                // if (query.getStartScroe()!=null && query.getEndScroe()!=null
                // ) {
                // predicates.add(cb.between(evaluationItemSum,
                // query.getStartScroe(), query.getEndScroe()));
                // }
                if (query.getExamId() != null && query.getExamId() > 0) {
                    predicates.add(cb.equal(root.get("examId"), query.getExamId()));
                }
                if (query.getSchoolId() != null) {
                    predicates.add(cb.equal(root.get("schoolId"), query.getSchoolId()));
                }
                if (StringUtils.isNotBlank(query.getIds())) {
                    String[] ids = query.getIds().split(",");
                    List<String> list = new ArrayList<String>();
                    for (String str : ids) {
                        list.add(str);
                    }
                    predicates.add(root.get("id").in(list));
                }
                if (query.getStudentIds() != null && query.getStudentIds().size() > 0) {
                    predicates.add(root.get("id").in(query.getStudentIds()));
                }
                if (StringUtils.isNotBlank(query.getExamNumber())) {
                    predicates.add(cb.equal(root.get("examNumber"), query.getExamNumber()));
                } else if (StringUtils.isNotBlank(query.getExamNumberIn())) {
                    String[] list = query.getExamNumberIn().split(",");
                    if (list.length > 0) {
                        Predicate[] sub = new Predicate[list.length];
                        for (int i = 0; i < list.length; i++) {
                            sub[i] = cb.equal(root.get("examNumber"), list[i]);
                        }
                        predicates.add(cb.or(sub));
                    }
                }
                if (StringUtils.isNotEmpty(query.getSecretNumber())) {
                    predicates.add(cb.equal(root.get("secretNumber"), query.getSecretNumber()));
                }
                if (StringUtils.isNotEmpty(query.getStudentCode())) {
                    predicates.add(cb.equal(root.get("studentCode"), query.getStudentCode()));
                }
                if (StringUtils.isNotBlank(query.getSubjectCode())) {
                    predicates.add(cb.equal(root.get("subjectCode"), query.getSubjectCode()));
                }
                if (StringUtils.isNotBlank(query.getCampusName())) {
                    predicates.add(cb.equal(root.get("campusName"), query.getCampusName()));
                }
                if (StringUtils.isNotBlank(query.getName())) {
                    predicates.add(cb.like(root.get("name").as(String.class), query.getName() + "%"));
                }
                if (StringUtils.isNotBlank(query.getBatchCode())) {
                    predicates.add(cb.equal(root.get("batchCode"), query.getBatchCode()));
                }
                if (StringUtils.isNotBlank(query.getPackageCode())) {
                    predicates.add(cb.equal(root.get("packageCode"), query.getPackageCode()));
                }
                if (StringUtils.isNotBlank(query.getSubjectLevel())) {
                    predicates.add(cb.equal(root.get("subjectLevel"), query.getSubjectLevel()));
                }
                if (StringUtils.isNotBlank(query.getSubjectCategory())) {
                    predicates.add(cb.equal(root.get("subjectCategory"), query.getSubjectCategory()));
                }
                if (query.getObjectiveScore() != null) {
                    predicates.add(cb.equal(root.get("objectiveScore"), query.getObjectiveScore()));
                } else if (query.getObjectiveScoreGt() != null) {
                    predicates.add(cb.greaterThan(root.get("objectiveScore").as(Double.class),
                            query.getObjectiveScoreGt()));
                } else if (query.getObjectiveScoreLt() != null) {
                    predicates
                            .add(cb.lessThan(root.get("objectiveScore").as(Double.class), query.getObjectiveScoreLt()));
                }
                if (query.getStatusSet() != null && query.getStatusSet().size() > 0) {
                    List<Predicate> sub = new LinkedList<>();
                    for (SubjectiveStatus status : query.getStatusSet()) {
                        sub.add(cb.equal(root.get("subjectiveStatus").as(SubjectiveStatus.class), status));
                    }
                    predicates.add(cb.or(sub.toArray(new Predicate[sub.size()])));
                }
                if (query.getSubjectiveScore() != null) {
                    predicates.add(cb.equal(root.get("subjectiveScore"), query.getSubjectiveScore()));
                } else if (query.getSubjectiveScoreGt() != null) {
                    predicates.add(cb.greaterThan(root.get("subjectiveScore").as(Double.class),
                            query.getSubjectiveScoreGt()));
                } else if (query.getSubjectiveScoreLt() != null) {
                    predicates.add(cb.lessThan(root.get("subjectiveScore").as(Double.class),
                            query.getSubjectiveScoreLt()));
                }
                if (query.getUpload() != null) {
                    predicates.add(cb.equal(root.get("upload"), query.getUpload()));
                }
                if (query.getInspected() != null) {
                    predicates.add(cb.equal(root.get("inspected"), query.getInspected()));
                }
                if (query.getAbsent() != null) {
                    if (query.getAbsent()) {// 缺考=缺考+ 未上传
                        predicates.add(cb.or(cb.equal(root.get("absent"), true), cb.equal(root.get("upload"), false)));
                    } else {
                        predicates.add(cb.equal(root.get("absent"), query.getAbsent()));
                        predicates.add(cb.equal(root.get("upload"), true));
                    }
                }
                if (query.getBreach() != null) {
                    predicates.add(cb.equal(root.get("breach"), query.getBreach()));
                }
                if (query.getException() != null) {
                    predicates.add(cb.equal(root.get("exception"), query.getException()));
                }
                if (query.getTrial() != null) {
                    predicates.add(cb.equal(root.get("trial"), query.getTrial()));
                }
                if (query.getManualAbsent() != null) {
                    predicates.add(cb.equal(root.get("manualAbsent"), query.getManualAbsent()));
                }
                if (query.getUploadTimeNotNull() != null) {
                    predicates.add(cb.isNotNull(root.get("uploadTime")));
                }
                if (query.getMinUploadTime() != null) {
                    predicates.add(cb.greaterThan(root.get("uploadTime").as(Date.class), query.getMinUploadTime()));
                }
                if (query.getMaxUploadTime() != null) {
                    predicates.add(cb.lessThan(root.get("uploadTime").as(Date.class), query.getMaxUploadTime()));
                }
                if (StringUtils.isNotBlank(query.getSubjectCodeIn())) {
                    String[] list = query.getSubjectCodeIn().split(",");
                    if (list.length > 0) {
                        Predicate[] sub = new Predicate[list.length];
                        for (int i = 0; i < list.length; i++) {
                            sub[i] = cb.equal(root.get("subjectCode"), list[i]);
                        }
                        predicates.add(cb.or(sub));
                    }
                }
                if (StringUtils.isNotBlank(query.getCampusNameIn())) {
                    String[] list = query.getCampusNameIn().split(",");
                    if (list.length > 0) {
                        Predicate[] sub = new Predicate[list.length];
                        for (int i = 0; i < list.length; i++) {
                            sub[i] = cb.equal(root.get("campusName"), list[i]);
                        }
                        predicates.add(cb.or(sub));
                    }
                }
                if (StringUtils.isNotBlank(query.getExamSiteIn())) {
                    String[] list = query.getExamSiteIn().split(",");
                    if (list.length > 0) {
                        Predicate[] sub = new Predicate[list.length];
                        for (int i = 0; i < list.length; i++) {
                            sub[i] = cb.equal(root.get("examSite"), list[i]);
                        }
                        predicates.add(cb.or(sub));
                    }
                }
                if (StringUtils.isNotBlank(query.getSubjectCodeNotIn())) {
                    String[] list = query.getSubjectCodeNotIn().split(",");
                    if (list.length > 0) {
                        Predicate[] sub = new Predicate[list.length];
                        for (int i = 0; i < list.length; i++) {
                            sub[i] = cb.notEqual(root.get("subjectCode"), list[i]);
                        }
                        predicates.add(cb.and(sub));
                    }
                }
                if (StringUtils.isNotBlank(query.getCampusNameNotIn())) {
                    String[] list = query.getCampusNameNotIn().split(",");
                    if (list.length > 0) {
                        Predicate[] sub = new Predicate[list.length];
                        for (int i = 0; i < list.length; i++) {
                            sub[i] = cb.notEqual(root.get("campusName"), list[i]);
                        }
                        predicates.add(cb.and(sub));
                    }
                }
                if (StringUtils.isNotBlank(query.getExamSiteNotIn())) {
                    String[] list = query.getExamSiteNotIn().split(",");
                    if (list.length > 0) {
                        Predicate[] sub = new Predicate[list.length];
                        for (int i = 0; i < list.length; i++) {
                            sub[i] = cb.notEqual(root.get("examSite"), list[i]);
                        }
                        predicates.add(cb.and(sub));
                    }
                }
                if (StringUtils.isNotBlank(query.getPaperType())) {
                    predicates.add(cb.equal(root.get("paperType"), query.getPaperType()));
                }
                if (StringUtils.isNotBlank(query.getCollege())) {
                    predicates.add(cb.equal(root.get("college"), query.getCollege()));
                }
                if (StringUtils.isNotBlank(query.getClassName())) {
                    predicates.add(cb.equal(root.get("className"), query.getClassName()));
                }
                if (StringUtils.isNotBlank(query.getTeacher())) {
                    predicates.add(cb.equal(root.get("teacher"), query.getTeacher()));
                }
                if (query.getInspectorId() != null) {
                    predicates.add(cb.equal(root.get("inspectorId"), query.getInspectorId()));
                }
                if (StringUtils.isNotBlank(query.getExamSite())) {
                    predicates.add(cb.equal(root.get("examSite"), query.getExamSite()));
                }
                if (StringUtils.isNotBlank(query.getExamRoom())) {
                    predicates.add(cb.equal(root.get("examRoom"), query.getExamRoom()));
                }
                if (query.getSheetCount() != null) {
                    predicates.add(cb.equal(root.get("sheetCount"), query.getSheetCount()));
                }
                return predicates.isEmpty() ? cb.conjunction() : cb.and(predicates.toArray(new Predicate[predicates
                        .size()]));
            }
        };
    }

    @Override
    public ExamStudent findBySchoolIdAndSubjectCodeAndStudentCode(Integer schoolId, String subjectCode,
            String studentCode) {
        List<Exam> exams = examService.findBySchoolId(schoolId);
        if (exams != null && exams.size() > 0) {
            return studentDao.findByExamIdAndSubjectCodeAndStudentCode(exams.get(0).getId(), subjectCode, studentCode);
        } else {
            return null;
        }
    }

    @Override
    public ExamStudent findBySchoolIdAndSubjectCodeAndStudentCodeAndRemark(Integer schoolId, String subjectCode,
            String studentCode, String examSeqCode) {
        return studentDao.findBySchoolIdAndSubjectCodeAndStudentCodeAndRemark(schoolId, subjectCode, studentCode,
                examSeqCode);
    }

    @Override
    public List<ExamStudent> findByExamIdAndUploadTimeAfter(int examId, Date date) {
        return studentDao.findByExamIdAndUploadTimeAfter(examId, date);
    }

    @Override
    public List<ExamStudent> findUploadedByExamIdAndSubjectCode(int examId, String code) {
        return studentDao.findByExamIdAndSubjectCodeAndUploadTimeNotNull(examId, code);
    }

    @Override
    public List<Integer> findIdByExamIdAndSubjectCodeAndSubjectiveStatus(Integer examId, String subjectCode,
            SubjectiveStatus... status) {
        return studentDao.findIdByExamIdAndSubjectCodeAndSubjectiveStatus(examId, subjectCode, status);
    }

    @Override
    public ExamStudent findByExamIdAndSubjectCodeAndUploadTimeAfter(int examId, String code, Date date) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(code);
        query.setMinUploadTime(date);
        query.setUploadTimeNotNull(true);
        query.setPageNumber(1);
        query.setPageSize(1);
        query.setSort(new Sort(Direction.ASC, "uploadTime", "id"));
        query = findByQuery(query);
        return query.getCurrentCount() > 0 ? query.getResult().get(0) : null;
    }

    @Override
    public List<ExamStudent> findUnLibraryStudent(int examId, String subjectCode, int groupNumber, int count) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setPageNumber(1);
        query.setPageSize(count);
        return studentDao.findUnLibraryStudent(examId, subjectCode, groupNumber, query);
    }

    @Override
    public List<ExamStudent> findUnTrialStudent(Integer examId, String subjectCode, Integer groupNumber) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setPageNumber(1);
        query.setPageSize(Integer.MAX_VALUE);
        query.setSort(new Sort(Direction.ASC, "uploadTime", "id"));
        List<ExamStudent> list = studentDao.findUnTrialStudent(examId, subjectCode, groupNumber, true, query);
        return list;
    }

    @Override
    public List<ExamStudent> findAbsentOrBreachLibraryStudent(int examId, String subjectCode) {
        return studentDao.findAbsentOrBreachLibraryStudent(examId, subjectCode);
    }

    @Override
    public long countByExamIdAndSubjectiveScoreListIsNotNull(Integer examId) {
        return studentDao.countByExamIdAndSubjectiveScoreListNotNull(examId);
    }

    @Override
    public Long countByExamIdAndSubjectCodeAndCampus(Integer examId, String subjectCode, String campusName,
            boolean upload, boolean absent) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setCampusName(campusName);
        query.setUpload(upload);
        query.setAbsent(absent);
        return countByQuery(query);
    }

    public long countByNoAbsentAndBreach(int examId, String subjectCode, boolean upload, boolean absent, boolean breach) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setUpload(upload);
        query.setAbsent(absent);
        query.setBreach(breach);
        return countByQuery(query);
    }

    public long countByAbsentAndBreach(int examId, String subjectCode, Boolean absent, Boolean breach) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        if (breach != null) {
            query.setBreach(breach);
        }
        if (absent != null) {
            query.setAbsent(absent);
        }
        return countByQuery(query);
    }

    @Override
    public String findIdsByMarkLogin(String markLogin) {
        List<String[]> o = studentDao.findIdsByMarkLogin(markLogin);
        return StringUtils.join(o, ",");
    }

    @Override
    public String findIdsByMarkName(String MarkName) {
        List<String[]> o = studentDao.findIdsByMarkName(MarkName);
        return StringUtils.join(o, ",");
    }

    /**
     * 根据考生获得某张原图上的评卷标记和分数明细
     */
    @Override
    public List<PictureTag> buildSheetTags(ExamStudent student, int index, boolean withGroupScore) {
        List<PictureTag> list = new LinkedList<>();
        // 第一页原图才需要显示总分明细
        // if (index == 1) {
        // list.add(buildScoreDetailTag(student));
        // }
        // 构造评卷标记信息
        Exam exam = examService.findById(student.getExamId());
        ExamSubject subject = subjectService.find(student.getExamId(), student.getSubjectCode());
        List<PictureConfigItem> sliceConfig = subject.getSliceConfigList();
        if (sliceConfig.isEmpty()) {
            sliceConfig = exam.getSliceConfigList();
        }
        if (!sliceConfig.isEmpty()) {
            List<PictureTag> tags = PictureConfigTransform.process(sliceConfig,
                    getSliceTags(student, withGroupScore, sliceConfig)).get(index);
            if (tags != null) {
                list.addAll(tags);
            }
        }
        return list;
    }

    /**
     * 根据考生获得所有原图上的评卷标记和分数明细
     */
    @Override
    public Map<Integer, List<PictureTag>> buildSheetTags(ExamStudent student, boolean withGroupScore,
            boolean sliceConfigFix) {
        Map<MarkGroup, List<OriginTag>> tagMap = new HashMap<MarkGroup, List<OriginTag>>();
        Exam exam = examService.findById(student.getExamId());
        ExamSubject subject = subjectService.find(student.getExamId(), student.getSubjectCode());
        List<PictureConfigItem> sliceConfig = new ArrayList<PictureConfigItem>();
        if (student.getCardNumber() != null) {
            AnswerCard card = cardService.findByExamIdAndNumber(student.getExamId(), student.getCardNumber());
            if (card != null) {
                sliceConfig = card.getSliceConfigList();
            }
        }
        if (sliceConfig.isEmpty()) {
            sliceConfig = subject.getSliceConfigList();
        }
        if (sliceConfig.isEmpty()) {
            sliceConfig = exam.getSliceConfigList();
        }
        if (sliceConfigFix && student.getSliceCount() > sliceConfig.size()) {
            int picCount = student.getSliceCount() - sliceConfig.size();
            int sliceConfigSize = sliceConfig.size();
            for (int i = 1; i <= picCount; i++) {
                PictureConfigItem e = new PictureConfigItem(sliceConfigSize + i);
                sliceConfig.add(e);
            }
        }
        if (!sliceConfig.isEmpty()) {
            // 有裁切图配置时才需要获取原始评卷标记信息
            tagMap = getSliceTags(student, withGroupScore, sliceConfig);
        }
        Map<Integer, List<PictureTag>> map = PictureConfigTransform.process(sliceConfig, tagMap);
        // List<PictureTag> list = map.get(1);
        // if (list == null) {
        // list = new LinkedList<>();
        // map.put(1, list);
        // }
        // list.add(buildScoreDetailTag(student));
        return map;
    }

    /**
     * 根据考生获取所有评卷分组的评卷标记内容
     */
    @Override
    public Map<MarkGroup, List<OriginTag>> getSliceTags(ExamStudent student, boolean withGroupScore,
            List<PictureConfigItem> sliceConfig) {
        Map<MarkGroup, List<OriginTag>> tagMap = new HashMap<MarkGroup, List<OriginTag>>();
        List<ExamQuestion> questions = questionService.findByExamAndSubjectAndObjective(student.getExamId(),
                student.getSubjectCode(), false);
        List<ScoreItem> scoreList = student.getScoreList(false);
        List<MarkGroup> markGroups = groupService.findByExamAndSubject(student.getExamId(), student.getSubjectCode());
        for (MarkGroup group : markGroups) {
            tagMap.put(group, buildOriginTags(student, group, questions, scoreList, withGroupScore, sliceConfig));
        }
        return tagMap;
    }

    /**
     * 根据考生构造显示到第一张原图左上角的总分与主客观题明细标记
     *
     * @param student
     * @return
     */
    @SuppressWarnings("unused")
    private PictureTag buildScoreDetailTag(ExamStudent student) {
        DecimalFormat format = new DecimalFormat("####.###");
        // 所有显示内容
        List<String> lines = new LinkedList<>();
        lines.add("成绩明细");
        // 总分得分明细
        lines.add("总分 (客观+主观) | " + format.format(student.getTotalScore()) + "="
                + format.format(student.getObjectiveScore() != null ? student.getObjectiveScore() : 0) + "+"
                + format.format(student.getSubjectiveScore() != null ? student.getSubjectiveScore() : 0));
        // 客观题得分明细
        List<String> objectives = new LinkedList<>();
        List<ExamQuestion> questions = questionService.findByExamAndSubjectAndObjectiveAndPaperType(
                student.getExamId(), student.getSubjectCode(), true, student.getPaperType());
        List<ScoreItem> scoreList = student.getScoreList(true);
        List<String> details = new ArrayList<>();
        int i = 0;
        int length = 0;
        for (ScoreItem item : scoreList) {
            i++;
            if (questions.size() < i) {
                break;
            }
            ExamQuestion question = questions.get(i - 1);
            if (question.getTotalScore() == null || question.getTotalScore() == 0) {
                continue;
            }
            String content = item.getAnswer() + ":" + format.format(item.getScore());
            if ((length + content.length()) > 60) {
                // 一行只显示不超过60个字符，避免遮盖主观题答题区域
                objectives.add(StringUtils.join(details, ","));
                details.clear();
                length = 0;
            }
            details.add(content);
            length += content.length();
        }
        if (!details.isEmpty()) {
            objectives.add(StringUtils.join(details, ","));
            details.clear();
        }
        if (objectives.size() > 1) {
            objectives.add(0, "客观题明细");
            lines.addAll(objectives);
        }
        PictureTag tag = new PictureTag();
        tag.setSize(30);
        tag.setTop(40);
        tag.setContent(lines);
        tag.setUserRole("");
        return tag;
    }

    /**
     * 获取某个考生某个评卷分组的所有评卷标记
     *
     * @param student
     * @param group
     * @param questions
     * @param scoreList
     * @return
     */
    private List<OriginTag> buildOriginTags(ExamStudent student, MarkGroup group, List<ExamQuestion> questions,
            List<ScoreItem> scoreList, boolean withGroupScore, List<PictureConfigItem> sliceConfig) {
        if (scoreList.size() == 0) {
            return new LinkedList<>();
        }
        DecimalFormat format = new DecimalFormat("####.###");
        // 从考生主观题得分中拆解出本大题得分
        BigDecimal score = BigDecimal.ZERO;
        List<Double> details = new ArrayList<>();
        int i = -1;
        for (ExamQuestion question : questions) {
            i++;
            if (group.getNumber().equals(question.getGroupNumber())) {
                double value = scoreList.size() > i ? scoreList.get(i).getScore() : 0;
                score = score.add(BigDecimal.valueOf(value));
                details.add(value);
            }
        }
        // 原图中需要显示的内容列表
        List<OriginTag> originTags = new LinkedList<>();
        // 首先添加本大题总得分
        if (withGroupScore) {
            List<MarkConfigItem> configList = group.getPictureConfigList();
            if (configList.isEmpty()) {
                // 未设置评卷区域，则自动取第一张裁切图的固定位置
                originTags.add(new OriginTag(0, "", group.getNumber(), format.format(score), 1, 10, 10));
            } else {
                // 取第一个显示区域相对裁切图的位置
                MarkConfigItem config = configList.get(0);
                OriginTag tag = new OriginTag(0, "", group.getNumber(), format.format(score), config.getI(),
                        config.getX(), config.getY());
                // 兼容比例模式，且元素在裁切坐标内
                if (config.getX() <= 1 && config.getY() <= 1 && config.getI() <= sliceConfig.size()
                        && sliceConfig.get(config.getI() - 1).getW() > 0
                        && sliceConfig.get(config.getI() - 1).getH() > 0) {
                    tag = new OriginTag(0, "", group.getNumber(), format.format(score), config.getI(), config.getX()
                            * sliceConfig.get(config.getI() - 1).getW(), config.getY()
                            * sliceConfig.get(config.getI() - 1).getH());
                }
                originTags.add(tag);
            }
        }
        // 检测应该使用哪个评卷任务的轨迹记录
        // MarkLibrary selected = null;
        // List<MarkLibrary> libraries =
        // libraryService.findByStudentAndGroup(student.getId(),
        // group.getNumber());
        // for (MarkLibrary library : libraries) {
        // if (checkScore(library, score.doubleValue(), details)) {
        // selected = library;
        // break;
        // }
        // }
        // if (selected != null) {
        // // 添加轨迹分
        // List<MarkTrack> tracks =
        // trackService.findByLibraryId(selected.getId());
        // for (MarkTrack markTrack : tracks) {
        // // 轨迹分为未作答时，只显示一个汉字"空"
        // originTags.add(new OriginTag(markTrack.isUnanswered() ? "空" :
        // format.format(markTrack.getScore()),
        // markTrack.getOffsetIndex(), markTrack.getOffsetX(),
        // markTrack.getOffsetY()));
        // }
        // // 添加特殊标记
        // List<MarkSpecialTag> specialTags =
        // specialTagService.findByLibraryId(selected.getId());
        // for (MarkSpecialTag markSpecialTag : specialTags) {
        // originTags.add(new OriginTag(markSpecialTag.getTagName(),
        // markSpecialTag.getOffsetIndex(),
        // markSpecialTag.getOffsetX(), markSpecialTag.getOffsetY()));
        // }
        List<MarkLibrary> libraries = libraryService.findByStudentAndGroup(student.getId(), group.getNumber());
        for (MarkLibrary library : libraries) {
            // if (checkScore(library, score.doubleValue(), details)) {
            // 添加轨迹分
            List<MarkTrack> tracks = trackService.findByLibraryId(library.getId());
            for (MarkTrack markTrack : tracks) {
                // 未作答时只显示汉字"空"
                originTags.add(new OriginTag(library.getMarkerId(), Role.MARKER.toString(), library.getGroupNumber(),
                        markTrack.isUnanswered() ? "空" : format.format(markTrack.getScore()), markTrack
                                .getOffsetIndex(), markTrack.getOffsetX(), markTrack.getOffsetY()));
            }
            // 添加特殊标记
            List<MarkSpecialTag> specialTags = specialTagService.findByLibraryId(library.getId());
            for (MarkSpecialTag markSpecialTag : specialTags) {
                originTags.add(new OriginTag(library.getMarkerId(), Role.MARKER.toString(), library.getGroupNumber(),
                        markSpecialTag.getTagName(), markSpecialTag.getOffsetIndex(), markSpecialTag.getOffsetX(),
                        markSpecialTag.getOffsetY()));
            }
            // }
        }
        List<HeaderTag> headerTags = headerTagDao.findByStudentIdAndGroupNumberOrderByIdAsc(student.getId(),
                group.getNumber());
        for (HeaderTag headerTag : headerTags) {
            originTags.add(new OriginTag(headerTag.getUserId(), Role.SUBJECT_HEADER.toString(), headerTag
                    .getGroupNumber(), headerTag.getTagName(), headerTag.getOffsetIndex(), headerTag.getOffsetX(),
                    headerTag.getOffsetY()));
        }
        List<HeaderTrack> headerTracks = headerTrackDao.findByPkStudentIdAndGroupNumber(student.getId(),
                group.getNumber());
        for (HeaderTrack headerTrack : headerTracks) {
            originTags.add(new OriginTag(headerTrack.getUserId(), Role.SUBJECT_HEADER.toString(), headerTrack
                    .getGroupNumber(), headerTrack.isUnanswered() ? "空" : format.format(headerTrack.getScore()),
                    headerTrack.getOffsetIndex(), headerTrack.getOffsetX(), headerTrack.getOffsetY()));
        }
        return originTags;
    }

    // private boolean checkScore(MarkLibrary library, double score,
    // List<Double> details) {
    // if (library.getMarkerScore() == null) {
    // return false;
    // }
    // if (library.getMarkerScore().doubleValue() != score) {
    // return false;
    // }
    // return true;
    // }

    @Override
    public long countByExamIdAndManualAbsent(int examId, boolean manualAbsent) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setManualAbsent(manualAbsent);
        return countByQuery(query);
    }

    @Override
    public boolean validateStatus(Integer id) {
        return studentDao.countByIdAndUploadAndAbsentAndBreach(id, true, false, false) > 0;
    }

    @Override
    public List<String> findDistinctClassName(Integer examId, String subjectCode) {
        return studentDao.findDistinctClassName(examId, subjectCode);
    }

    @Override
    public List<String> findDistinctClassName(Integer examId) {
        return studentDao.findDistinctClassName(examId);
    }

    @Transactional
    @Override
    public void updateSubjectiveStatusAndTimeAndInspectorId(Integer studentId, SubjectiveStatus status,
            Date inspectTime, Integer inspectorId) {
        studentDao.updateSubjectiveStatusAndTimeAndInspectorId(studentId, status, inspectTime, inspectorId);
    }

    @Override
    public List<ExamStudent> findByExamIdAndStudentCodeAndSubjectCode(int examId, String studentCode, String subjectCode) {
        return studentDao.findByExamIdAndStudentCodeAndSubjectCode(examId, studentCode, subjectCode);
    }

    @Override
    public long countByExamIdAndSubjectCodeAndTrial(int examId, String subjectCode, boolean trial) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setTrial(trial);
        return countByQuery(query);
    }

    @Override
    public ExamStudent randomStudent(Integer examId, String code) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(code);
        query.setAbsent(false);
        query.setUpload(true);
        query.setBreach(false);
        query.setTrial(false);
        query.addStatus(SubjectiveStatus.UNMARK);
        query.setPageNumber(1);
        query.setPageSize(200);
        query.setSort(new Sort(Direction.ASC, "uploadTime"));
        List<ExamStudent> list = findByQuery(query).getResult();
        if (list.isEmpty()) {
            return null;
        }
        return list.get(RandomUtils.nextInt(list.size()));

    }

    @Override
    public long countSheetCountByExamId(Integer examId) {
        Long count = studentDao.sumSheetCountByExamId(examId);
        return count == null ? 0 : count;
    }

    @Override
    public long countSheetCountByExamIdAndSubjectCode(int examId, String code) {
        Long count = studentDao.sumSheetCountByExamIdAndSubjectCode(examId, code);
        return count == null ? 0 : count;
    }

    @Override
    public Set<String> findSubjectUnInspectedByExamId(int examId) {
        return studentDao.findSubjectUnInspectedByExamId(examId, false, SubjectiveStatus.MARKED);
    }

    @Override
    public List<String> findDistinctCollege(int examId) {
        return studentDao.findDistinctCollege(examId);
    }

    @Override
    public List<String> findDistinctSubjectCodeByCollege(int examId, String college) {
        return studentDao.findDistinctSubjectCodeByCollege(examId, college);
    }

    @Override
    public int findMinInspectCountByExamIdAndSubjectCode(int examId, String subjectCode) {
        Integer count = studentDao.findMinInspectCountByExamIdAndSubjectCode(examId, subjectCode);
        return count == null ? 0 : count;
    }

    @Transactional
    @Override
    public void updateInspectCount(Integer studentId, Integer inspectCount, Date inspectTime, Integer inspectorId) {
        studentDao.updateInspectCount(studentId, inspectCount, inspectTime, inspectorId, true);
    }

    /**
     * 根据考生获得所有原图上的评卷标记和分数明细
     */
    @Override
    public Map<Integer, List<PictureTag>> buildSliceTags(ExamStudent student, boolean withGroupScore) {
        Map<MarkGroup, List<OriginTag>> tagMap = new HashMap<MarkGroup, List<OriginTag>>();
        Exam exam = examService.findById(student.getExamId());
        ExamSubject subject = subjectService.find(student.getExamId(), student.getSubjectCode());
        List<PictureConfigItem> sliceConfig = new ArrayList<PictureConfigItem>();
        if (student.getCardNumber() != null) {
            AnswerCard card = cardService.findByExamIdAndNumber(student.getExamId(), student.getCardNumber());
            if (card != null) {
                sliceConfig = card.getSliceConfigList();
            }
        }
        if (sliceConfig.isEmpty()) {
            sliceConfig = subject.getSliceConfigList();
        }
        if (sliceConfig.isEmpty()) {
            sliceConfig = exam.getSliceConfigList();
        }
        if (!sliceConfig.isEmpty()) {
            // 有裁切图配置时才需要获取原始评卷标记信息
            tagMap = getSliceTags(student, withGroupScore, sliceConfig);
        }
        Map<Integer, List<PictureTag>> map = new HashMap<Integer, List<PictureTag>>();
        for (Entry<MarkGroup, List<OriginTag>> entry : tagMap.entrySet()) {
            // 遍历所有显示元素
            for (OriginTag tag : entry.getValue()) {
                PictureTag pt = new PictureTag();
                pt.setContent(tag.getContent());
                pt.setLeft(tag.getOffsetX());
                pt.setTop(tag.getOffsetY());
                pt.setUserId(tag.getUserId());
                pt.setUserRole(tag.getUserRole());
                pt.setGroupNumber(tag.getGroupNumber());
                List<PictureTag> list = map.get(tag.getOffsetIndex());
                if (list == null) {
                    list = new LinkedList<>();
                    map.put(tag.getOffsetIndex(), list);
                }
                list.add(pt);
            }
        }
        return map;
    }

    @Override
    public long countByExamIdAndSubjectCodeAndCollege(Integer examId, String subjectCode, String collegeName) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setCollege(collegeName);
        return countByQuery(query);
    }

    @Override
    public long countByExamIdAndSubjectCodeAndClass(Integer examId, String subjectCode, String className) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setClassName(className);
        return countByQuery(query);
    }

    @Override
    public long countByExamIdAndSubjectCodeAndTeacher(Integer examId, String subjectCode, String teacherName) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setTeacher(teacherName);
        return countByQuery(query);
    }

    @Transactional
    @Override
    public int batchUpdate(List<ExamStudent> updateList) {
        int i = 0;
        for (ExamStudent student : updateList) {
            i = i
                    + this.studentDao.updateInfo(student.getId(), student.getCollege(), student.getClassName(),
                            student.getTeacher(), student.getExamRoom(), student.getExamSite(), student.getRemark());
        }
        return i;
    }

    @Transactional
    @Override
    public void scoreVerifyTag(Integer userId, Integer studentId, Boolean tag) {
        studentDao.scoreVerifyTag(userId, new Date(), tag, studentId);
    }

    @Transactional
    @Override
    public void inspect(Integer studentId, Date inspectTime, Integer inspectorId) {
        studentDao.inspect(studentId, inspectTime, inspectorId, true);
    }

    @Transactional
    @Override
    public int cancelInspect(Integer studentId) {
        return studentDao.cancelInspect(studentId, false);
    }

    @Override
    public ExamStudentSearchQuery findByQueryPlus(ExamStudentSearchQuery query) {
        checkQuery(query);
        if (query.getSheetCount() != null) {
            query.setSheetCount(query.getSheetCount() * 2);
        }
        Integer totalCount = countByQueryPlus(query);
        if (totalCount > 0) {
            List<ExamStudent> list = findByQuerySql(query);
            query.setResult(list);
            query.setTotalCount(totalCount);
        } else {
            query.setResult(new ArrayList<>());
            query.setTotalCount(0);
        }
        if (query.getSheetCount() != null) {
            query.setSheetCount(query.getSheetCount() / 2);
        }
        return query;
    }

    private Integer countByQueryPlus(ExamStudentSearchQuery req) {
        StringBuilder sql = new StringBuilder();
        sql.append("select count(1) ");
        sql.append("from eb_exam_student s ");
        sql.append(getWhereSql(req));
        Query query = entityManager.createNativeQuery(sql.toString());
        return Integer.valueOf(query.getResultList().get(0).toString());
    }

    @SuppressWarnings("unchecked")
    private List<ExamStudent> findByQuerySql(ExamStudentSearchQuery req) {
        int offset = (req.getPageNumber() - 1) * req.getPageSize();
        StringBuilder sql = new StringBuilder();
        sql.append(" select s.sheet_count sheetCount,s.exam_number examNumber,s.exam_id examId,s.id,s.student_code studentCode,s.name");
        sql.append(" ,s.subject_code subjectCode,s.subject_name subjectName,s.is_upload upload,s.is_breach breach,s.is_absent absent,s.subjective_score subjectiveScore ");
        sql.append(" ,s.score_verify_time scoreVerifyTime,s.subjective_score_list subjectiveScoreList,s.objective_score objectiveScore,s.objective_score_list objectiveScoreList  ");
        sql.append(" ,s.subject_level subjectLevel,s.subject_category subjectCategory,s.college,s.class_name className,s.teacher ");
        sql.append(" ,s.exam_site examSite,s.exam_room examRoom,s.package_code packageCode");
        sql.append(" from eb_exam_student s ");
        sql.append(getWhereSql(req));
        sql.append(" order by s.id,s.exam_number ");
        sql.append(" limit " + offset + "," + req.getPageSize());
        Query query = entityManager.createNativeQuery(sql.toString());
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(ExamStudent.class));
        List<ExamStudent> ret = query.getResultList();
        return ret;
    }

    private String getWhereSql(ExamStudentSearchQuery query) {
        StringBuilder sql = new StringBuilder(" WHERE 1=1 ");
        if (query.getStartScore() != null) {
            if (query.getStartScore() == 0) {
                sql.append(" and (s.is_absent=1 or s.is_breach=1 or (s.subjective_score+s.objective_score>="
                        + query.getStartScore() + " and s.subjective_score+s.objective_score<=" + query.getEndScore()
                        + ") )");
            } else {
                sql.append(" and s.is_absent=0 and s.is_breach=0 and s.subjective_score+s.objective_score>="
                        + query.getStartScore() + " and s.subjective_score+s.objective_score<=" + query.getEndScore());
            }
        }
        if (query.getExamId() != null && query.getExamId() > 0) {
            sql.append(" and s.exam_id=" + query.getExamId());
        }
        if (query.getSchoolId() != null) {
            sql.append(" and s.school_id=" + query.getSchoolId());
        }
        // if (StringUtils.isNotBlank(query.getIds())) {
        // sql.append(" and s.id in("+query.getIds()+")");
        // }
        // if (query.getStudentIds() != null && query.getStudentIds().size() >
        // 0) {
        // sql.append(" and s.id in("+StringUtils.join(query.getStudentIds(),",")+")");
        // }
        if (StringUtils.isNotBlank(query.getExamNumber())) {
            sql.append(" and s.exam_number='" + query.getExamNumber() + "'");
        }
        if (StringUtils.isNotEmpty(query.getSecretNumber())) {
            sql.append(" and s.secret_number='" + query.getSecretNumber() + "'");
        }
        if (StringUtils.isNotEmpty(query.getStudentCode())) {
            sql.append(" and s.student_code='" + query.getStudentCode() + "'");
        }
        if (StringUtils.isNotBlank(query.getSubjectCode())) {
            sql.append(" and s.subject_code='" + query.getSubjectCode() + "'");

        }
        if (StringUtils.isNotBlank(query.getCampusName())) {
            sql.append(" and s.campus_name='" + query.getCampusName() + "'");
        }
        if (StringUtils.isNotBlank(query.getName())) {
            sql.append(" and s.name like '" + query.getName() + "%'");
        }
        if (StringUtils.isNotBlank(query.getBatchCode())) {
            sql.append(" and s.batch_code='" + query.getBatchCode() + "'");
        }
        if (StringUtils.isNotBlank(query.getPackageCode())) {
            sql.append(" and s.package_code='" + query.getPackageCode() + "'");
        }
        if (StringUtils.isNotBlank(query.getSubjectLevel())) {
            sql.append(" and s.subject_level='" + query.getSubjectLevel() + "'");
        }
        if (StringUtils.isNotBlank(query.getSubjectCategory())) {
            sql.append(" and s.subject_category='" + query.getSubjectCategory() + "'");
        }
        if (query.getObjectiveScore() != null) {
            sql.append(" and s.objective_score=" + query.getObjectiveScore());
        } else if (query.getObjectiveScoreGt() != null) {
            sql.append(" and s.objective_score>" + query.getObjectiveScoreGt());
        } else if (query.getObjectiveScoreLt() != null) {
            sql.append(" and s.objective_score<" + query.getObjectiveScoreLt());
        }
        if (query.getStatusSet() != null && query.getStatusSet().size() > 0) {
            List<String> codes = query.getStatusSet().stream().map(e -> e.name()).collect(Collectors.toList());
            sql.append(" and s.subjective_status in('" + StringUtils.join(codes, "','") + "')");
        }
        if (query.getSubjectiveScore() != null) {
            sql.append(" and s.subjective_score=" + query.getSubjectiveScore());
        } else if (query.getSubjectiveScoreGt() != null) {
            sql.append(" and s.subjective_score>" + query.getSubjectiveScoreGt());
        } else if (query.getSubjectiveScoreLt() != null) {
            sql.append(" and s.subjective_score<" + query.getSubjectiveScoreLt());
        }
        if (query.getUpload() != null) {
            sql.append(" and s.is_upload=" + (query.getUpload() ? 1 : 0));
        }
        if (query.getInspected() != null) {
            sql.append(" and s.inspected=" + (query.getInspected() ? 1 : 0));
        }
        if (query.getAbsent() != null) {
            if (query.getAbsent()) {// 缺考=缺考+ 未上传
                sql.append(" and (s.is_absent=1 or s.is_upload=0)");
            } else {
                sql.append(" and s.is_absent=" + (query.getAbsent() ? 1 : 0));
                sql.append(" and s.is_upload=1");
            }
        }
        if (query.getBreach() != null) {
            sql.append(" and s.is_breach=" + (query.getBreach() ? 1 : 0));
        }
        if (query.getException() != null) {
            sql.append(" and s.is_exception=" + (query.getException() ? 1 : 0));
        }
        if (query.getManualAbsent() != null) {
            sql.append(" and s.is_manual_absent=" + (query.getManualAbsent() ? 1 : 0));
        }
        if (query.getUploadTimeNotNull() != null) {
            sql.append(" and s.upload_time is not null");
        }
        if (StringUtils.isNotBlank(query.getSubjectCodeIn())) {
            sql.append(" and s.subject_code in('" + StringUtils.join(query.getSubjectCodeIn().split(","), "','") + "')");
        }
        if (StringUtils.isNotBlank(query.getCampusNameIn())) {
            sql.append(" and s.campus_name in('" + StringUtils.join(query.getCampusNameIn().split(","), "','") + "')");
        }
        if (StringUtils.isNotBlank(query.getExamSiteIn())) {
            sql.append(" and s.exam_site in('" + StringUtils.join(query.getExamSiteIn().split(","), "','") + "')");
        }
        if (StringUtils.isNotBlank(query.getSubjectCodeNotIn())) {
            sql.append(" and s.subject_code not in('" + StringUtils.join(query.getSubjectCodeNotIn().split(","), "','")
                    + "')");
        }
        if (StringUtils.isNotBlank(query.getCampusNameNotIn())) {
            sql.append(" and s.campus_name not in('" + StringUtils.join(query.getCampusNameNotIn().split(","), "','")
                    + "')");
        }
        if (StringUtils.isNotBlank(query.getExamSiteNotIn())) {
            sql.append(" and s.exam_site not in('" + StringUtils.join(query.getExamSiteNotIn().split(","), "','")
                    + "')");
        }
        if (StringUtils.isNotBlank(query.getPaperType())) {
            sql.append(" and s.paper_type='" + query.getPaperType() + "'");
        }
        if (StringUtils.isNotBlank(query.getCollege())) {
            sql.append(" and s.college='" + query.getCollege() + "'");
        }
        if (StringUtils.isNotBlank(query.getClassName())) {
            sql.append(" and s.class_name='" + query.getClassName() + "'");
        }
        if (StringUtils.isNotBlank(query.getTeacher())) {
            sql.append(" and s.teacher='" + query.getTeacher() + "'");
        }
        if (query.getInspectorId() != null) {
            sql.append(" and s.inspector_id=" + query.getTeacher());
        }
        if (StringUtils.isNotBlank(query.getExamSite())) {
            sql.append(" and s.exam_site='" + query.getExamSite() + "'");
        }
        if (StringUtils.isNotBlank(query.getExamRoom())) {
            sql.append(" and s.exam_room='" + query.getExamRoom() + "'");
        }
        if (query.getSheetCount() != null) {
            sql.append(" and s.sheet_count=" + query.getSheetCount());
        }
        if (Role.SCHOOL_VIEWER.equals(query.getRole())) {
            sql.append(" and exists (select 1 from eb_user_student us where us.user_id=" + query.getUserId()
                    + " and s.exam_number=us.exam_number)");
        }
        return sql.toString();
    }

    @Transactional
    @Override
    public boolean updateTrialById(Integer studentId, boolean trial) {
        return studentDao.updateTrialById(studentId, trial) > 0;
    }

    @Transactional
    @Override
    public void scoreVerifyView(Integer userId, Integer studentId) {
        studentDao.scoreVerifyTag(userId, new Date(), studentId);
    }

    @Transactional
    @Override
    public void cancelInspect(int examId, String subjectCode) {
        studentDao.cancelInspect(examId, subjectCode, false, true);
    }

    @Override
    public List<String> findDistinctExamSite(int examId) {
        return studentDao.findDistinctExamSite(examId);
    }

    @Override
    public long countByExamIdAndExamSite(int examId, String examSite) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setExamSite(examSite);
        return countByQuery(query);
    }

    @Override
    public long countByExamIdAndExamSite(int examId, String examSite, boolean upload) {
        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
        query.setExamId(examId);
        query.setExamSite(examSite);
        query.setUpload(upload);
        return countByQuery(query);
    }

    @Override
    public long countSheetCountByExamIdAndExamSite(int examId, String examSite) {
        Long count = studentDao.sumSheetCountByExamIdAndExamSite(examId, examSite);
        return count == null ? 0 : count;
    }
}
