/*
 * ************************************************* Copyright (c) 2018 QMTH.
 * All Rights Reserved. Created by Deason on 2018-07-12 15:33:35.
 * *************************************************
 */

package cn.com.qmth.examcloud.core.questions.service.converter;

import java.io.File;
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 org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cn.com.qmth.examcloud.core.questions.base.IoUtils;
import cn.com.qmth.examcloud.core.questions.base.converter.model.Constants;
import cn.com.qmth.examcloud.core.questions.base.converter.model.PolicyType;
import cn.com.qmth.examcloud.core.questions.base.converter.model.QuesStructType;
import cn.com.qmth.examcloud.core.questions.base.converter.model.dps.PaperAnswerDto;
import cn.com.qmth.examcloud.core.questions.base.converter.model.dps.PaperDetailDto;
import cn.com.qmth.examcloud.core.questions.base.converter.model.dps.PaperDto;
import cn.com.qmth.examcloud.core.questions.base.converter.model.dps.PolicyDto;
import cn.com.qmth.examcloud.core.questions.base.converter.model.dps.PolicyWeightDto;
import cn.com.qmth.examcloud.core.questions.base.converter.model.dps.QuestionDto;
import cn.com.qmth.examcloud.core.questions.base.converter.model.dps.QuestionOptionDto;
import cn.com.qmth.examcloud.core.questions.base.converter.model.dps.SimpleQuestionDto;
import cn.com.qmth.examcloud.core.questions.base.converter.model.platform.PaperDetailVo;
import cn.com.qmth.examcloud.core.questions.base.converter.model.platform.PaperVo;
import cn.com.qmth.examcloud.core.questions.base.converter.model.platform.QuestionVo;
import cn.com.qmth.examcloud.core.questions.base.converter.utils.Cryptogram;
import cn.com.qmth.examcloud.core.questions.base.converter.utils.FileUtil;
import cn.com.qmth.examcloud.commons.util.JsonMapper;
import cn.com.qmth.examcloud.core.questions.base.converter.utils.NumUtil;
import cn.com.qmth.examcloud.core.questions.base.enums.ExamFileType;
import cn.com.qmth.examcloud.core.questions.dao.entity.Paper;
import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetail;
import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnit;
import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
import cn.com.qmth.examcloud.core.questions.service.PaperDetailService;
import cn.com.qmth.examcloud.core.questions.service.PaperService;

/**
 * 分布式印刷考试的试卷转换服务类
 */
@Service
public class PrintExamPaperService {

    private static final Logger log = LoggerFactory.getLogger(PrintExamPaperService.class);

    @Autowired
    private PaperService paperService;

    @Autowired
    private PaperDetailService paperDetailService;

