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

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import cn.com.qmth.stmms.biz.exam.model.Exam;
import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
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.ExamStudentService;
import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
import cn.com.qmth.stmms.biz.file.enums.FormatType;
import cn.com.qmth.stmms.biz.file.service.FileService;
import cn.com.qmth.stmms.biz.lock.LockService;
import cn.com.qmth.stmms.biz.report.model.ReportSubject;
import cn.com.qmth.stmms.biz.report.model.ReportSubjectClass;
import cn.com.qmth.stmms.biz.report.model.ReportSubjectCollege;
import cn.com.qmth.stmms.biz.report.model.ReportSubjectGroup;
import cn.com.qmth.stmms.biz.report.model.ReportSubjectQuestion;
import cn.com.qmth.stmms.biz.report.model.ReportSubjectTeacher;
import cn.com.qmth.stmms.biz.report.query.ReportSubjectQuery;
import cn.com.qmth.stmms.biz.report.service.ReportService;
import cn.com.qmth.stmms.biz.report.service.ReportSubjectClassGroupService;
import cn.com.qmth.stmms.biz.report.service.ReportSubjectClassService;
import cn.com.qmth.stmms.biz.report.service.ReportSubjectCollegeService;
import cn.com.qmth.stmms.biz.report.service.ReportSubjectGroupService;
import cn.com.qmth.stmms.biz.report.service.ReportSubjectQuestionService;
import cn.com.qmth.stmms.biz.report.service.ReportSubjectRangeService;
import cn.com.qmth.stmms.biz.report.service.ReportSubjectService;
import cn.com.qmth.stmms.biz.report.service.ReportSubjectTeacherClassService;
import cn.com.qmth.stmms.biz.report.service.ReportSubjectTeacherService;
import cn.com.qmth.stmms.biz.report.utils.ReportContext;
import cn.com.qmth.stmms.biz.report.utils.ReportSubjectRangeDTO;
import cn.com.qmth.stmms.biz.utils.ExportExcel;
import cn.com.qmth.stmms.common.enums.ExamStatus;
import cn.com.qmth.stmms.common.enums.LockType;
import cn.com.qmth.stmms.common.enums.SubjectiveStatus;

import com.qmth.boot.tools.codec.CodecUtils;

@Component
public class ReportServiceImpl implements ReportService {

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

    @Autowired
    private ReportSubjectService reportSubjectService;

    @Autowired
    private ReportSubjectRangeService reportSubjectRangeService;

    @Autowired
    private ReportSubjectCollegeService reportSubjectCollegeService;

    @Autowired
    private ReportSubjectTeacherService reportSubjectTeacherService;

    @Autowired
    private ReportSubjectTeacherClassService reportSubjectTeacherClassService;

    @Autowired
    private ReportSubjectClassService reportSubjectClassService;

    @Autowired
    private ReportSubjectQuestionService reportSubjectQuestionService;

    @Autowired
    private ReportSubjectGroupService reportSubjectGroupService;

    @Autowired
    private ReportSubjectClassGroupService reportSubjectClassGroupService;

    @Autowired
    private LockService lockService;

    @Autowired
    private FileService fileService;

    @Autowired
    private ExamService examService;

    @Autowired
    private ExamStudentService studentService;

    @Autowired
    private ExamSubjectService subjectService;

    @Autowired
    private ExamQuestionService questionService;

    private ReportContext context;

    private Map<String, List<ExamQuestion>> objectiveMap;

    private Map<String, List<ExamQuestion>> subjectiveMap;

    private Map<String, ExamSubject> subjectMap;

    /**
     * 删除数据
     *
     * @param examId
     */
    @Override
    public void deleteData(Integer examId) {

        reportSubjectService.deleteByExamId(examId);

        reportSubjectRangeService.deleteByExamId(examId);

        reportSubjectCollegeService.deleteByExamId(examId);

        reportSubjectTeacherService.deleteByExamId(examId);

        reportSubjectTeacherClassService.deleteByExamId(examId);

        reportSubjectClassService.deleteByExamId(examId);

        reportSubjectQuestionService.deleteByExamId(examId);

        reportSubjectGroupService.deleteByExamId(examId);

        reportSubjectClassGroupService.deleteByExamId(examId);

    }

    /**
     * 删除数据
     *
     * @param examId
     * @param subjectCode
     */
    @Override
    public void deleteData(Integer examId, String subjectCode) {

        reportSubjectService.deleteByExamIdAndSubjectCode(examId, subjectCode);

        reportSubjectRangeService.deleteByExamIdAndSubjectCode(examId, subjectCode);

        reportSubjectCollegeService.deleteByExamIdAndSubjectCode(examId, subjectCode);

        reportSubjectTeacherService.deleteByExamIdAndSubjectCode(examId, subjectCode);

        reportSubjectTeacherClassService.deleteByExamIdAndSubjectCode(examId, subjectCode);

        reportSubjectClassService.deleteByExamIdAndSubjectCode(examId, subjectCode);

        reportSubjectQuestionService.deleteByExamIdAndSubjectCode(examId, subjectCode);

        reportSubjectGroupService.deleteByExamIdAndSubjectCode(examId, subjectCode);

        reportSubjectClassGroupService.deleteByExamIdAndSubjectCode(examId, subjectCode);

    }

