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

import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
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 cn.com.qmth.stmms.biz.common.BaseQueryService;
import cn.com.qmth.stmms.biz.mark.dao.MarkLibraryDao;
import cn.com.qmth.stmms.biz.mark.model.MarkLibrary;
import cn.com.qmth.stmms.biz.mark.query.MarkLibrarySearchQuery;
import cn.com.qmth.stmms.biz.mark.service.MarkLibraryService;
import cn.com.qmth.stmms.biz.utils.TaskLock;
import cn.com.qmth.stmms.biz.utils.TaskLockUtil;
import cn.com.qmth.stmms.common.enums.LibraryStatus;

@Service
public class MarkLibraryServiceImpl extends BaseQueryService<MarkLibrary> implements MarkLibraryService {

    @Autowired
    private MarkLibraryDao libraryDao;

    public static final int UN_SELECTIVE_SCORE = -1;

    @Override
    public MarkLibrary findById(int id) {
        return libraryDao.findOne(id);
    }

    @Override
    public List<MarkLibrary> findByStudentAndGroup(int studentId, int groupNumber) {
        return libraryDao.findByStudentIdAndGroupNumber(studentId, groupNumber);
    }

    @Override
    public List<MarkLibrary> findByStudentId(int studentId) {
        return libraryDao.findByStudentId(studentId);
    }

    @Override
    public List<MarkLibrary> findUnMarked(Integer examId, String subjectCode, Integer groupNumber, Integer markerId,
            Integer userId, boolean filterClass, int pageNumber, int pageSize, Set<LibraryStatus> statusSet) {
        MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
        query.setPageNumber(pageNumber);
        query.setPageSize(pageSize);
        return filterClass ? libraryDao.findUnMarkedFilterClass(examId, subjectCode, groupNumber, markerId, userId,
                statusSet, query) : libraryDao.findUnMarked(examId, subjectCode, groupNumber, markerId, statusSet,
                query);
    }

    @Override
    public long countByExamAndSubjectAndGroupAndStatus(int examId, String subjectCode, int groupNumber,
            LibraryStatus... status) {
        MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setGroupNumber(groupNumber);
        for (LibraryStatus libraryStatus : status) {
            query.addStatus(libraryStatus);
        }
        return countByQuery(query);
    }

    @Override
    public long countByMarker(int markerId) {
        MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
        query.setMarkerId(markerId);
        // query.addStatus(LibraryStatus.MARKED);
        return countByQuery(query);
    }

    @Override
    public long countByQuery(final MarkLibrarySearchQuery query) {
        return libraryDao.count(buildSpecification(query));
    }

    @Override
    public MarkLibrarySearchQuery findByQuery(final MarkLibrarySearchQuery query) {
        checkQuery(query);
        query.addSort("examNumber", Direction.ASC);
        Page<MarkLibrary> result = libraryDao.findAll(buildSpecification(query), query);
        fillResult(result, query);
        return query;
    }