    public void downloadPaper(List<Paper> oldPapers, String dirPath, String password) {
        if (oldPapers == null || oldPapers.size() == 0) {
            throw new IllegalArgumentException("Paper list size is empty.");
        }
        if (StringUtils.isBlank(dirPath)) {
            throw new IllegalArgumentException("dirPath must not be empty.");
        }
        if (StringUtils.isNotBlank(password)) {
            if (!password.matches("^[0-9A-Za-z]{6,12}$")) {
                throw new IllegalArgumentException("加密密码请输入6至12位的数字或字母！");
            }
        }
        log.info("exportPrintExamPaper start!");
        // 提取题库试卷的数据
        List<PaperVo> papers = new ArrayList<>();
        for (Paper oldPaper : oldPapers) {
            List<PaperDetail> oldDetails = paperService.findPaperDetailsById(oldPaper.getId());
            if (oldDetails == null || oldDetails.size() == 0) {
                continue;
            }
            // 封装试卷
            PaperVo paper = new PaperVo();
            paper.setId(oldPaper.getId());
            paper.setName(oldPaper.getName());
            paper.setCourseCode(oldPaper.getCourse().getCode());
            paper.setCourseName(oldPaper.getCourse().getName());
            paper.setTotalScore(oldPaper.getTotalScore());
            paper.setDetailCount(oldPaper.getPaperDetailCount());
            paper.setHasVideo(0);
            List<PaperDetailVo> details = new ArrayList<>();
            for (PaperDetail oldDetail : oldDetails) {
                List<PaperDetailUnit> oldDetailUnits = paperDetailService.getUnitsByPaperDetailId(oldDetail.getId());
                if (oldDetailUnits == null || oldDetailUnits.size() == 0) {
                    continue;
                }
                // 封装大题
                PaperDetailVo detail = new PaperDetailVo();
                detail.setNumber(oldDetail.getNumber());
                detail.setName(oldDetail.getName());
                detail.setTotalScore(oldDetail.getScore());
                detail.setQuestionCount(oldDetail.getUnitCount());
                List<QuestionVo> questions = new ArrayList<>();
                for (int i = 0; i < oldDetailUnits.size(); i++) {
                    PaperDetailUnit oldDetailUnit = oldDetailUnits.get(i);
                    Question oldQuestion = oldDetailUnit.getQuestion();
                    // 封装试题
                    QuestionVo question = new QuestionVo();
                    question.setNumber(i + 1);
                    question.setId(oldDetailUnit.getId());
                    question.setScore(oldDetailUnit.getScore());
                    question.setStructType(oldDetailUnit.getQuestionType().getId().intValue());
                    question.setObjective(QuesStructType.isObjective(question.getStructType()));
                    question.setBody(ConvertQuestion.parseBody(oldQuestion.getQuesBody()));
                    question.setAnswer(ConvertQuestion.parseAnswer(oldQuestion.getQuesAnswer()));
                    question.setOptions(ConvertQuestion.parseOption(oldQuestion));
                    questions.add(question);

                    // 封装套题
                    List<Question> oldSubQuestions = oldQuestion.getSubQuestions();
                    if (oldSubQuestions == null || oldSubQuestions.size() == 0) {
                        continue;
                    }
                    List<QuestionVo> subQuestions = new ArrayList<>();
                    for (int j = 0; j < oldSubQuestions.size(); j++) {
                        Question oldSubQuestion = oldSubQuestions.get(j);
                        QuestionVo subQuestion = new QuestionVo();
                        subQuestion.setNumber(j + 1);
                        subQuestion.setId(oldSubQuestion.getId());
                        subQuestion.setScore(oldSubQuestion.getScore());
                        subQuestion.setStructType(oldSubQuestion.getQuestionType().getId().intValue());
                        subQuestion.setObjective(QuesStructType.isObjective(subQuestion.getStructType()));
                        subQuestion.setBody(ConvertQuestion.parseBody(oldSubQuestion.getQuesBody()));
                        subQuestion.setAnswer(ConvertQuestion.parseAnswer(oldSubQuestion.getQuesAnswer()));
                        subQuestion.setOptions(ConvertQuestion.parseOption(oldSubQuestion));
                        subQuestions.add(subQuestion);
                    }
                    question.setSubQuestions(subQuestions);
                }
                detail.setQuestions(questions);
                detail.setQuestionCount(questions.size());
                details.add(detail);
            }
            paper.setDetails(details);
            papers.add(paper);
        }
        this.convert(papers, dirPath, password);
        log.info("exportPrintExamPaper end!");
    }

    /**
     * 将题库试卷格式转换为分布式印刷的试卷格式
     */
    public void convert(List<PaperVo> papers, String dirPath, String password) {
        if (papers == null || papers.size() == 0) {
            log.warn("No paper to convert.");
            return;
        }
        // 临时目录
        dirPath = dirPath.replaceAll("\\\\", "/");
        final String tempName = ExamFileType.PRINT_EXAM_PACKAGE.getName() + "_" + FileUtil.generateDateName();
        final String tempDir = dirPath + "/" + tempName;
        final String tempFilePath = dirPath + "/" + tempName + ".zip";
        FileUtil.makeDirs(tempDir);

        // 汇总课程编码
        Set<String> courseCodes = new HashSet<>();
        for (PaperVo paper : papers) {
            courseCodes.add(paper.getCourseCode());
        }

        // 按"课程编码"封装试卷，数据文件转换保存到临时目录
        this.parseCoursePapers(courseCodes, papers, tempDir, password);

        // 压缩转换后的数据包
        File tempDirFile = new File(tempDir);
        File tempFile = new File(tempFilePath);
        FileUtil.doZip(tempDirFile, tempFile);
        FileUtils.deleteQuietly(tempDirFile);
    }

