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

import java.util.HashSet;
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.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.exam.dao.ExamSubjectDao;
import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
import cn.com.qmth.stmms.biz.exam.service.query.ExamSubjectSearchQuery;
import cn.com.qmth.stmms.common.enums.ExamSubjectStatus;

@Service("examSubjectService")
public class ExamSubjectServiceImpl extends BaseQueryService<ExamSubject> implements ExamSubjectService {

    @Autowired
    private ExamSubjectDao subjectDao;

    @Autowired
    private ExamQuestionService questionService;

    @Transactional
    @Override
    // @CachePut(value = "exam_subject_cache", key =
    // "T(String).valueOf(#subject.examId)+'-'+#subject.code", condition =
    // "#subject!=null && #subject.examId!=null && #subject.code!=null")
    public ExamSubject save(ExamSubject subject) {
        return subjectDao.save(subject);
    }

    @Override
    // @Cacheable(value = "exam_subject_cache", key =
    // "T(String).valueOf(#examId)+'-'+#code", condition =
    // "#examId>0 && #code!=null")
    public ExamSubject find(int examId, String code) {
        return subjectDao.findByExamIdAndCode(examId, code);
    }

    @Override
    public List<ExamSubject> list(int examId) {
        return subjectDao.findByExamId(examId);
    }

    @Override
    public List<String> listLevel(int examId) {
        return subjectDao.findDistinctLevel(examId);
    }

    @Override
    public List<String> listCategory(int examId) {
        return subjectDao.findDistinctCategory(examId);
    }

    @Override
    public List<ExamSubject> list(int examId, int uploadCountGt) {
        return subjectDao.findByExamIdAndUploadCountGt(examId, uploadCountGt);
    }

    @Override
    public List<ExamSubject> list(int examId, ExamSubjectStatus status, int uploadCountGt) {
        return subjectDao.findByExamIdAndStatusAndUploadCountGt(examId, status, uploadCountGt);
    }

    @Override
    public List<ExamSubject> list(int examId, ExamSubjectStatus status) {
        if (status != null) {
            Set<ExamSubjectStatus> statusSet = new HashSet<ExamSubjectStatus>();
            statusSet.add(status);
            return subjectDao.findByExamIdAndStatus(examId, statusSet);
        } else {
            return list(examId);
        }
    }

    @Override
    public List<ExamSubject> list(int examId, Set<ExamSubjectStatus> statusSet) {
        if (statusSet != null && statusSet.size() > 0) {
            return subjectDao.findByExamIdAndStatus(examId, statusSet);
        } else {
            return list(examId);
        }
    }

    @Override
    public List<ExamSubject> list(Set<ExamSubjectStatus> statusSet) {
        if (statusSet != null && statusSet.size() > 0) {
            return subjectDao.findByStatus(statusSet);
        } else {
            return new LinkedList<ExamSubject>();
        }
    }

    @Override
    public long count(int examId) {
        return subjectDao.countByExamId(examId);
    }

    @Override
    public long count(int examId, Set<ExamSubjectStatus> statusSet) {
        if (statusSet != null && statusSet.size() > 0) {
            return subjectDao.countByExamIdAndStatus(examId, statusSet);
        } else {
            return subjectDao.countByExamId(examId);
        }
    }

    @Override
    public long count(int examId, ExamSubjectStatus status) {
        if (status != null) {
            Set<ExamSubjectStatus> statusSet = new HashSet<ExamSubjectStatus>();
            statusSet.add(status);
            return subjectDao.countByExamIdAndStatus(examId, statusSet);
        } else {
            return subjectDao.countByExamId(examId);
        }
    }

    @Override
    public ExamSubjectSearchQuery findByQuery(ExamSubjectSearchQuery query) {
        checkQuery(query);
        Page<ExamSubject> result = subjectDao.findAll(buildSpecification(query), query);
        fillResult(result, query);
        return query;
    }

