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.ExamQuestionDao;
import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
import cn.com.qmth.stmms.biz.exam.model.SelectiveGroup;
import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
import cn.com.qmth.stmms.biz.exam.service.ExamService;
import cn.com.qmth.stmms.biz.exam.service.SelectiveGroupService;
import cn.com.qmth.stmms.biz.exam.service.query.ExamQuestionSearchQuery;
import cn.com.qmth.stmms.biz.utils.QuestionNumberComparator;
import cn.com.qmth.stmms.common.enums.ObjectivePolicy;
import cn.com.qmth.stmms.common.enums.ObjectiveStatus;
import cn.com.qmth.stmms.common.utils.BigDecimalUtils;

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.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import java.math.BigDecimal;
import java.util.*;

@Service
public class ExamQuestionServiceImpl extends BaseQueryService<ExamQuestion> implements ExamQuestionService {

    @Autowired
    private ExamQuestionDao questionDao;

    @Autowired
    private ExamService examService;

    @Autowired
    private SelectiveGroupService selectiveGroupService;

    @Transactional
    @Override
    public ExamQuestion save(ExamQuestion question) {
        return questionDao.save(question);
    }

    @Transactional
    @Override
    public ExamQuestion saveAndFlush(ExamQuestion question) {
        return questionDao.saveAndFlush(question);
    }

    @Transactional
    @Override
    public void save(List<ExamQuestion> questionList) {
        questionDao.save(questionList);
    }

    @Transactional
    @Override
    public void updateMainTitle(Integer examId, String subjectCode, boolean objective, Integer mainNumber,
            String mainTitle) {
        questionDao.updateMainTitle(examId, subjectCode, objective, mainNumber, mainTitle);
    }

    @Transactional
    @Override
    public void deleteByExamAndSubjectAndObjective(Integer examId, String subjectCode, boolean objective) {
        questionDao.deleteByExamIdAndSubjectCodeAndObjective(examId, subjectCode, objective);
    }

    @Transactional
    @Override
    public void deleteByExamAndSubjectAndObjectiveAndGroupNumber(Integer examId, String subjectCode, boolean objective,
            Integer groupNumber) {
        questionDao.deleteByExamIdAndSubjectCodeAndObjectiveAndGroupNumber(examId, subjectCode, objective, groupNumber);
    }

    @Override
    public List<ExamQuestion> findByExamAndSubjectAndObjective(Integer examId, String subjectCode, boolean objective) {
        return questionSort(questionDao.findByExamIdAndSubjectCodeAndObjective(examId, subjectCode, objective));
    }

    private List<ExamQuestion> questionSort(List<ExamQuestion> list) {
        // list.sort((o1, o2) -> {
        // int i = o1.getMainNumber() - o2.getMainNumber();
        // if (i == 0) {
        // if (o1.getSubNumber().contains("-")) {
        // String[] o1s = o1.getSubNumber().split("-");
        // String[] o2s = o2.getSubNumber().split("-");
        // int j = Integer.parseUnsignedInt(o1s[0]) -
        // Integer.parseUnsignedInt(o2s[0]);
        // if (j == 0) {
        // return Integer.parseUnsignedInt(o1s[1]) -
        // Integer.parseUnsignedInt(o2s[1]);
        // } else {
        // return j;
        // }
        // } else {
        // return Integer.parseUnsignedInt(o1.getSubNumber()) -
        // Integer.parseUnsignedInt(o2.getSubNumber());
        // }
        // }
        // return i;
        // });
        list.sort((o1, o2) -> QuestionNumberComparator.instance.compare(o1, o2));
        return list;
    }

    @Override
    public List<ExamQuestion> findByExamAndSubjectAndObjectiveGroupByNumber(Integer examId, String subjectCode,
            boolean objective) {
        List<String> paperTypeList = this.getPaperType(examId, subjectCode);
        List<ExamQuestion> list = new ArrayList<ExamQuestion>();
        if (paperTypeList.isEmpty()) {
            list = this.findByExamAndSubjectAndObjective(examId, subjectCode, objective);
        } else {
            list = this.findByExamAndSubjectAndObjectiveAndPaperType(examId, subjectCode, objective,
                    paperTypeList.get(0));
        }
        return questionSort(list);
    }

    @Override
    public List<ExamQuestion> findByExamAndSubjectAndObjectiveAndPaperType(Integer examId, String subjectCode,
            boolean objective, String paperType) {
        return questionSort(questionDao.findByExamIdAndSubjectCodeAndObjectiveAndPaperType(examId, subjectCode,
                objective, paperType, new Sort(Direction.ASC, "mainNumber", "subNumber")));
    }

    @Override
    public List<ExamQuestion> findByExamAndSubjectAndObjectiveAndGroupNumber(Integer examId, String subjectCode,
            boolean objective, Integer groupNumber) {
        return questionSort(questionDao.findByExamIdAndSubjectCodeAndObjectiveAndGroupNumber(examId, subjectCode,
                objective, groupNumber));
    }