    /**
     * 按"课程编码"汇总试卷，数据文件转换保存到临时目录
     */
    private void parseCoursePapers(Set<String> courseCodes, List<PaperVo> papers, String tempDir, String password) {
        // 试卷和答案分开汇总到不同文件夹
        final String paperDir = tempDir + "/paper";
        final String answerDir = tempDir + "/answer";
        String key = null;// 加密密匙
        if (StringUtils.isNotBlank(password)) {
            // 用密码生成密匙
            key = Cryptogram.convertKey(password);
        }
        JsonMapper mapper = JsonMapper.nonNullMapper();
        for (String courseCode : courseCodes) {
            List<PaperDto> paperList = new ArrayList<>();
            List<PaperAnswerDto> answerList = new ArrayList<>();
            for (PaperVo paper : papers) {
                if (!courseCode.equals(paper.getCourseCode())) {
                    continue;
                }
                this.parsePaper(paper, paperList, answerList);
            }
            if (key != null) {
                // 加密试卷和答案
                this.aesPaper(paperList, key);
                this.aesAnswer(answerList, key);
            }
            List<PolicyDto> policyList = this.generatePolicy(paperList, courseCode);
            FileUtil.saveAsFile(paperDir + "/" + courseCode + "/" + Constants.FILE_PAPER, mapper.toJson(paperList));
            FileUtil.saveAsFile(paperDir + "/" + courseCode + "/" + Constants.FILE_POLICY, mapper.toJson(policyList));
            FileUtil.saveAsFile(answerDir + "/" + courseCode + "/" + Constants.FILE_ANSWER, mapper.toJson(answerList));
            // json = CommonUtils.replaceUnicodeStr(json);//todo
        }

        File paperZipFile = new File(tempDir + "/paper.zip");
        File answerZipFile = new File(tempDir + "/answer.zip");
        File paperDirFile = new File(paperDir);
        File answerDirFile = new File(answerDir);
        FileUtil.doZip(paperDirFile, paperZipFile);
        FileUtil.doZip(answerDirFile, answerZipFile);

        short[] paperHeaders = { 1, 1 };// 试卷头信息
        short[] paperAesHeaders = { 1, 2 };// 试卷加密头信息
        short[] answerHeaders = { 2, 1 };// 答案头信息
        short[] answerAesHeaders = { 2, 2 };// 答案加密头信息
        if (StringUtils.isNotBlank(password)) {
            String sign = Cryptogram.md5Key(password);
            FileUtil.appendHeader(paperZipFile, paperAesHeaders, sign);
            FileUtil.appendHeader(answerZipFile, answerAesHeaders, sign);
        } else {
            FileUtil.appendHeader(paperZipFile, paperHeaders, null);
            FileUtil.appendHeader(answerZipFile, answerHeaders, null);
        }

        IoUtils.removeFile(paperZipFile);
        IoUtils.removeFile(answerZipFile);

        // FileUtils.deleteQuietly(paperDirFile);
        // FileUtils.deleteQuietly(answerDirFile);
    }

    /**
     * 解析试卷
     */
    private void parsePaper(PaperVo paper, List<PaperDto> paperList, List<PaperAnswerDto> answerList) {
        // 试卷类型给默认值A-Z
        String paperType = NumUtil.toEnNumber(paperList.size() + 1);
        // 封装试卷完整信息
        PaperDto newPaper = new PaperDto(paper);
        newPaper.setPaperType(paperType);
        List<PaperDetailVo> details = paper.getDetails();
        if (details == null || details.size() == 0) {
            throw new IllegalArgumentException("试卷的大题不能为空！");
        }
        Double paperScore = 0d;// 试卷总分
        PaperAnswerDto newAnswer = new PaperAnswerDto(paper.getId());
        List<PaperDetailDto> newDetails = new ArrayList<>();
        for (PaperDetailVo detail : details) {
            List<QuestionVo> questions = detail.getQuestions();
            if (questions == null || questions.size() == 0) {
                throw new IllegalArgumentException("试卷大题下的试题不能为空！");
            }
            Double detailScore = 0d;// 大题总分
            PaperDetailDto newDetail = new PaperDetailDto(detail);
            for (QuestionVo question : questions) {
                // 封装试题
                QuestionDto newQuestion = new QuestionDto(question);
                // 封装试题答案
                SimpleQuestionDto simpleQuestion = new SimpleQuestionDto(question);
                if (question.hasSubQuestion()) {
                    Double questionScore = 0d;// 套题总分
                    for (QuestionVo subQuestion : question.getSubQuestions()) {
                        // 封装子试题
                        QuestionDto newSubQuestion = new QuestionDto(subQuestion);
                        newQuestion.addSubQuestion(newSubQuestion);
                        // 封装子试题答案
                        SimpleQuestionDto subSimpleQuestion = new SimpleQuestionDto(subQuestion);
                        subSimpleQuestion.setNumber(subQuestion.getNumber());
                        simpleQuestion.addSubQuestion(subSimpleQuestion);
                        questionScore += newSubQuestion.getScore();
                    }
                    newQuestion.setScore(questionScore);
                }
                detailScore += newQuestion.getScore();
                newDetail.addQuestion(newQuestion);
                newAnswer.addQuestion(simpleQuestion);
            }
            newDetail.setTotalScore(detailScore);
            paperScore += detailScore;
            newDetails.add(newDetail);
        }
        newPaper.setTotalScore(paperScore);
        newPaper.setDetails(parseDetails(newDetails));
        paperList.add(newPaper);
        answerList.add(newAnswer);
    }