    private Specification<MarkLibrary> buildSpecification(final MarkLibrarySearchQuery query) {
        return new Specification<MarkLibrary>() {

            @Override
            public Predicate toPredicate(Root<MarkLibrary> root, CriteriaQuery<?> cQuery, CriteriaBuilder cb) {
                List<Predicate> predicates = new LinkedList<Predicate>();
                if (query.getExamId() > 0) {
                    predicates.add(cb.equal(root.get("examId"), query.getExamId()));
                }
                if (StringUtils.isNotBlank(query.getSubjectCode())) {
                    predicates.add(cb.equal(root.get("subjectCode"), query.getSubjectCode()));
                }
                if (query.getCampusId() > 0) {
                    predicates.add(cb.equal(root.get("campusId"), query.getCampusId()));
                }
                if (query.getGroupNumber() != null) {
                    predicates.add(cb.equal(root.get("groupNumber"), query.getGroupNumber()));
                }
                if (StringUtils.isNotBlank(query.getExamNumber())) {
                    predicates.add(cb.equal(root.get("examNumber"), query.getExamNumber()));
                }
                if (StringUtils.isNotBlank(query.getSecretNumber())) {
                    predicates.add(cb.equal(root.get("secretNumber"), query.getSecretNumber()));
                }
                if (query.getStudentId() != null) {
                    predicates.add(cb.equal(root.get("studentId"), query.getStudentId()));
                }
                if (query.getMarkerId() > 0) {
                    predicates.add(cb.equal(root.get("markerId"), query.getMarkerId()));
                }
                if (query.getStatusSet() != null && query.getStatusSet().size() > 0) {
                    List<Predicate> sub = new LinkedList<>();
                    for (LibraryStatus status : query.getStatusSet()) {
                        sub.add(cb.equal(root.get("status").as(LibraryStatus.class), status));
                    }
                    predicates.add(cb.or(sub.toArray(new Predicate[sub.size()])));
                }
                if (query.getMarkerScore() != null) {
                    predicates.add(cb.equal(root.get("markerScore"), query.getMarkerScore()));
                }
                if (query.getStartScore() != null) {
                    Predicate predicate1 = cb.ge(root.get("markerScore"), query.getStartScore());
                    predicates.add(predicate1);
                }
                if (query.getEndScore() != null) {
                    Predicate predicate2 = cb.le(root.get("markerScore"), query.getEndScore());
                    predicates.add(predicate2);
                }
                if (query.getMarkerScore() != null && query.getStartScore() != null) {
                    Predicate predicate1 = cb.ge(root.get("markerScore"), query.getStartScore());
                    Predicate predicate2 = cb.le(root.get("markerScore"), query.getEndScore());
                    predicates.add(cb.and(predicate1, predicate2));
                }
                if (query.isUnselective()) {
                    Predicate predicate1 = cb.equal(root.get("markerScore"), UN_SELECTIVE_SCORE);
                    Predicate predicate2 = cb.equal(root.get("headerScore"), UN_SELECTIVE_SCORE);
                    predicates.add(cb.or(predicate1, predicate2));
                }
                if (StringUtils.isNotBlank(query.getQuestionScore())) {
                    Predicate predicate1 = cb.like(root.get("markerScoreList").as(String.class),
                            query.getQuestionScore() + ",%");
                    Predicate predicate2 = cb.like(root.get("markerScoreList").as(String.class),
                            "%," + query.getQuestionScore());
                    Predicate predicate3 = cb.like(root.get("markerScoreList").as(String.class),
                            "%," + query.getQuestionScore() + ",%");
                    Predicate predicate4 = cb.equal(root.get("markerScoreList"), query.getQuestionScore());
                    predicates.add(cb.or(predicate1, predicate2, predicate3, predicate4));
                }
                if (query.getHeaderId() != null && query.getHeaderId() > 0) {
                    predicates.add(cb.equal(root.get("headerId"), query.getHeaderId()));
                }
                if (StringUtils.isNotBlank(query.getRejectReason())) {
                    predicates.add(cb.like(root.get("rejectReason").as(String.class), query.getRejectReason() + "%"));
                }
                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));
                    }
                }
                return predicates.isEmpty() ? cb.conjunction() : cb.and(predicates.toArray(new Predicate[predicates
                        .size()]));
            }
        };
    }

    @Override
    public List<MarkLibrary> findByStudentIdAndStatus(int studentId, LibraryStatus status) {
        return libraryDao.findByStudentIdAndStatus(studentId, status);
    }

    @Override
    public List<Object[]> findScoreCount(int examId, String subjectCode, Integer groupNumber, Integer id,
            LibraryStatus... status) {
        return libraryDao.findScoreCount(examId, subjectCode, groupNumber, id, status);
    }

    @Override
    public List<Double> findScore(int examId, String subjectCode, Integer groupNumber, LibraryStatus... status) {
        return libraryDao.findScore(examId, subjectCode, groupNumber, status);
    }

    @Override
    @Transactional
    public MarkLibrary save(MarkLibrary library) {
        return libraryDao.save(library);
    }

    @Override
    @Transactional
    public void save(List<MarkLibrary> libraryList) {
        libraryDao.save(libraryList);
    }

    @Override
    public void releaseByLibrary(MarkLibrary library) {
        TaskLock taskLock = TaskLockUtil.getInspectedLibraryTask(getGroupKey(library));
        taskLock.remove(library.getId(), 1);
    }

    @Override
    public void releaseByUserId(Integer examId, String subjectCode, Integer groupNumber, Integer userId) {
        TaskLock taskLock = TaskLockUtil.getInspectedLibraryTask(examId + "_" + subjectCode + "_" + groupNumber);
        taskLock.clear(userId);
    }

    @Override
    public boolean hasApplied(MarkLibrary library, Integer userId) {
        TaskLock taskLock = TaskLockUtil.getInspectedLibraryTask(getGroupKey(library));
        return taskLock.exist(library.getId(), 1, userId);
    }

    @Override
    public boolean applyLibrary(MarkLibrary library, Integer userId) {
        TaskLock taskLock = TaskLockUtil.getInspectedLibraryTask(getGroupKey(library));
        boolean lock = taskLock.add(library.getId(), 1, userId);
        // 上锁失败直接返回
        if (!lock) {
            return false;
        }
        // 重复校验任务状态
        if (library.getStatus().equals(LibraryStatus.MARKED)) {
            return true;
        } else {
            taskLock.remove(library.getId(), 1, userId);
            return false;
        }
    }

    private String getGroupKey(MarkLibrary library) {
        return library.getExamId() + "_" + library.getSubjectCode() + "_" + library.getGroupNumber();
    }

    @Override
    public Set<String> findSubjectUnFinishByExamId(int examId) {
        return libraryDao.findSubjectUnFinishByExamId(examId, LibraryStatus.MARKED, LibraryStatus.ARBITRATED,
                LibraryStatus.INSPECTED);
    }

    @Override
    public long countByExamAndSubjectAndGroupAndFilterClass(Integer examId, String subjectCode, Integer groupNumber,
            Integer userId) {
        return libraryDao.countFilterClass(examId, subjectCode, groupNumber, userId);
    }

    @Override
    public long countMarkedByExamAndSubjectAndGroupAndFilterClass(Integer examId, String subjectCode,
            Integer groupNumber, Integer userId, LibraryStatus... status) {
        return libraryDao.countMarkedFilterClass(examId, subjectCode, groupNumber, userId, status);
    }

    @Override
    public MarkLibrarySearchQuery findHistory(MarkLibrarySearchQuery query) {
        checkQuery(query);
        Page<MarkLibrary> result = libraryDao.findAll(buildHistotySpecification(query), query);
        fillResult(result, query);
        return query;
    }

    private Specification<MarkLibrary> buildHistotySpecification(final MarkLibrarySearchQuery query) {
        return new Specification<MarkLibrary>() {

            @Override
            public Predicate toPredicate(Root<MarkLibrary> root, CriteriaQuery<?> cQuery, CriteriaBuilder cb) {
                List<Predicate> predicates = new LinkedList<Predicate>();
                if (query.getExamId() > 0) {
                    predicates.add(cb.equal(root.get("examId"), query.getExamId()));
                }
                if (StringUtils.isNotBlank(query.getSubjectCode())) {
                    predicates.add(cb.equal(root.get("subjectCode"), query.getSubjectCode()));
                }
                if (query.getGroupNumber() != null) {
                    predicates.add(cb.equal(root.get("groupNumber"), query.getGroupNumber()));
                }
                if (StringUtils.isNotBlank(query.getExamNumber())) {
                    predicates.add(cb.equal(root.get("examNumber"), query.getExamNumber()));
                }
                if (StringUtils.isNotBlank(query.getSecretNumber())) {
                    predicates.add(cb.like(root.get("secretNumber"), "%" + query.getSecretNumber() + "%"));
                }
                if (query.getStudentId() != null) {
                    predicates.add(cb.equal(root.get("studentId"), query.getStudentId()));
                }
                if (query.getMarkerId() > 0) {
                    predicates.add(cb.equal(root.get("markerId"), query.getMarkerId()));
                }
                if (query.getStatusSet() != null && query.getStatusSet().size() > 0) {
                    List<Predicate> sub = new LinkedList<>();
                    for (LibraryStatus status : query.getStatusSet()) {
                        sub.add(cb.equal(root.get("status").as(LibraryStatus.class), status));
                    }
                    predicates.add(cb.or(sub.toArray(new Predicate[sub.size()])));
                }
                if (query.getMarkerScore() != null) {
                    predicates.add(cb.equal(root.get("markerScore"), query.getMarkerScore()));
                }
                if (query.getStartScore() != null) {
                    Predicate predicate1 = cb.ge(root.get("markerScore"), query.getStartScore());
                    predicates.add(predicate1);
                }
                if (query.getEndScore() != null) {
                    Predicate predicate2 = cb.le(root.get("markerScore"), query.getEndScore());
                    predicates.add(predicate2);
                }
                return predicates.isEmpty() ? cb.conjunction() : cb.and(predicates.toArray(new Predicate[predicates
                        .size()]));
            }
        };
    }
}