    @Override
    public long countByQuery(ExamSubjectSearchQuery query) {
        return subjectDao.count(buildSpecification(query));
    }

    @Transactional
    @Override
    // @CacheEvict(value = "exam_subject_cache", beforeInvocation = true,
    // allEntries = true)
    public void delete(ExamSubject markSubject) {
        subjectDao.delete(markSubject);
    }

    @Transactional
    @Override
    // @CacheEvict(value = "exam_subject_cache", beforeInvocation = true,
    // allEntries = true)
    public void deleteByExamId(int examId) {
        subjectDao.deleteByExamId(examId);
    }

    @Transactional
    @Override
    // @CacheEvict(value = "exam_subject_cache", beforeInvocation = true,
    // allEntries = true)
    public void changeStatus(int examId, ExamSubjectStatus status) {
        subjectDao.updateStatusByExamId(examId, status);
    }

    @Transactional
    @Override
    // @CacheEvict(value = "exam_subject_cache", beforeInvocation = true,
    // allEntries = true)
    public void updateTotalScore(int examId) {
        subjectDao.updateTotalScoreByExamId(examId);
    }

    @Transactional
    @Override
    // @CacheEvict(value = "exam_subject_cache", beforeInvocation = true,
    // allEntries = true)
    public void updateScore(int examId, String code) {
        subjectDao.updateObjectiveScore(examId, code,
                questionService.sumScoreByExamAndSubjectAndObjective(examId, code, true));
        subjectDao.updateSubjectiveScore(examId, code,
                questionService.sumScoreByExamAndSubjectAndObjective(examId, code, false));
        subjectDao.updateTotalScoreByExamIdAndCode(examId, code);
    }

    private Specification<ExamSubject> buildSpecification(final ExamSubjectSearchQuery query) {
        return new Specification<ExamSubject>() {

            @Override
            public Predicate toPredicate(Root<ExamSubject> root, CriteriaQuery<?> cQuery, CriteriaBuilder cb) {
                List<Predicate> predicates = new LinkedList<Predicate>();
                if (query.getExamId() > 0) {
                    predicates.add(cb.equal(root.get("pk").get("examId"), query.getExamId()));
                }
                if (StringUtils.isNotEmpty(query.getCode())) {
                    predicates.add(cb.equal(root.get("pk").get("code"), query.getCode()));
                }
                if (StringUtils.isNotBlank(query.getLevel())) {
                    predicates.add(cb.equal(root.get("level"), query.getLevel()));
                }
                if (StringUtils.isNotBlank(query.getCategory())) {
                    predicates.add(cb.equal(root.get("category"), query.getCategory()));
                }
                if (query.getStatus() != null) {
                    predicates.add(cb.equal(root.get("status"), query.getStatus()));
                }
                if (query.getTotalScoreNotEqual() != null) {
                    predicates.add(cb.notEqual(root.get("totalScore"), query.getTotalScoreNotEqual()));
                }
                if (query.getSubjectiveScoreNotEqual() != null) {
                    predicates.add(cb.notEqual(root.get("subjectiveScore"), query.getSubjectiveScoreNotEqual()));
                }
                if (query.getUploadCountGt() != null) {
                    predicates.add(cb.gt(root.get("uploadCount").as(Integer.class), query.getUploadCountGt()));
                }
                if (query.getUploadCountEqual() != null) {
                    predicates.add(cb.equal(root.get("uploadCount").as(Integer.class), query.getUploadCountEqual()));
                }
                return predicates.isEmpty() ? cb.conjunction()
                        : cb.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        };
    }

    @Override
    @Transactional
    public void updateUploadCount(Integer examId, String subjectCode) {
        subjectDao.updateUploadByExamIdAndCode(examId, subjectCode);
    }

    @Override
    @Transactional
    public void updateUploadCount(Integer examId) {
        subjectDao.updateUploadByExamId(examId);
    }
}