    /**
     * 解析试卷试题
     */
    private List<PaperDetailDto> parseDetails(List<PaperDetailDto> details) {
        // 聚合同类题，并重新计算每道大题的总分以及题号顺序
        List<PaperDetailDto> allDetails = new ArrayList<>();
        PaperDetailDto singleDetail = new PaperDetailDto(QuesStructType.SINGLE_ANSWER_QUESTION.getName());
        PaperDetailDto multipleDetail = new PaperDetailDto(QuesStructType.MULTIPLE_ANSWER_QUESTION.getName());
        PaperDetailDto boolDetail = new PaperDetailDto(QuesStructType.BOOL_ANSWER_QUESTION.getName());
        PaperDetailDto fillDetail = new PaperDetailDto(QuesStructType.FILL_BLANK_QUESTION.getName());
        PaperDetailDto textDetail = new PaperDetailDto(QuesStructType.TEXT_ANSWER_QUESTION.getName());
        PaperDetailDto nestedDetail = new PaperDetailDto(QuesStructType.NESTED_ANSWER_QUESTION.getName());
        Integer singleNumber = 0, multipleNumber = 0, boolNumber = 0, fillNumber = 0, textNumber = 0, nestedNumber = 0;
        Double singleScore = 0d, multipleScore = 0d, boolScore = 0d, fillScore = 0d, textScore = 0d, nestedScore = 0d;
        for (PaperDetailDto detail : details) {
            for (QuestionDto question : detail.getQuestions()) {
                if (QuesStructType.SINGLE_ANSWER_QUESTION.getId() == question.getStructType()) {
                    question.setNumber(++singleNumber);
                    singleScore += question.getScore();
                    singleDetail.setTotalScore(singleScore);
                    singleDetail.addQuestion(question);
                } else if (QuesStructType.MULTIPLE_ANSWER_QUESTION.getId() == question.getStructType()) {
                    question.setNumber(++multipleNumber);
                    multipleScore += question.getScore();
                    multipleDetail.setTotalScore(multipleScore);
                    multipleDetail.addQuestion(question);
                } else if (QuesStructType.BOOL_ANSWER_QUESTION.getId() == question.getStructType()) {
                    question.setNumber(++boolNumber);
                    boolScore += question.getScore();
                    boolDetail.setTotalScore(boolScore);
                    boolDetail.addQuestion(question);
                } else if (QuesStructType.FILL_BLANK_QUESTION.getId() == question.getStructType()) {
                    question.setNumber(++fillNumber);
                    fillScore += question.getScore();
                    fillDetail.setTotalScore(fillScore);
                    fillDetail.addQuestion(question);
                } else if (QuesStructType.TEXT_ANSWER_QUESTION.getId() == question.getStructType()) {
                    question.setNumber(++textNumber);
                    textScore += question.getScore();
                    textDetail.setTotalScore(textScore);
                    textDetail.addQuestion(question);
                } else if (QuesStructType.NESTED_ANSWER_QUESTION.getId() == question.getStructType()) {
                    question.setNumber(++nestedNumber);
                    nestedScore += question.getScore();
                    nestedDetail.setTotalScore(nestedScore);
                    nestedDetail.addQuestion(question);
                }
            }
        }
        int number = 0;
        if (singleDetail.getQuestions().size() > 0) {
            singleDetail.setNumber(++number);
            allDetails.add(singleDetail);
        }
        if (multipleDetail.getQuestions().size() > 0) {
            multipleDetail.setNumber(++number);
            allDetails.add(multipleDetail);
        }
        if (boolDetail.getQuestions().size() > 0) {
            boolDetail.setNumber(++number);
            allDetails.add(boolDetail);
        }
        if (fillDetail.getQuestions().size() > 0) {
            fillDetail.setNumber(++number);
            allDetails.add(fillDetail);
        }
        if (textDetail.getQuestions().size() > 0) {
            textDetail.setNumber(++number);
            allDetails.add(textDetail);
        }
        if (nestedDetail.getQuestions().size() > 0) {
            nestedDetail.setNumber(++number);
            allDetails.add(nestedDetail);
        }
        return allDetails;
    }