    @Override
    public List<ExamQuestion> findByExamAndSubjectAndObjectiveAndGroupNumberNotEqual(Integer examId,
            String subjectCode, boolean objective, Integer groupNumber) {
        return questionSort(questionDao.findByExamIdAndSubjectCodeAndObjectiveAndGroupNumberNotEqual(examId,
                subjectCode, objective, groupNumber));
    }

    @Override
    public List<ExamQuestion> findByExamAndSubjectAndObjectiveAndMainNumber(Integer examId, String subjectCode,
            boolean objective, Integer mainNumber) {
        return questionSort(questionDao.findByExamIdAndSubjectCodeAndObjectiveAndMainNumber(examId, subjectCode,
                objective, mainNumber));
    }

    @Override
    public ExamQuestionSearchQuery findByQuery(ExamQuestionSearchQuery query) {
        checkQuery(query);
        Page<ExamQuestion> result = questionDao.findAll(buildSpecification(query), query);
        fillResult(result, query);
        if (result != null) {
            List<ExamQuestion> list = new ArrayList<>(query.getResult());
            query.setResult(questionSort(list));
        }
        return query;
    }

    @Override
    public long countByQuery(ExamQuestionSearchQuery query) {
        return questionDao.count(buildSpecification(query));
    }

    @Override
    public long countByExamAndSubjectAndObjectiveAndMainNumber(Integer examId, String subjectCode, boolean objective,
            Integer mainNumber) {
        return questionDao.countByExamIdAndSubjectCodeAndObjectiveAndMainNumber(examId, subjectCode, objective,
                mainNumber);
    }

    private Specification<ExamQuestion> buildSpecification(final ExamQuestionSearchQuery query) {
        return new Specification<ExamQuestion>() {

            @Override
            public Predicate toPredicate(Root<ExamQuestion> root, CriteriaQuery<?> cQuery, CriteriaBuilder cb) {

                List<Predicate> predicates = new LinkedList<Predicate>();
                if (query.getExamId() != null) {
                    predicates.add(cb.equal(root.get("examId"), query.getExamId()));
                }
                if (query.getSubjectCode() != null) {
                    predicates.add(cb.equal(root.get("subjectCode"), query.getSubjectCode()));
                }
                if (query.getPaperType() != null) {
                    predicates.add(cb.equal(root.get("paperType"), query.getPaperType()));
                }
                if (query.getObjective() != null) {
                    predicates.add(cb.equal(root.get("objective"), query.getObjective()));
                }
                if (query.getTotalCountGt() != null) {
                    predicates.add(cb.gt(root.get("totalCount").as(Integer.class), query.getTotalCountGt()));
                }
                if (query.getTotalScoreGt() != null) {
                    predicates.add(cb.gt(root.get("totalScore").as(Double.class), query.getTotalScoreGt()));
                }
                if (query.getZeroRateGt() != null) {
                    predicates.add(cb.ge(root.get("zeroCount").as(Integer.class),
                            cb.prod(root.get("totalCount").as(Integer.class), query.getZeroRateGt())));
                }
                if (query.getMainNumber() != null) {
                    predicates.add(cb.equal(root.get("mainNumber"), query.getMainNumber()));
                }
                return predicates.isEmpty() ? cb.conjunction() : cb.and(predicates.toArray(new Predicate[predicates
                        .size()]));
            }
        };
    }

    @Override
    public List<String> getPaperType(int examId, String subjectCode) {
        return questionDao.getPaperType(examId, subjectCode, true);
    }

