//云端图片操作工具
import EventEmitter from "events";
import api from "./api";
import env from "./env";
import config from "./config";
import fs from "fs";
import path from "path";
import readline from "readline";
import request_util from "requestretry";
import sizeOf from "image-size";
import mustache from "mustache";
import mkdirp from "mkdirp";

import _logger from "./logger";
const logger = _logger("image.js");
const downloadLogger = _logger("download");

const gm =
  config.imagemagick != undefined
    ? require("gm").subClass({
        imageMagick: true,
        appPath: config.imagemagick,
      })
    : require("gm");

class executor extends EventEmitter {
  async readFile(file) {
    return new Promise((resolve) => {
      const data = [];
      if (fs.existsSync(file)) {
        const reader = readline.createInterface({
          input: fs.createReadStream(file),
        });
        reader.on("line", (line) => {
          data.push(line);
        });
        reader.on("close", () => {
          resolve(data);
        });
      } else {
        resolve(data);
      }
    });
  }

  async addWatermark(image, file, student, index, trackMode) {
    const fontFile = config.watermark.fontFile;
    const color = config.watermark.color;
    const size = sizeOf(image);
    const imgData = gm(image);
    //添加第一页的得分明细
    if (index == 1) {
      //初始坐标
      let x = 30;
      let y = 10;
      //最大宽/高限制
      const fontSize = config.watermark.fontSize || 30;
      const maxX = size.width / 2 - x * 2;
      const height = fontSize + 10;
      //计算总分
      const totalScore =
        (parseFloat(student.objectiveScore) || 0) +
        (parseFloat(student.subjectiveScore) || 0);
      //显示总分明细
      imgData.font(fontFile, fontSize).fill(color);
      imgData.drawText(x, (y += height), "成绩明细");
      //普通考试模式，按客观+主观模式显示总分
      if (trackMode === "1") {
        imgData.drawText(
          x,
          (y += height),
          "总分=(客观+主观) | " +
            totalScore +
            "=" +
            student.objectiveScore +
            "+" +
            student.subjectiveScore
        );
      }
      //研究生考试模式，只显示总分
      else if (trackMode === "2") {
        imgData.drawText(x, (y += height), "总分=" + totalScore + "分");
      }
      //显示客观题明细
      if (
        student.objectiveScoreDetail &&
        student.objectiveScoreDetail.length > 0
      ) {
        const lines = [];
        let array = [];
        //前置提示文字的字符数
        let count = 10;
        lines.push(array);
        for (let i = 0; i < student.objectiveScoreDetail.length; i++) {
          const detail = student.objectiveScoreDetail[i];
          const content = detail.answer + ":" + detail.score;
          //超长后另起一行显示客观题
          if ((count + content.length) * fontSize * 0.7 > maxX) {
            array = [];
            lines.push(array);
            count = 10;
          }
          array.push(content);
          count += content.length;
        }
        //显示所有行的客观题明细
        for (let l = 0; l < lines.length; l++) {
          imgData.drawText(
            x,
            (y += height),
            "客观题识别结果 | " + lines[l].join(";")
          );
        }
      }
      //显示复核人
      if (student.inspector) {
        imgData.drawText(
          x,
          (y += height),
          "复核人： " + student.inspector.loginName
        );
      }
      //显示主观题明细
      if (
        student.subjectiveScoreDetail &&
        student.subjectiveScoreDetail.length > 0
      ) {
        //普通考试模式，按小题显示明细
        if (trackMode === "1") {
          const title = "主观题号 | 分数 | 评卷员 | 仲裁员";
          const startY = y;
          let width = title.length * fontSize;
          imgData.drawText(x, (y += height), title);
          for (let i = 0; i < student.subjectiveScoreDetail.length; i++) {
            const detail = student.subjectiveScoreDetail[i];
            //超过最大高度了则另起一列
            if (y + height + 15 > size.height) {
              y = startY;
              x += width;
              imgData.drawText(x, (y += height), title);
            }
            const content =
              detail.mainNumber +
              "-" +
              detail.subNumber +
              " : " +
              detail.score +
              " " +
              (detail.marker || "") +
              " " +
              (detail.header || "");
            width = Math.max(width, content.length * fontSize);
            imgData.drawText(x, (y += height), content);
          }
        }
        //研究生考试模式，按分组显示明细
        else if (trackMode === "2") {
          const title = "评卷分组 | 总分 | 评卷员 | 仲裁员";
          const startY = y;
          let width = title.length * fontSize;
          imgData.drawText(x, (y += height), title);
          //所有小题得分按评卷分组聚合
          let maxGroupNumber = 0;
          const groups = {};
          for (let i = 0; i < student.subjectiveScoreDetail.length; i++) {
            const detail = student.subjectiveScoreDetail[i];
            let group = groups[detail.groupNumber];
            if (group == undefined) {
              group = {
                number: detail.groupNumber,
                score: 0,
                title: {},
                titleString: [],
                marker: {},
                markerString: [],
                header: {},
                headerString: [],
              };
              groups[detail.groupNumber] = group;
              maxGroupNumber = Math.max(maxGroupNumber, group.number);
            }
            group.score = group.score + detail.score;
            if (detail.mainTitle && !group.title[detail.mainTitle]) {
              group.titleString.push(detail.mainTitle);
              group.title[detail.mainTitle] = true;
            }
            if (detail.marker && !group.marker[detail.marker]) {
              group.markerString.push(detail.marker);
              group.marker[detail.marker] = true;
            }
            if (detail.header && !group.header[detail.header]) {
              group.headerString.push(detail.header);
              group.header[detail.header] = true;
            }
          }
          for (let i = 1; i <= maxGroupNumber; i++) {
            const group = groups[i];
            if (group != undefined) {
              //超过最大高度了则另起一列
              if (y + height + 15 > size.height) {
                y = startY;
                x += width;
                imgData.drawText(x, (y += height), title);
              }
              const content =
                group.number +
                "(" +
                group.titleString.join(",") +
                ")" +
                " " +
                group.score +
                " " +
                group.markerString.join(",") +
                " " +
                group.headerString.join(",");
              width = Math.max(width, content.length * fontSize);
              imgData.drawText(x, (y += height), content);
            }
          }
        }
      }
    }
    //显示评卷标记
    if (student.tags != undefined && student.tags[index] != undefined) {
      const fontSize = 60;
      const height = fontSize + 10;
      imgData.font(fontFile, fontSize).fill(color);
      const tags = student.tags[index];
      for (let i = 0; i < tags.length; i++) {
        const tag = tags[i];
        if (tag.content != undefined) {
          let top = tag.top;
          for (let j = 0; j < tag.content.length; j++) {
            imgData.drawText(tag.left, top, tag.content[j]);
            top += height;
          }
        }
      }
    }

    return new Promise((resolve, reject) => {
      imgData.write(file, (error) => {
        if (error) {
          logger.error("add watermark error: " + file);
          logger.error(error);
          reject(error);
        } else {
          resolve();
        }
      });
    });
  }