    /**
     * 生成调卷规则
     */
    private List<PolicyDto> generatePolicy(List<PaperDto> papers, String courseCode) {
        List<PolicyDto> policyList = new ArrayList<>();
        Map<String, List<String>> maps = new HashMap<>();
        for (PaperDto paper : papers) {
            // 按试卷类型封装试卷ID
            List<String> ids = maps.get(paper.getPaperType());
            if (ids == null) {
                ids = new ArrayList<>();
            }
            ids.add(paper.getId());
            maps.put(paper.getPaperType(), ids);
        }
        // 生成默认调卷规则
        for (Map.Entry<String, List<String>> entry : maps.entrySet()) {
            PolicyDto policy = new PolicyDto();
            policy.setType(PolicyType.Random.getId());
            policy.setObjectiveShuffle(false);
            policy.setOptionShuffle(false);
            policy.setCourseCode(courseCode);
            policy.setPaperType(entry.getKey());
            List<String> ids = entry.getValue();
            if (ids.size() == 0) {
                continue;
            }
            // 计算每份试卷的随机抽取权重
            double weight = 1d / ids.size();
            for (String id : ids) {
                policy.addWeight(new PolicyWeightDto(id, weight));
            }
            policyList.add(policy);
        }
        return policyList;
    }

    /**
     * 加密试卷
     */
    private void aesPaper(List<PaperDto> papers, String key) {
        log.info("aesPaper..." + key);
        for (PaperDto paper : papers) {
            List<PaperDetailDto> details = paper.getDetails();
            for (PaperDetailDto detail : details) {
                List<QuestionDto> questions = detail.getQuestions();
                for (QuestionDto question : questions) {
                    List<QuestionOptionDto> options = question.getOptions();
                    if (options != null) {
                        for (QuestionOptionDto option : options) {
                            String newBody = Cryptogram.aesEncrypt(option.getBody(), key);// 加密选项内容
                            option.setBody(newBody);
                        }
                    }
                    List<QuestionDto> subQuestions = question.getSubQuestions();
                    if (subQuestions != null) {
                        for (QuestionDto subQuestion : subQuestions) {
                            List<QuestionOptionDto> subOptions = subQuestion.getOptions();
                            if (subOptions != null) {
                                for (QuestionOptionDto option : subOptions) {
                                    String newBody = Cryptogram.aesEncrypt(option.getBody(), key);// 加密(子题)选项内容
                                    option.setBody(newBody);
                                }
                            }
                            String newBody = Cryptogram.aesEncrypt(subQuestion.getBody(), key);// 加密(子题)题干内容
                            subQuestion.setBody(newBody);
                        }
                    }
                    String newBody = Cryptogram.aesEncrypt(question.getBody(), key);// 加密题干内容
                    question.setBody(newBody);
                }
            }
        }
    }

    /**
     * 加密试卷答案
     */
    private void aesAnswer(List<PaperAnswerDto> papers, String key) {
        log.info("aesAnswer..." + key);
        for (PaperAnswerDto paper : papers) {
            List<SimpleQuestionDto> questions = paper.getQuestions();
            for (SimpleQuestionDto question : questions) {
                List<SimpleQuestionDto> subQuestions = question.getSubQuestions();
                if (subQuestions != null) {
                    for (SimpleQuestionDto subQuestion : subQuestions) {
                        String newAnswer = Cryptogram.aesEncrypt(subQuestion.getAnswer(), key);// 加密答案内容
                        subQuestion.setAnswer(newAnswer);
                    }
                }
                String newAnswer = Cryptogram.aesEncrypt(question.getAnswer(), key);// 加密答案内容
                question.setAnswer(newAnswer);
            }
        }
    }

    /**
     * 解压并提取JSON文件
     */
    @SuppressWarnings("unused")
    private List<File> parseDataFiles(final String dirPath, File zipFile) throws Exception {
        List<File> dataFileList = new ArrayList<>();
        // 解压当前文件
        List<File> fileList = FileUtil.unZip(new File(dirPath), zipFile);
        if (fileList == null || fileList.size() == 0) {
            return dataFileList;
        }
        // 解压当前文件内的其它ZIP文件，并提取JSON文件
        for (File curFile : fileList) {
            if (curFile.getName().toLowerCase().endsWith(Constants.ZIP)) {
                final String curDir = FileUtil.getFilePathName(curFile.getAbsolutePath());
                List<File> files = FileUtil.unZip(new File(curDir), curFile);
                if (files == null || files.size() == 0) {
                    continue;
                }
                for (File file : files) {
                    if (file.getName().toLowerCase().endsWith(Constants.JSON)) {
                        dataFileList.add(file);
                    }
                }
            } else if (curFile.getName().toLowerCase().endsWith(Constants.JSON)) {
                dataFileList.add(curFile);
            }
        }
        return dataFileList;
    }

}