const { ScriptBase } = require('./base'),
  Fsx = require('fs-extra'),
  _ = require('lodash'),
  Moment = require('moment'),
  Path = require('path'),
  Os = require('os'),
  Package = require('../package.json'),
  PngQuality = require('png-quality'),
  PngStrip = require('png-strip-chunks'),
  PngTextMeta = require('png-text-meta');

const qualityPsnrs = {
  veryhigh: 48,
  high: 45,
  medium: 42,
  low: 39
};

const lightFileMetaGap = 200;

class PngScript extends ScriptBase {
  constructor(options = {}) {
    _.defaultsDeep(options, {
      quality: 'medium',
      strip: true,
    });

    super(options);
  }

  static async install(installDirPath) {
    await this.installCommandFiles(installDirPath, [
      'pngquant',
    ]);
  }

  async readCommentLightFileMeta(path) {
    try {
      const buffer = await Fsx.readFile(path);
      const textMetaExists = await PngTextMeta.find(buffer, 'lightfile');
      const json = JSON.parse(textMetaExists);
      return json;
    } catch(ex) {}
  }

  async runScript(srcPath) {
    this.phase('start');
    const stats = {},
      metrics = {
        pngquant: false,
        pngquantPsnr: null,
        pngquantReduced: 0,
        strip: false,
        stripReduced: 0,
        lastPsnr: null,
        path: srcPath,
        reduced: false,
        reducedSize: 0,
        startedAt: Moment().format(),
      };
    const psnrLimit = qualityPsnrs[this.options.quality] || qualityPsnrs.medium;

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

    // tEXtチャンクからlightfileを探してJSONとして解析する
    const textMetaExists = await PngTextMeta.find(srcBuffer, 'lightfile');
    if (textMetaExists) {
      metrics.alreadyDone = true;
      metrics.tool = 'LightFile';
      try {
        const json = JSON.parse(textMetaExists);
      } catch(ex) {}
    }
    
    // 一時ファイルの確保
    let tmp;
    if (Os.platform() == 'win32') {
      // Windowsの場合tmpファイルがpngquantの書き込みでエラーになるのディレクトリを用意する
      const dir = this.newTmpDir();
      tmp = { name: Path.join(dir.name, 'tmp.png') };
    } else {
      // Windows以外ではtmpFile
      tmp = this.newTmpFile({ postfix: '.png' });
    }

    // pngquantコマンドの実行
    this.phase('pngquant');
    await this.executeCommand('pngquant', ['--verbose', '--speed', "3", '--force', '--output', tmp.name, srcPath]);

    // PSNRの比較
    this.phase('pngquantPsnr');
    const pngquantBuffer = await Fsx.readFile(tmp.name);
    metrics.pngquantPsnr = await PngQuality.psnr(srcBuffer, pngquantBuffer);

    // PSNRが基準を下回ったら不採用。srcPathを使う。
    let strippingBuffer;
    if (metrics.pngquantPsnr < psnrLimit) {
      strippingBuffer = srcBuffer;
      metrics.pngquant = false;
    } else {
      strippingBuffer = pngquantBuffer;
      metrics.pngquant = true;
      metrics.pngquantReduced = (pngquantBuffer.length - srcBuffer.length);
    }
    
    // メタデータの削除
    const strippedBuffer = await PngStrip.strip(strippingBuffer, [PngStrip.types.core, PngStrip.types.transparency, PngStrip.types.colorSpace]);
    if (strippedBuffer.length < strippingBuffer.length) {
      metrics.strip = true;
      metrics.stripReduced = strippedBuffer.length - strippingBuffer.length;
    }

    // 削減後のサイズと比較
    stats.after = { size: strippedBuffer.length };

    // LightFileコメントを追加してもファイルが削減される見込みがある場合は採用
    if (stats.after.size + lightFileMetaGap < stats.before.size) {
      metrics.reduced = true;
      metrics.reducedSize = stats.after.size - stats.before.size;

      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;
      comment.pngquant = metrics.pngquant;
      comment.strip = metrics.strip;

      // PNGへのコメントの書き込み
      this.phase('comment');
      const commentedBuffer = await PngTextMeta.update(strippedBuffer, 'lightfile', JSON.stringify(comment));

      // PSNRの最終チェック
      this.phase('lastPsnr');
      metrics.lastPsnr = await PngQuality.psnr(srcBuffer, commentedBuffer);
      if (metrics.lastPsnr < psnrLimit) {
        throw new Error(`処理後のPSNR値 ${lastPsnr} が閾値 ${psnrLimit} を下回ったため削減対象から除外します。`);
      }

      // srcPathに書き戻し
      await Fsx.writeFile(srcPath, commentedBuffer);
    } else {
      // 軽量化できなかったのでunavailable=trueを返す
      metrics.unavailable = true;
    }

    return metrics;
  }

  static runnableFor(path) {
    return path.match(/\.png$/i);
  }
}

PngScript.qualityPsnrs = qualityPsnrs;

module.exports = {
  PngScript: PngScript,
};