    @Override
    public long countByExamAndSubjectAndObjectiveAndGroupNumber(int examId, String subjectCode, boolean objective,
            Integer groupNumber) {
        return questionDao.countByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode, objective, groupNumber);
    }

    @Override
    public long countByExamAndSubjectAndObjectiveAndMainNumberAndPaperType(Integer examId, String subjectCode,
            boolean objective, Integer mainNumber, String paperType) {
        return questionDao.countByExamIdAndSubjectCodeAndObjectiveAndMainNumber(examId, subjectCode, objective,
                mainNumber, paperType);
    }

    @Override
    public long countByExamAndSubjectAndObjectiveAndMainNumberAndSubNumber(Integer examId, String subjectCode,
            boolean objective, Integer mainNumber, String subNumber) {
        return questionDao.countByExamIdAndSubjectCodeAndObjectiveAndMainNumberAndSubNumber(examId, subjectCode,
                objective, mainNumber, subNumber);
    }

    @Transactional
    @Override
    public ExamQuestion updateObjectivePolicy(Integer questionId, ObjectivePolicy objectivePolicy) {
        ExamQuestion question = questionDao.findOne(questionId);
        question.setObjectivePolicy(objectivePolicy);
        questionDao.save(question);
        examService.updateObjectiveStatus(question.getExamId(), ObjectiveStatus.WAITING);
        return question;
    }

    @Override
    public ExamQuestion findById(Integer questionId) {
        return questionDao.findOne(questionId);
    }

    @Override
    public ExamQuestion findByExamAndSubjectAndObjectiveAndMainNumberAndSubNumber(Integer examId, String subjectCode,
            boolean objective, Integer mainNumber, String subNumber) {
        return questionDao.findByExamIdAndSubjectCodeAndObjectiveAndMainNumberAndSubNumber(examId, subjectCode,
                objective, mainNumber, subNumber);
    }

    @Override
    public List<ExamQuestion> findMainByExamAndSubjectAndObjective(Integer examId, String subjectCode, boolean objective) {
        List<ExamQuestion> list = new ArrayList<ExamQuestion>();
        Set<Integer> mainNumbers = new LinkedHashSet<>();
        List<ExamQuestion> all = questionDao.findByExamIdAndSubjectCodeAndObjective(examId, subjectCode, objective);
        for (ExamQuestion q : all) {
            if (!mainNumbers.contains(q.getMainNumber())) {
                mainNumbers.add(q.getMainNumber());
                List<ExamQuestion> questions = questionDao.findByExamIdAndSubjectCodeAndObjectiveAndMainNumber(examId,
                        subjectCode, objective, q.getMainNumber());
                BigDecimal totalScore = BigDecimal.ZERO;
                for (ExamQuestion examQuestion : questions) {
                    totalScore = totalScore.add(BigDecimal.valueOf(examQuestion.getTotalScore()));
                }
                q.setTotalScore(totalScore.doubleValue());
                list.add(q);
            }
        }
        return list;
    }

    @Override
    public double sumTotalScore(int examId, String subjectCode, boolean objective) {
        if (objective) {
            List<String> paperTypeList = this.getPaperType(examId, subjectCode);
            if (paperTypeList.size() > 1) {
                Double score = 0d;
                for (String paperType : paperTypeList) {
                    List<ExamQuestion> list = this.findByExamAndSubjectAndObjectiveAndPaperType(examId, subjectCode,
                            objective, paperType);
                    Double paperTypeScore = list.stream().mapToDouble(ExamQuestion::getTotalScore).sum();
                    if (paperTypeScore > score) {
                        score = paperTypeScore;
                    }
                }
                return score;
            } else {
                Double score = questionDao.sumTotalScore(examId, subjectCode, objective);
                return score != null ? score.doubleValue() : 0d;
            }
        } else {
            List<SelectiveGroup> selectiveGroups = selectiveGroupService
                    .findByExamIdAndSubjectCode(examId, subjectCode);
            if (!selectiveGroups.isEmpty()) {
                double totalScore = 0;
                Map<Integer, SelectiveGroup> map = new HashMap<Integer, SelectiveGroup>();
                for (SelectiveGroup selectiveGroup : selectiveGroups) {
                    map.put(selectiveGroup.getMainNumber(), selectiveGroup);
                }
                List<ExamQuestion> questions = this.findByExamAndSubjectAndObjective(examId, subjectCode, false);
                for (ExamQuestion examQuestion : questions) {
                    if (!map.containsKey(examQuestion.getMainNumber())) {
                        // 非选做题总分
                        totalScore = BigDecimalUtils.add(totalScore, examQuestion.getTotalScore());
                    }
                }
                double selectiveScore = selectiveGroupService.sumScoreByExamIdAndSubjectCode(examId, subjectCode);
                // 选做题分组总分
                totalScore = BigDecimalUtils.add(totalScore, selectiveScore);
                return totalScore;
            } else {
                Double score = questionDao.sumTotalScore(examId, subjectCode, objective);
                return score != null ? score.doubleValue() : 0d;
            }
        }
    }

    @Override
    @Transactional
    public void deleteById(Integer questionId) {
        questionDao.delete(questionId);
        questionDao.flush();
    }

    @Override
    public long countByExamIdAndSubjectAndObjectiveAndGroupNumberIsNull(Integer examId, String subjectCode,
            boolean objective) {
        return questionDao.countByExamIdAndSubjectAndObjectiveAndGroupNumberIsNull(examId, subjectCode, objective);
    }

    @Override
    public Set<String> FindSubjectCodeByExamIdAndObjectiveAndGroupNumberIsNull(int examId, boolean objective) {
        return questionDao.FindSubjectCodeByExamIdAndObjectiveAndGroupNumberIsNull(examId, objective);
    }

    @Override
    @Transactional
    public void resetByExamIdAndSubjectCodeAndObjectiveAndGroupNumber(Integer examId, String subjectCode,
            boolean objective, Integer groupNumber) {
        questionDao.resetByExamIdAndSubjectCodeAndObjectiveAndGroupNumber(examId, subjectCode, objective, groupNumber);
    }

    @Override
    public double sumTotalScoreByGroupNumber(int examId, String subjectCode, boolean objective, Integer groupNumber) {
        Double score = questionDao.sumTotalScoreByGroupNumber(examId, subjectCode, objective, groupNumber);
        return score != null ? score.doubleValue() : 0d;
    }

}
