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

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

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

describe('Partial image extraction', () => {
  it('JPEG', (_t, done) => {
    sharp(fixtures.inputJpg)
      .extract({ left: 2, top: 2, width: 20, height: 20 })
      .toBuffer((err, data, info) => {
        if (err) throw err;
        assert.strictEqual(20, info.width);
        assert.strictEqual(20, info.height);
        fixtures.assertSimilar(fixtures.expected('extract.jpg'), data, done);
      });
  });

  it('PNG', (_t, done) => {
    sharp(fixtures.inputPng)
      .extract({ left: 200, top: 300, width: 400, height: 200 })
      .toBuffer((err, data, info) => {
        if (err) throw err;
        assert.strictEqual(400, info.width);
        assert.strictEqual(200, info.height);
        fixtures.assertSimilar(fixtures.expected('extract.png'), data, done);
      });
  });

  it('WebP', (_t, done) => {
    sharp(fixtures.inputWebP)
      .extract({ left: 100, top: 50, width: 125, height: 200 })
      .toBuffer((err, data, info) => {
        if (err) throw err;
        assert.strictEqual(125, info.width);
        assert.strictEqual(200, info.height);
        fixtures.assertSimilar(fixtures.expected('extract.webp'), data, done);
      });
  });

  describe('Animated WebP', () => {
    it('Before resize', (_t, done) => {
      sharp(fixtures.inputWebPAnimated, { pages: -1 })
        .extract({ left: 0, top: 30, width: 80, height: 20 })
        .resize(320, 80)
        .toBuffer((err, data, info) => {
          if (err) throw err;
          assert.strictEqual(320, info.width);
          assert.strictEqual(80 * 9, info.height);
          fixtures.assertSimilar(fixtures.expected('gravity-center-height.webp'), data, done);
        });
    });

    it('After resize', (_t, done) => {
      sharp(fixtures.inputWebPAnimated, { pages: -1 })
        .resize(320, 320)
        .extract({ left: 0, top: 120, width: 320, height: 80 })
        .toBuffer((err, data, info) => {
          if (err) throw err;
          assert.strictEqual(320, info.width);
          assert.strictEqual(80 * 9, info.height);
          fixtures.assertSimilar(fixtures.expected('gravity-center-height.webp'), data, done);
        });
    });
  });

  it('TIFF', (_t, done) => {
    sharp(fixtures.inputTiff)
      .extract({ left: 34, top: 63, width: 341, height: 529 })
      .toBuffer((err, data, info) => {
        if (err) throw err;
        assert.strictEqual(341, info.width);
        assert.strictEqual(529, info.height);
        fixtures.assertSimilar(fixtures.expected('extract.tiff'), data, done);
      });
  });

  it('Before resize', (_t, done) => {
    sharp(fixtures.inputJpg)
      .extract({ left: 10, top: 10, width: 10, height: 500 })
      .resize(100, 100)
      .toBuffer((err, data, info) => {
        if (err) throw err;
        assert.strictEqual(100, info.width);
        assert.strictEqual(100, info.height);
        fixtures.assertSimilar(fixtures.expected('extract-resize.jpg'), data, done);
      });
  });

  it('After resize and crop', (_t, done) => {
    sharp(fixtures.inputJpg)
      .resize(500, 500, {
        position: sharp.gravity.north
      })
      .extract({ left: 10, top: 10, width: 100, height: 100 })
      .toBuffer((err, data, info) => {
        if (err) throw err;
        assert.strictEqual(100, info.width);
        assert.strictEqual(100, info.height);
        fixtures.assertSimilar(fixtures.expected('resize-crop-extract.jpg'), data, done);
      });
  });

  it('Before and after resize and crop', (_t, done) => {
    sharp(fixtures.inputJpg)
      .extract({ left: 0, top: 0, width: 700, height: 700 })
      .resize(500, 500, {
        position: sharp.gravity.north
      })
      .extract({ left: 10, top: 10, width: 100, height: 100 })
      .toBuffer((err, data, info) => {
        if (err) throw err;
        assert.strictEqual(100, info.width);
        assert.strictEqual(100, info.height);
        fixtures.assertSimilar(fixtures.expected('extract-resize-crop-extract.jpg'), data, done);
      });
  });

  it('Extract then rotate', (_t, done) => {
    sharp(fixtures.inputPngWithGreyAlpha)
      .extract({ left: 20, top: 10, width: 380, height: 280 })
      .rotate(90)
      .jpeg()
      .toBuffer((err, data, info) => {
        if (err) throw err;
        assert.strictEqual(280, info.width);
        assert.strictEqual(380, info.height);
        fixtures.assertSimilar(fixtures.expected('extract-rotate.jpg'), data, done);
      });
  });

  it('Rotate then extract', (_t, done) => {
    sharp(fixtures.inputPngWithGreyAlpha)
      .rotate(90)
      .extract({ left: 20, top: 10, width: 280, height: 380 })
      .toBuffer((err, data, info) => {
        if (err) throw err;
        assert.strictEqual(280, info.width);
        assert.strictEqual(380, info.height);
        fixtures.assertSimilar(fixtures.expected('rotate-extract.jpg'), data, done);
      });
  });

  it('Extract then rotate then extract', (_t, done) => {
    sharp(fixtures.inputPngWithGreyAlpha)
      .extract({ left: 20, top: 10, width: 180, height: 280 })
      .rotate(90)
      .extract({ left: 20, top: 10, width: 200, height: 100 })
      .toBuffer((err, data, info) => {
        if (err) throw err;
        assert.strictEqual(200, info.width);
        assert.strictEqual(100, info.height);
        fixtures.assertSimilar(fixtures.expected('extract-rotate-extract.jpg'), data, done);
      });
  });

  it('Extract then rotate non-90 anagle', (_t, done) => {
    sharp(fixtures.inputPngWithGreyAlpha)
      .extract({ left: 20, top: 10, width: 380, height: 280 })
      .rotate(45)
      .jpeg()
      .toBuffer((err, data, info) => {
        if (err) throw err;
        assert.strictEqual(467, info.width);
        assert.strictEqual(467, info.height);
        fixtures.assertSimilar(fixtures.expected('extract-rotate-45.jpg'), data, done);
      });
  });

  it('Rotate then extract non-90 angle', (_t, done) => {
    sharp(fixtures.inputPngWithGreyAlpha)
      .rotate(45)
      .extract({ left: 20, top: 10, width: 380, height: 280 })
      .jpeg()
      .toBuffer((err, data, info) => {
        if (err) throw err;
        assert.strictEqual(380, info.width);
        assert.strictEqual(280, info.height);
        fixtures.assertSimilar(fixtures.expected('rotate-extract-45.jpg'), data, done);
      });
  });

  describe('Apply exif orientation and mirroring then extract', () => {
    [
      {
        name: 'EXIF-1',
        image: fixtures.inputJpgWithLandscapeExif1
      },
      {
        name: 'EXIF-2',
        image: fixtures.inputJpgWithLandscapeExif2
      },
      {
        name: 'EXIF-3',
        image: fixtures.inputJpgWithLandscapeExif3
      },
      {
        name: 'EXIF-4',
        image: fixtures.inputJpgWithLandscapeExif4
      },
      {
        name: 'EXIF-5',
        image: fixtures.inputJpgWithLandscapeExif5
      },
      {
        name: 'EXIF-6',
        image: fixtures.inputJpgWithLandscapeExif6
      },
      {
        name: 'EXIF-7',
        image: fixtures.inputJpgWithLandscapeExif7
      },
      {
        name: 'EXIF-8',
        image: fixtures.inputJpgWithLandscapeExif8
      }
    ].forEach(({ name, image }) => {
      it(name, (_t, done) => {
        sharp(image)
          .rotate()
          .extract({ left: 0, top: 208, width: 60, height: 40 })
          .toBuffer((err, data) => {
            if (err) throw err;
            fixtures.assertSimilar(fixtures.expected('rotate-mirror-extract.jpg'), data, done);
          });
      });
    });
  });

  describe('Invalid parameters', () => {
    describe('using the legacy extract(top,left,width,height) syntax', () => {
      it('String top', () => {
        assert.throws(() => {
          sharp(fixtures.inputJpg).extract('spoons', 10, 10, 10);
        });
      });

      it('Non-integral left', () => {
        assert.throws(() => {
          sharp(fixtures.inputJpg).extract(10, 10.2, 10, 10);
        });
      });

      it('Negative width - negative', () => {
        assert.throws(() => {
          sharp(fixtures.inputJpg).extract(10, 10, -10, 10);
        });
      });

      it('Null height', () => {
        assert.throws(() => {
          sharp(fixtures.inputJpg).extract(10, 10, 10, null);
        });
      });
    });

    it('Undefined', () => {
      assert.throws(() => {
        sharp(fixtures.inputJpg).extract();
      });
    });

    it('String top', () => {
      assert.throws(() => {
        sharp(fixtures.inputJpg).extract({ left: 10, top: 'spoons', width: 10, height: 10 });
      });
    });

    it('Non-integral left', () => {
      assert.throws(() => {
        sharp(fixtures.inputJpg).extract({ left: 10.2, top: 10, width: 10, height: 10 });
      });
    });

    it('Negative width - negative', () => {
      assert.throws(() => {
        sharp(fixtures.inputJpg).extract({ left: 10, top: 10, width: -10, height: 10 });
      });
    });

    it('Null height', () => {
      assert.throws(() => {
        sharp(fixtures.inputJpg).extract({ left: 10, top: 10, width: 10, height: null });
      });
    });

    it('Bad image area', (_t, done) => {
      sharp(fixtures.inputJpg)
        .extract({ left: 3000, top: 10, width: 10, height: 10 })
        .toBuffer((err) => {
          assert(err instanceof Error);
          assert.strictEqual(err.message, 'extract_area: bad extract area');
          done();
        });
    });

    it('Multiple extract emits warning', () => {
      let warningMessage = '';
      const s = sharp();
      s.on('warning', (msg) => { warningMessage = msg; });
      const options = { top: 0, left: 0, width: 1, height: 1 };
      s.extract(options).extract(options);
      assert.strictEqual(warningMessage, '');
      s.extract(options);
      assert.strictEqual(warningMessage, 'ignoring previous extract options');
    });

    it('Multiple rotate+extract emits warning', () => {
      let warningMessage = '';
      const s = sharp().rotate();
      s.on('warning', (msg) => { warningMessage = msg; });
      const options = { top: 0, left: 0, width: 1, height: 1 };
      s.extract(options).extract(options);
      assert.strictEqual(warningMessage, '');
      s.extract(options);
      assert.strictEqual(warningMessage, 'ignoring previous extract options');
    });

    it('Multiple extract+resize emits warning', () => {
      let warningMessage = '';
      const s = sharp();
      s.on('warning', (msg) => { warningMessage = msg; });
      const options = { top: 0, left: 0, width: 1, height: 1 };
      s.extract(options).extract(options);
      assert.strictEqual(warningMessage, '');
      s.resize(1);
      assert.strictEqual(warningMessage, 'operation order will be: extract, resize, extract');
    });
  });
});
