/*!
  Copyright 2013 Lovell Fuller and others.
  SPDX-License-Identifier: Apache-2.0
*/

const fs = require('node:fs');
const { describe, it } = require('node:test');
const assert = require('node:assert');

const sharp = require('../../');
const fixtures = require('../fixtures');

describe('PNG', () => {
  it('compression level is valid', () => {
    assert.doesNotThrow(() => {
      sharp().png({ compressionLevel: 0 });
    });
  });

  it('compression level is invalid', () => {
    assert.throws(() => {
      sharp().png({ compressionLevel: -1 });
    });
  });

  it('default compressionLevel generates smaller file than compressionLevel=0', (_t, done) => {
    // First generate with default compressionLevel
    sharp(fixtures.inputPng)
      .resize(320, 240)
      .png()
      .toBuffer((err, defaultData, defaultInfo) => {
        if (err) throw err;
        assert.strictEqual(true, defaultData.length > 0);
        assert.strictEqual('png', defaultInfo.format);
        // Then generate with compressionLevel=6
        sharp(fixtures.inputPng)
          .resize(320, 240)
          .png({ compressionLevel: 0 })
          .toBuffer((err, largerData, largerInfo) => {
            if (err) throw err;
            assert.strictEqual(true, largerData.length > 0);
            assert.strictEqual('png', largerInfo.format);
            assert.strictEqual(true, defaultData.length < largerData.length);
            done();
          });
      });
  });

  it('without adaptiveFiltering generates smaller file', (_t, done) => {
    // First generate with adaptive filtering
    sharp(fixtures.inputPng)
      .resize(320, 240)
      .png({ adaptiveFiltering: true })
      .toBuffer((err, adaptiveData, adaptiveInfo) => {
        if (err) throw err;
        assert.strictEqual(true, adaptiveData.length > 0);
        assert.strictEqual(adaptiveData.length, adaptiveInfo.size);
        assert.strictEqual('png', adaptiveInfo.format);
        assert.strictEqual(320, adaptiveInfo.width);
        assert.strictEqual(240, adaptiveInfo.height);
        // Then generate without
        sharp(fixtures.inputPng)
          .resize(320, 240)
          .png({ adaptiveFiltering: false })
          .toBuffer((err, withoutAdaptiveData, withoutAdaptiveInfo) => {
            if (err) throw err;
            assert.strictEqual(true, withoutAdaptiveData.length > 0);
            assert.strictEqual(withoutAdaptiveData.length, withoutAdaptiveInfo.size);
            assert.strictEqual('png', withoutAdaptiveInfo.format);
            assert.strictEqual(320, withoutAdaptiveInfo.width);
            assert.strictEqual(240, withoutAdaptiveInfo.height);
            assert.strictEqual(true, withoutAdaptiveData.length < adaptiveData.length);
            done();
          });
      });
  });

  it('Invalid PNG adaptiveFiltering value throws error', () => {
    assert.throws(() => {
      sharp().png({ adaptiveFiltering: 1 });
    });
  });

  it('Progressive PNG image', (_t, done) => {
    sharp(fixtures.inputJpg)
      .resize(320, 240)
      .png({ progressive: false })
      .toBuffer((err, nonProgressiveData, nonProgressiveInfo) => {
        if (err) throw err;
        assert.strictEqual(true, nonProgressiveData.length > 0);
        assert.strictEqual(nonProgressiveData.length, nonProgressiveInfo.size);
        assert.strictEqual('png', nonProgressiveInfo.format);
        assert.strictEqual(320, nonProgressiveInfo.width);
        assert.strictEqual(240, nonProgressiveInfo.height);
        sharp(nonProgressiveData)
          .png({ progressive: true })
          .toBuffer((err, progressiveData, progressiveInfo) => {
            if (err) throw err;
            assert.strictEqual(true, progressiveData.length > 0);
            assert.strictEqual(progressiveData.length, progressiveInfo.size);
            assert.strictEqual(true, progressiveData.length > nonProgressiveData.length);
            assert.strictEqual('png', progressiveInfo.format);
            assert.strictEqual(320, progressiveInfo.width);
            assert.strictEqual(240, progressiveInfo.height);
            done();
          });
      });
  });

  it('16-bit grey+alpha PNG identity transform', () => {
    const actual = fixtures.path('output.16-bit-grey-alpha-identity.png');
    return sharp(fixtures.inputPng16BitGreyAlpha)
      .toFile(actual)
      .then(() => {
        fixtures.assertMaxColourDistance(actual, fixtures.expected('16-bit-grey-alpha-identity.png'));
      });
  });

  it('16-bit grey+alpha PNG roundtrip', async () => {
    const after = await sharp(fixtures.inputPng16BitGreyAlpha)
      .toColourspace('grey16')
      .toBuffer();

    const [alphaMeanBefore, alphaMeanAfter] = (
      await Promise.all([
        sharp(fixtures.inputPng16BitGreyAlpha).stats(),
        sharp(after).stats()
      ])
    )
      .map(stats => stats.channels[1].mean);

    assert.strictEqual(alphaMeanAfter, alphaMeanBefore);
  });

  it('palette decode/encode roundtrip', async () => {
    const data = await sharp(fixtures.inputPngPalette)
      .png({ effort: 1, palette: true })
      .toBuffer();

    const { size, ...metadata } = await sharp(data).metadata();
    void size;
    assert.deepStrictEqual(metadata, {
      autoOrient: {
        height: 68,
        width: 68
      },
      format: 'png',
      width: 68,
      height: 68,
      space: 'srgb',
      channels: 3,
      density: 72,
      depth: 'uchar',
      isProgressive: false,
      isPalette: true,
      bitsPerSample: 8,
      paletteBitDepth: 8,
      hasProfile: false,
      hasAlpha: false
    });
  });

  it('Valid PNG libimagequant palette value does not throw error', () => {
    assert.doesNotThrow(() => {
      sharp().png({ palette: false });
    });
  });

  it('Invalid PNG libimagequant palette value throws error', () => {
    assert.throws(() => {
      sharp().png({ palette: 'fail' });
    });
  });

  it('Valid PNG libimagequant quality value produces image of same size or smaller', () => {
    const inputPngBuffer = fs.readFileSync(fixtures.inputPng);
    return Promise.all([
      sharp(inputPngBuffer).resize(10).png({ effort: 1, quality: 80 }).toBuffer(),
      sharp(inputPngBuffer).resize(10).png({ effort: 1, quality: 100 }).toBuffer()
    ]).then((data) => {
      assert.strictEqual(true, data[0].length <= data[1].length);
    });
  });

  it('Invalid PNG libimagequant quality value throws error', () => {
    assert.throws(() => {
      sharp().png({ quality: 101 });
    });
  });

  it('Invalid effort value throws error', () => {
    assert.throws(() => {
      sharp().png({ effort: 0.1 });
    });
  });

  it('Valid PNG libimagequant colours value produces image of same size or smaller', () => {
    const inputPngBuffer = fs.readFileSync(fixtures.inputPng);
    return Promise.all([
      sharp(inputPngBuffer).resize(10).png({ colours: 100 }).toBuffer(),
      sharp(inputPngBuffer).resize(10).png({ colours: 200 }).toBuffer()
    ]).then((data) => {
      assert.strictEqual(true, data[0].length <= data[1].length);
    });
  });

  it('Invalid PNG libimagequant colours value throws error', () => {
    assert.throws(() => {
      sharp().png({ colours: -1 });
    });
  });

  it('Invalid PNG libimagequant colors value throws error', () => {
    assert.throws(() => {
      sharp().png({ colors: 0.1 });
    });
  });

  it('Can set bitdepth of PNG without palette', async () => {
    const data = await sharp({
      create: {
        width: 8, height: 8, channels: 3, background: 'red'
      }
    })
      .toColourspace('b-w')
      .png({ colours: 2, palette: false })
      .toBuffer();

    const { channels, isPalette, bitsPerSample, paletteBitDepth, size, space } = await sharp(data).metadata();
    assert.strictEqual(channels, 1);
    assert.strictEqual(isPalette, false);
    assert.strictEqual(bitsPerSample, 1);
    assert.strictEqual(paletteBitDepth, undefined);
    assert.strictEqual(size, 89);
    assert.strictEqual(space, 'b-w');
  });

  it('Valid PNG libimagequant dither value produces image of same size or smaller', () => {
    const inputPngBuffer = fs.readFileSync(fixtures.inputPng);
    return Promise.all([
      sharp(inputPngBuffer).resize(10).png({ dither: 0.1 }).toBuffer(),
      sharp(inputPngBuffer).resize(10).png({ dither: 0.9 }).toBuffer()
    ]).then((data) => {
      assert.strictEqual(true, data[0].length <= data[1].length);
    });
  });

  it('Invalid PNG libimagequant dither value throws error', () => {
    assert.throws(() => {
      sharp().png({ dither: 'fail' });
    });
  });
});