  async downloadUrl(url) {
    return new Promise((resolve, reject) => {
      request_util(
        {
          url: url,
          method: "GET",
          encoding: null,
          timeout: 3000,
          maxAttempts: 3,
          retryDelay: 500,
          retryStrategy: request_util.RetryStrategies.HTTPOrNetworkError,
        },
        function (error, response, body) {
          if (!error && response.statusCode == 200) {
            resolve(body);
          } else {
            logger.error(error || url + " download error");
            error = error || {};
            error.code = response ? response.statusCode : 500;
            reject(error);
          }
        }
      );
    });
  }

  async downloadFile(
    type,
    append,
    url,
    localTemplate,
    data,
    dir,
    index,
    watermark,
    trackMode
  ) {
    data.index = index;
    const local = path.join(dir, mustache.render(localTemplate, data));
    mkdirp.sync(path.dirname(local));

    //续传模式下，判断目标文件是否存在，存在则直接跳过
    if (append && fs.existsSync(local)) {
      return Promise.resolve();
    } else {
      let imgData;
      try {
        imgData = await this.downloadUrl(url);
      } catch (err) {
        if (err.code === 404) {
          //文件不存在，记录日志并跳过
          downloadLogger.error("404 " + type + " " + url);
          return Promise.resolve();
        } else {
          logger.error(err);
          return Promise.reject(err);
        }
      }

      //是否需要添加分数水印
      if (watermark) {
        return this.addWatermark(imgData, local, data, index, trackMode);
      } else {
        return new Promise((resolve, reject) => {
          fs.writeFile(local, imgData, (err) => {
            if (err) {
              logger.error("write image file error: " + local);
              logger.error(err);
              reject(err);
            } else {
              resolve();
            }
          });
        });
      }
    }
  }

  async downloadSheet(
    dir,
    template,
    append,
    failover,
    watermark,
    trackMode,
    params
  ) {
    params.upload = true;
    params.withSheetUrl = true;
    params.withScoreDetail = watermark === true;
    params.withMarkTrack = watermark === true;
    params.withGroupScoreTrack = watermark === true && trackMode === "1";

    try {
      const totalCount = await api.countStudents(env.examId, params);
      this.emit("total", totalCount);

      let count = 0;
      let pageNumber = 0;
      this.emit("count", 0);
      for (;;) {
        pageNumber++;
        const array = await api.getStudents(env.examId, pageNumber, 10, params);
        if (array == undefined || array.length == 0) {
          break;
        }
        for (let i = 0; i < array.length; i++) {
          const promises = [];
          const student = array[i];
          student.examId = env.examId;
          for (let i = 0; i < student.sheetUrls.length; i++) {
            promises.push(
              this.downloadFile(
                "sheet",
                append,
                student.sheetUrls[i],
                template,
                student,
                dir,
                i + 1,
                watermark,
                trackMode
              )
            );
          }
          try {
            //等待所有图片下载完毕
            await Promise.all(promises);
            count++;
            this.emit("count", count);
          } catch (err) {
            //判断是否异常终止
            if (failover) {
              throw err;
            } else {
              logger.error("download sheet error:" + err);
              logger.error(err);
              continue;
            }
          }
        }
      }
      this.emit("finish");
    } catch (error) {
      logger.error("download sheet error:" + error);
      logger.error(error);
      this.emit("error", error);
    }
  }

  async downloadPackage(dir, template, append, failover) {
    try {
      const array = await api.getPackages(env.examId, true, true);
      this.emit("total", array.length);
      let count = 0;
      this.emit("count", 0);
      for (let i = 0; i < array.length; i++) {
        const p = array[i];
        p.examId = env.examId;
        for (let i = 0; i < p.urls.length; i++) {
          try {
            await this.downloadFile(
              "package",
              append,
              p.urls[i],
              template,
              p,
              dir,
              i + 1
            );
          } catch (err) {
            //判断是否异常终止
            if (failover) {
              throw err;
            } else {
              logger.error("download package error: " + err);
              logger.error(err);
              continue;
            }
          }
        }
        count++;
        this.emit("count", count);
      }
      this.emit("finish");
    } catch (error) {
      logger.error("download package error: " + error);
      logger.error(error);
      this.emit("error", error);
    }
  }
}

export default function () {
  return new executor();
}
