const { ScriptBase } = require('./base'),
  Fsx = require('fs-extra'),
  _ = require('lodash'),
  Moment = require('moment'),
  Tmp = require('tmp'),
  Package = require('../package.json');

class JpegScript extends ScriptBase {
  constructor(options = {}) {
    _.defaultsDeep(options, {
      quality: 'medium',
      method: 'ssim',
      passing: 0.95,
      loops: 6,
    });

    super(options);
  }

  static async install(installDirPath) {
    await this.installCommandFiles(installDirPath, [
      'jpeg-recompress',
      'jpeg-compare',
      'jhead',
    ]);
  }

  async readCommentLightFileMeta(path, defaults = null) {
    const tmp = Tmp.fileSync();
    try {
      await this.executeCommand('jhead', ['-q', '-cs', tmp.name, path]);
      const comment = (await Fsx.readFile(tmp.name)).toString();

      // 旧バージョンの画像コメントに対する例外処置
      if (comment.match(/Compressed by shrink/i)) {
        return { by: 'Other', tool: 'ShrinkPics' };
      } else if (comment.match(/Compressed by jpeg-recompress/i)) {
        return { by: 'Other', tool: 'jpeg-recompress' };
      } else if (comment.match(/Optimized by JPEGmini/i)) {
        return { by: 'Other', tool: 'JPEGMini' };
      }

      return JSON.parse(comment);
    } catch (ex) {
      return defaults;
    } finally {
      tmp.removeCallback();
    }
  }

  async runScript(srcPath) {
    this.phase('start');
    const stats = {},
      metrics = {
        ssim: 1.0,
        path: srcPath,
        startedAt: Moment().format(),
      };

    // 処理前のファイルサイズ
    stats.before = await Fsx.stat(srcPath);

    // ヘッダの確認
    this.phase('jheadRead');
    const currentCommentJson = await this.readCommentLightFileMeta(srcPath);
    if (currentCommentJson && (currentCommentJson.by == 'LightFile' || currentCommentJson.by == 'Other')) {
      metrics.alreadyDone = true;
      metrics.by = currentCommentJson.by;
      if (currentCommentJson.tool) metrics.tool = currentCommentJson.tool;
      return metrics;
    }

    // 一時ファイルの確保
    this.phase('tmp');
    const tmp = this.newTmpFile({ postfix: '.jpg' });

    // jpeg-recompressコマンドの実行
    this.phase('jpegRecompress');
    const quality = this.options.quality == 'high' ? 'veryhigh' : this.options.quality;
    const opts = ['-Q', '-m', this.options.method, '-l', this.options.loops, '-q', quality];
    metrics.recompressOpts = opts.join(' ');
    await this.executeCommand('jpeg-recompress', [...opts, srcPath, tmp.name]);

    // jheadコマンドによるサムネイル除去の実行
    this.phase('jheadThumbnail');
    await this.executeCommand('jhead', ['-q', '-dt', tmp.name]);

    // 処理後のファイルサイズ
    stats.after = await Fsx.stat(tmp.name);

    // 書き戻し
    if (stats.after.size < stats.before.size) {
      // コメント記入
      this.phase('jheadMark');
      const comment = { before: stats.before.size, after: stats.after.size, reduced: stats.after.size - stats.before.size, quality: this.options.quality };
      if (stats.before.size > 0) comment.rate = comment.reduced * 1.0 / stats.before.size;
      comment.by = 'LightFile';
      comment.core = Package.version;

      await this.executeCommand('jhead', ['-q', '-cl', JSON.stringify(comment), tmp.name]);

      // ファイルサイズ最終チェック
      stats.after = await Fsx.stat(tmp.name);
      if (stats.after.size < stats.before.size) {
        // SSIM最終チェック
        this.phase('jpegCompare');
        const jpegCompareResult = await this.executeCommand('jpeg-compare', ['-m', this.options.method, srcPath, tmp.name]);

        const [line] = jpegCompareResult.stdout.toString().split(/\r?\n/, 2);
        const [, value] = line.split(/\s/);
        metrics.ssim = parseFloat(value);

        // 元画像とのSSIMがpassing(デフォルト0.95)以上であれば不慮の破損はないと判断する
        if (metrics.ssim > this.options.passing) {
          await Fsx.copyFile(tmp.name, srcPath);
        } else {
          throw new Error(`処理後のSSIM値 ${metrics.ssim} が閾値 ${this.options.passing} を下回ったため削減対象から除外します。`);
        }
      } else {
        // サイズが元を上回ってしまったので対象から外す
        metrics.unavailable = true;
      }
    } else {
      // サイズが元を上回ってしまったので対象から外す
      metrics.unavailable = true;
    }

    return metrics;
  }

  static runnableFor(path) {
    return path.match(/\.jpe?g$/i);
  }
}

module.exports = {
  JpegScript: JpegScript,
};