    @Override
    public void reportExam(Integer examId, Set<String> subjectCodeSet) {

        subjectiveMap = new HashMap<String, List<ExamQuestion>>();
        objectiveMap = new HashMap<String, List<ExamQuestion>>();
        subjectMap = new HashMap<String, ExamSubject>();
        Exam exam = examService.findById(examId);
        if (exam == null || exam.getStatus() != ExamStatus.START) {
            // log.info("report exception for examId=" + examId +
            // ", exam is null or status error");
            return;
        }
        if (!lockService.trylock(LockType.SCORE_CALCULATE, examId)) {
            return;
        }
        if (subjectCodeSet == null) {
            subjectCodeSet = new HashSet<String>();
            List<ExamSubject> list = subjectService.list(exam.getId());
            for (ExamSubject subject : list) {
                if (!lockService.isLocked(LockType.SCORE_CALCULATE, subject.getExamId(), subject.getCode())) {
                    subjectCodeSet.add(subject.getCode());
                }
            }
        }
        log.info("start report for examId=" + examId + ", subjectCode count=" + subjectCodeSet.size());

        for (String subjectCode : subjectCodeSet) {
            // 尝试上锁，失败直接跳过
            if (!lockService.trylock(LockType.SCORE_CALCULATE, examId, subjectCode)) {
                log.warn("report locked for examId=" + examId + ", subjectCode=" + subjectCode);
                continue;
            }
            try {
                log.info("start report for examId=" + examId + ", subjectCode=" + subjectCode);
                // 删除原有统计数据
                this.deleteData(examId, subjectCode);
                context = new ReportContext(exam);
                int pageNumber = 1;
                int pageSize = 1000;
                List<ExamStudent> list = studentService.findByExamIdAndSubjectCode(examId, subjectCode, pageNumber,
                        pageSize);
                while (list != null && list.size() > 0) {
                    for (ExamStudent student : list) {
                        // 统计
                        statistic(student);
                    }
                    pageNumber++;
                    list = studentService.findByExamIdAndSubjectCode(examId, subjectCode, pageNumber, pageSize);
                }
                // 结束统计
                context.save();
                // 生成文件
                reoprtExcel(examId, subjectCode);
            } catch (Exception e) {
                log.error("report exception for examId=" + examId + ", subjectCode=" + subjectCode, e);
            } finally {
                lockService.unlock(LockType.SCORE_CALCULATE, examId, subjectCode);
                log.info("finish report for examId=" + examId + ", subjectCode=" + subjectCode);
            }
        }
        lockService.unlock(LockType.SCORE_CALCULATE, examId);
    }

    private void statistic(ExamStudent student) {
        ExamSubject subject = findExamSubject(student.getExamId(), student.getSubjectCode());
        if (SubjectiveStatus.MARKED.equals(student.getSubjectiveStatus())
                || (subject.getSubjectiveScore().doubleValue() == 0 && subject.getObjectiveScore() > 0)) {
            student.setSubject(subject);
            student.setObjectiveQuestionList(findQuestionList(student.getExamId(), student.getSubjectCode(),
                    student.getPaperType(), true));
            student.setSubjectiveQuestionList(findQuestionList(student.getExamId(), student.getSubjectCode(),
                    student.getPaperType(), false));
            context.process(student);
        }
    }

    private List<ExamQuestion> findQuestionList(int examId, String subjectCode, String paperType, boolean objective) {
        if (objective) {
            String key = subjectCode + "_" + StringUtils.trimToEmpty(paperType);
            List<ExamQuestion> list = objectiveMap.get(key);
            if (list == null) {
                list = questionService.findByExamAndSubjectAndObjectiveAndPaperType(examId, subjectCode, true,
                        paperType);
                objectiveMap.put(key, list);
            }
            return list;
        } else {
            List<ExamQuestion> list = subjectiveMap.get(subjectCode);
            if (list == null) {
                list = questionService.findByExamAndSubjectAndObjective(examId, subjectCode, false);
                subjectiveMap.put(subjectCode, list);
            }
            return list;
        }
    }

    private ExamSubject findExamSubject(int examId, String subjectCode) {
        ExamSubject subject = subjectMap.get(subjectCode);
        if (subject == null) {
            subject = subjectService.find(examId, subjectCode);
            subjectMap.put(subjectCode, subject);
        }
        return subject;
    }

