const Fs = require('fs');
const Path = require('path');
const Os = require('os');
const { exec } = require('child_process');
const ConfigManager = require('./config-manager');
const FileUtils = require('./utils/file-utils');

/** 包名 */
const PACKAGE_NAME = require('./package.json').name;

/**
 * i18n
 * @param {string} key
 * @returns {string}
 */
const translate = (key) => Editor.T(`${PACKAGE_NAME}.${key}`);

/** 扩展名 */
const EXTENSION_NAME = translate('name');

/** 内置资源目录 */
const internalPath = Path.normalize('assets/internal/');

module.exports = {

  /**
   * 项目路径
   * @type {string}
   */
  projectPath: null,

  /**
   * 资源根目录路径
   * @type {string}
   */
  assetsPath: null,

  /**
   * 压缩引擎路径
   * @type {string}
   */
  pngquantPath: null,

  /**
   * 日志
   * @type {{ successCount: number, failedCount: number, successInfo: string, failedInfo: string }}
   */
  logger: null,

  /**
   * 需要排除的文件夹
   * @type {string[]}
   */
  excludeFolders: null,

  /**
   * 需要排除的文件
   * @type {string[]}
   */
  excludeFiles: null,

  /**
   * 扩展消息
   * @type {{ [key: string]: Function }}
   */
  messages: {

    /**
     * 打开设置面板
     */
    'open-setting-panel'() {
      Editor.Panel.open(`${PACKAGE_NAME}.setting`);
    },

    /**
     * 读取配置
     * @param {any} event 
     */
    'read-config'(event) {
      const config = ConfigManager.get();
      event.reply(null, config);
    },

    /**
     * 保存配置
     * @param {any} event 
     * @param {any} config 
     */
    'save-config'(event, config) {
      const configFilePath = ConfigManager.set(config);
      Editor.log(`[${EXTENSION_NAME}]`, translate('configSaved'), configFilePath);
      event.reply(null, true);
    },

  },

  /**
   * 生命周期：加载
   */
  load() {
    // 绑定 this
    this.onBuildStart = this.onBuildStart.bind(this);
    this.onBuildFinished = this.onBuildFinished.bind(this);
    // 监听事件
    Editor.Builder.on('build-start', this.onBuildStart);
    Editor.Builder.on('build-finished', this.onBuildFinished);
  },

  /**
   * 生命周期：加载
   */
  unload() {
    // 取消事件监听
    Editor.Builder.removeListener('build-start', this.onBuildStart);
    Editor.Builder.removeListener('build-finished', this.onBuildFinished);
  },

  /**
  * 构建开始回调
  * @param {BuildOptions} options 
  * @param {Function} callback 
  */
  onBuildStart(options, callback) {
    const config = ConfigManager.get();
    if (config && config.enabled) {
      Editor.log(`[${EXTENSION_NAME}]`, translate('willCompress'));
      // 取消编辑器资源选中（解除占用）
      Editor.Selection.clear('asset');
    }
    // Done
    callback();
  },

  /**
   * 构建完成回调
   * @param {BuildOptions} options 
   * @param {Function} callback 
   */
  async onBuildFinished(options, callback) {
    const config = ConfigManager.get();

    // 未开启直接跳过
    if (!config || !config.enabled) {
      callback();
      return;
    }

    // 获取项目路径
    this.projectPath = Editor.Project.path || Editor.projectPath;
    this.assetsPath = Path.join(this.projectPath, 'assets');

    // 获取压缩引擎路径
    const platform = Os.platform(),
      pngquantPath = this.pngquantPath = Path.join(__dirname, enginePathMap[platform]);
    // 设置引擎文件的执行权限（仅 macOS）
    if (pngquantPath && platform === 'darwin') {
      if (Fs.statSync(pngquantPath).mode != 33261) {
        // 默认为 33188
        Fs.chmodSync(pngquantPath, 33261);
      }
    }

    // 压缩引擎路径
    if (!pngquantPath) {
      Editor.log(`[${EXTENSION_NAME}]`, translate('notSupport'), platform);
      callback();
      return;
    }

    // 准备
    Editor.log(`[${EXTENSION_NAME}]`, translate('prepareCompress'));

    // 组装压缩命令
    const qualityParam = `--quality ${config.minQuality}-${config.maxQuality}`,
      speedParam = `--speed ${config.speed}`,
      skipParam = '--skip-if-larger',
      outputParam = '--ext=.png',
      writeParam = '--force',
      // colorsParam = config.colors,
      // compressOptions = `${qualityParam} ${speedParam} ${skipParam} ${outputParam} ${writeParam} ${colorsParam}`;
      compressOptions = `${qualityParam} ${speedParam} ${skipParam} ${outputParam} ${writeParam}`;

    // 需要排除的文件夹
    this.excludeFolders = config.excludeFolders ? config.excludeFolders.map(value => Path.normalize(value)) : [];
    // 需要排除的文件
    this.excludeFiles = config.excludeFiles ? config.excludeFiles.map(value => Path.normalize(value)) : [];

    // 重置日志
    this.logger = {
      successCount: 0,
      failedCount: 0,
      successInfo: '',
      failedInfo: ''
    };

    // 开始压缩
    Editor.log(`[${EXTENSION_NAME}]`, translate('startCompress'));

    // 遍历项目资源
    const dest = options.dest,
      dirs = ['res', 'assets', 'subpackages', 'remote'];
    for (let i = 0; i < dirs.length; i++) {
      const dirPath = Path.join(dest, dirs[i]);
      if (!Fs.existsSync(dirPath)) {
        continue;
      }
      Editor.log(`[${EXTENSION_NAME}]`, translate('compressDir'), dirPath);
      // 压缩并记录结果
      await this.compress(dirPath, compressOptions);
    }

    // 打印压缩结果
    this.printResults();

    // Done
    callback();
  },

  /**
   * 压缩
   * @param {string} srcPath 文件路径
   * @param {object} options 压缩参数
   */
  async compress(srcPath, options) {
    const pngquantPath = this.pngquantPath,
      tasks = [];
    const handler = (filePath, stats) => {
      // 过滤文件
      if (!this.filter(filePath)) {
        return;
      }
      // 加入压缩队列
      tasks.push(new Promise(res => {
        const sizeBefore = stats.size / 1024,
          command = `"${pngquantPath}" ${options} -- "${filePath}"`;
        // pngquant $OPTIONS -- "$FILE"
        exec(command, (error, stdout, stderr) => {
          this.recordResult(error, sizeBefore, filePath);
          res();
        });
      }));
    };
    FileUtils.map(srcPath, handler);
    await Promise.all(tasks);
  },

  /**
   * 判断资源是否可以进行压缩
   * @param {string} path 路径
   */
  filter(path) {
    // 排除非 png 资源和内置资源
    if (!path.endsWith('.png') || path.includes(internalPath)) {
      return false;
    }
    // 排除指定文件夹和文件
    const assetPath = this.getAssetPath(path);
    if (assetPath) {
      const excludeFolders = this.excludeFolders,
        excludeFiles = this.excludeFiles;
      // 文件夹
      for (let i = 0, l = excludeFolders.length; i < l; i++) {
        if (assetPath.startsWith(excludeFolders[i])) {
          return false;
        }
      }
      // 文件
      for (let i = 0, l = excludeFiles.length; i < l; i++) {
        if (assetPath.startsWith(excludeFiles[i])) {
          return false;
        }
      }
    }
    // 测试通过
    return true;
  },

  /**
   * 获取资源源路径
   * @param {string} filePath 
   * @return {string} 
   */
  getAssetPath(filePath) {
    // 获取源路径（图像在项目中的实际路径）
    const basename = Path.basename(filePath),
      uuid = basename.slice(0, basename.indexOf('.')),
      sourcePath = Editor.assetdb.uuidToFspath(uuid);
    if (!sourcePath) {
      // 图集资源
      // 暂时还没有找到办法处理
      return null;
    }
    return Path.relative(this.assetsPath, sourcePath);
  },

  /**
   * 记录结果
   * @param {object} error 错误
   * @param {number} sizeBefore 压缩前尺寸
   * @param {string} filePath 文件路径
   */
  recordResult(error, sizeBefore, filePath) {
    const log = this.logger;
    if (!error) {
      // 成功
      const fileName = Path.basename(filePath),
        sizeAfter = Fs.statSync(filePath).size / 1024,
        savedSize = sizeBefore - sizeAfter,
        savedRatio = savedSize / sizeBefore * 100;
      log.successCount++;
      log.successInfo += `\n + ${'Successful'.padEnd(13, ' ')} | ${fileName.padEnd(50, ' ')} | ${(sizeBefore.toFixed(2) + ' KB').padEnd(13, ' ')} ->   ${(sizeAfter.toFixed(2) + ' KB').padEnd(13, ' ')} | ${(savedSize.toFixed(2) + ' KB').padEnd(13, ' ')} | ${(savedRatio.toFixed(2) + '%').padEnd(20, ' ')}`;
    } else {
      // 失败
      log.failedCount++;
      log.failedInfo += `\n - ${'Failed'.padEnd(13, ' ')} | ${filePath.replace(this.projectPath, '')}`;
      switch (error.code) {
        case 98:
          log.failedInfo += `\n ${' '.repeat(10)} - 失败原因：压缩后体积增大（已经不能再压缩了）`;
          break;
        case 99:
          log.failedInfo += `\n ${' '.repeat(10)} - 失败原因：压缩后质量低于已配置最低质量`;
          break;
        case 127:
          log.failedInfo += `\n ${' '.repeat(10)} - 失败原因：压缩引擎没有执行权限`;
          break;
        default:
          log.failedInfo += `\n ${' '.repeat(10)} - 失败原因：code ${error.code}`;
          break;
      }
    }
  },

  /**
   * 打印结果
   */
  printResults() {
    const log = this.logger,
      header = `\n # ${'Result'.padEnd(13, ' ')} | ${'Name / Path'.padEnd(50, ' ')} | ${'Size Before'.padEnd(13, ' ')} ->   ${'Size After'.padEnd(13, ' ')} | ${'Saved Size'.padEnd(13, ' ')} | ${'Compressibility'.padEnd(20, ' ')}`;
    Editor.log('[PAC]', `压缩完成（${log.successCount} 张成功 | ${log.failedCount} 张失败）`);
    Editor.log('[PAC]', '压缩日志 >>>' + header + log.successInfo + log.failedInfo);
  },

}

/** 压缩引擎路径表 */
const enginePathMap = {
  /** macOS */
  'darwin': 'pngquant/macos/pngquant',
  /** Windows */
  'win32': 'pngquant/windows/pngquant'
}