    private void reoprtExcel(int examId, String subjectCode) throws IOException, Exception {
        uploadRange(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), "课程分段统计");
        uploadCollege(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), "电厂分析");
        uploadClass(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), "机组分析");
        uploadTeacher(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), "总评统计");
        uploadQuestion(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), true, "客观题分析");
        uploadQuestion(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), false, "主观题分析");
        uploadGroup(examId, subjectCode, findExamSubject(examId, subjectCode).getName(), "分组统计分析");
    }

    private void uploadGroup(int examId, String subjectCode, String subjectName, String name) throws IOException,
            Exception {
        ReportSubjectQuery query = new ReportSubjectQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setPageNumber(1);
        query.setPageSize(Integer.MAX_VALUE);
        List<ReportSubjectGroup> list = reportSubjectGroupService.findByQuery(query);
        for (ReportSubjectGroup r : list) {
            r.setAvgScore(new BigDecimal(r.getAvgScore()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setScoreRate(new BigDecimal(r.getScoreRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setStdev(new BigDecimal(r.getStdev()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setCoefficient(new BigDecimal(r.getCoefficient()).setScale(2, RoundingMode.HALF_UP).doubleValue());
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        new ExportExcel(name, ReportSubjectGroup.class).setDataList(list).write(os);
        os.flush();
        byte[] value = os.toByteArray();
        String md5 = CodecUtils.toHexString(CodecUtils.md5(value));
        fileService.uploadReport(new ByteArrayInputStream(value), md5, examId, subjectCode + "-" + subjectName, name,
                FormatType.XLSX);
    }

    private void uploadQuestion(int examId, String subjectCode, String subjectName, boolean objective, String name)
            throws IOException, Exception {
        ReportSubjectQuery query = new ReportSubjectQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setObjective(objective);
        query.setPageNumber(1);
        query.setPageSize(Integer.MAX_VALUE);
        List<ReportSubjectQuestion> list = reportSubjectQuestionService.findByQuery(query);
        for (ReportSubjectQuestion r : list) {
            r.setAvgScore(new BigDecimal(r.getAvgScore()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setStdev(new BigDecimal(r.getStdev()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setScoreRate(new BigDecimal(r.getScoreRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setFullScoreRate(new BigDecimal(r.getFullScoreRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        new ExportExcel(name, ReportSubjectQuestion.class).setDataList(list).write(os);
        os.flush();
        byte[] value = os.toByteArray();
        String md5 = CodecUtils.toHexString(CodecUtils.md5(value));
        fileService.uploadReport(new ByteArrayInputStream(value), md5, examId, subjectCode + "-" + subjectName, name,
                FormatType.XLSX);
    }

    private void uploadTeacher(int examId, String subjectCode, String subjectName, String name) throws IOException,
            Exception {
        ReportSubjectQuery query = new ReportSubjectQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setPageNumber(1);
        query.setPageSize(Integer.MAX_VALUE);
        List<ReportSubjectTeacher> list = reportSubjectTeacherService.findByQuery(query);
        for (ReportSubjectTeacher r : list) {
            r.setAvgScore(new BigDecimal(r.getAvgScore()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setExcellentRate(new BigDecimal(r.getExcellentRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setPassRate(new BigDecimal(r.getPassRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setRelativeAvgScore(new BigDecimal(r.getRelativeAvgScore()).setScale(2, RoundingMode.HALF_UP)
                    .doubleValue());
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        new ExportExcel(name, ReportSubjectTeacher.class).setDataList(list).write(os);
        os.flush();
        byte[] value = os.toByteArray();
        String md5 = CodecUtils.toHexString(CodecUtils.md5(value));
        fileService.uploadReport(new ByteArrayInputStream(value), md5, examId, subjectCode + "-" + subjectName, name,
                FormatType.XLSX);
    }

    private void uploadClass(int examId, String subjectCode, String subjectName, String name) throws IOException,
            Exception {
        ReportSubjectQuery query = new ReportSubjectQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setPageNumber(1);
        query.setPageSize(Integer.MAX_VALUE);
        List<ReportSubjectClass> list = reportSubjectClassService.findByQuery(query);
        for (ReportSubjectClass r : list) {
            r.setAvgScore(new BigDecimal(r.getAvgScore()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setExcellentRate(new BigDecimal(r.getExcellentRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setPassRate(new BigDecimal(r.getPassRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        new ExportExcel(name, ReportSubjectClass.class).setDataList(list).write(os);
        os.flush();
        byte[] value = os.toByteArray();
        String md5 = CodecUtils.toHexString(CodecUtils.md5(value));
        fileService.uploadReport(new ByteArrayInputStream(value), md5, examId, subjectCode + "-" + subjectName, name,
                FormatType.XLSX);
    }

    private void uploadCollege(int examId, String subjectCode, String subjectName, String name) throws IOException,
            Exception {
        ReportSubjectQuery query = new ReportSubjectQuery();
        query.setExamId(examId);
        query.setSubjectCode(subjectCode);
        query.setPageNumber(1);
        query.setPageSize(Integer.MAX_VALUE);
        List<ReportSubjectCollege> list = reportSubjectCollegeService.findByQuery(query);
        for (ReportSubjectCollege r : list) {
            r.setAvgScore(new BigDecimal(r.getAvgScore()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setExcellentRate(new BigDecimal(r.getExcellentRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
            r.setPassRate(new BigDecimal(r.getPassRate()).setScale(2, RoundingMode.HALF_UP).doubleValue());
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        new ExportExcel(name, ReportSubjectCollege.class).setDataList(list).write(os);
        os.flush();
        byte[] value = os.toByteArray();
        String md5 = CodecUtils.toHexString(CodecUtils.md5(value));
        fileService.uploadReport(new ByteArrayInputStream(value), md5, examId, subjectCode + "-" + subjectName, name,
                FormatType.XLSX);
    }

    private void uploadRange(int examId, String subjectCode, String subjectName, String name) throws IOException,
            Exception {
        List<ReportSubjectRangeDTO> list = new ArrayList<ReportSubjectRangeDTO>();
        ReportSubject subject = reportSubjectService.findOne(examId, subjectCode);
        if (subject != null) {
            JSONArray array = getScoreRange(subject.getScoreRange(), subject.getTotalScore(),
                    subject.getRealityCount(), 0.25);
            for (int i = 0; i < array.size(); i++) {
                JSONObject jsonObject = array.getJSONObject(i);
                Double score = jsonObject.getDouble("score");
                Integer rangeCount = jsonObject.getInt("rangeCount");
                Double rangeRate = jsonObject.getDouble("rangeRate");
                list.add(new ReportSubjectRangeDTO(score + "<=X<" + (score + 0.25), rangeCount, rangeRate));
            }
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        new ExportExcel(name, ReportSubjectRangeDTO.class).setDataList(list).write(os);
        os.flush();
        byte[] value = os.toByteArray();
        String md5 = CodecUtils.toHexString(CodecUtils.md5(value));
        fileService.uploadReport(new ByteArrayInputStream(value), md5, examId, subjectCode + "-" + subjectName, name,
                FormatType.XLSX);
    }

    private JSONArray getScoreRange(String scoreRange, double totalScore, Integer totalCount, double range) {
        JSONArray result = new JSONArray();
        JSONObject jsonObject = JSONObject.fromObject(scoreRange);
        int rangeCount = 0;
        int sumCount = 0;
        int total = (int) Math.ceil(totalScore / range);
        for (int i = total; i >= 0; i--) {
            double start = i * range;
            double end = (i + 1) * range;
            if (start > totalScore) {
                start = Math.ceil(totalScore);
            }
            if (end > totalScore) {
                end = Math.ceil(totalScore);
            }
            if (end < 0) {
                end = 0;
            }
            rangeCount = getSumCount(jsonObject, start, end);
            sumCount = sumCount + rangeCount;
            result.add(getRangeJson(totalCount, rangeCount, sumCount, i * range));
        }
        return result;
    }

    private int getSumCount(JSONObject jsonObject, double start, double end) {
        int sumCount = 0;
        int currentCount = 0;
        double rate = 0.25;
        if (start < end) {
//            for (int i = start; i < end; i++) {
//                currentCount = jsonObject.getInt(String.valueOf(i));
//                sumCount = sumCount + currentCount;
//            }
            double length = (end - start) / rate;
            for (int i = 0; i < length; i++) {
                currentCount = jsonObject.getInt(String.valueOf(start + i * rate));
                sumCount = sumCount + currentCount;
            }
        } else if (start > end) {
//            for (int i = end; i < start; i++) {
//                currentCount = jsonObject.getInt(String.valueOf(i));
//                sumCount = sumCount + currentCount;
//            }
            double length = (start - end) / rate;
            for (int i = 0; i < length; i++) {
                currentCount = jsonObject.getInt(String.valueOf(end + i * rate));
                sumCount = sumCount + currentCount;
            }
        } else {
            sumCount = jsonObject.getInt(String.valueOf(start));
        }
        return sumCount;
    }

    private JSONObject getRangeJson(Integer totalCount, int rangeCount, int sumCount, double score) {
        JSONObject value = new JSONObject();
        value.accumulate("score", score);
        value.accumulate("rangeCount", rangeCount);
        value.accumulate("rangeRate", rangeCount * 100.0 / totalCount);
        value.accumulate("sumCount", sumCount);
        value.accumulate("sumRate", sumCount * 100.0 / totalCount);
        return value;
    }
}
