import {describe, test, assert, vi, expect} from 'vitest';
import {MemoryLoader} from '../../src/io/memoryLoader.js';
import {b64urlToArrayBuffer} from '../dicom/utils.js';

import bbmri53323131 from '../data/bbmri-53323131.dcm?inline';
import bbmri53323275 from '../data/bbmri-53323275.dcm?inline';
import dwvTestSimple from '../data/dwv-test-simple.dcm?inline';
import dwvTestBadPrefix from '../data/dwv-test_bad-prefix.dcm?inline';
import multiframeTest1 from '../data/multiframe-test1.dcm?inline';
import bbmriZip from '../data/bbmri.zip?inline';
import dwvTestBadZip from '../data/dwv-test_bad.zip?inline';

/**
 * Tests for the 'io/memoryLoader.js' file.
 */

describe('io', () => {

  /**
   * Check the events of memory load.
   *
   * @param {string} id An id for the test.
   * @param {Array} data The data to load as a string array.
   * @param {number} nData The theoretical number of data.
   * @param {number} nDataOk The theoretical number of data with no error.
   * @returns {Promise} The load promise.
   */
  async function checkLoad(id, data, nData, nDataOk) {

    return new Promise((resolve) => {

      const prefix = '[' + id + '] ';
      const nDataError = nData - nDataOk;

      // checks
      const loadStartDates = [];
      const progressDates = [];
      const loadItemDates = [];
      const loadDates = [];
      const errorDates = [];
      const abortDates = [];
      let gotLoadEnd = false;

      // create loader
      const loader = new MemoryLoader();
      // callbacks
      loader.onloadstart = function (/*event*/) {
        loadStartDates.push(new Date());
      };
      loader.onprogress = function (/*event*/) {
        progressDates.push(new Date());
      };
      loader.onloaditem = function (/*event*/) {
        loadItemDates.push(new Date());
      };
      loader.onload = function (/*event*/) {
        loadDates.push(new Date());
      };
      loader.onloadend = function (/*event*/) {
        const loadEndDate = new Date();
        assert.notOk(gotLoadEnd,
          prefix + 'Received first load end.');
        gotLoadEnd = true;

        // check number of events
        assert.equal(loadStartDates.length, 1,
          prefix + 'Received one load start.');
        if (nDataOk !== 0) {
          assert.ok(progressDates.length > 0,
            prefix + 'Received at least one progress.');
        }
        assert.equal(loadItemDates.length, nDataOk,
          prefix + 'Received loaditem.');
        const nLoad = nDataError === 0 ? 1 : 0;
        assert.equal(loadDates.length, nLoad,
          prefix + 'Received load.');
        assert.equal(errorDates.length, nDataError,
          prefix + 'Received error(s).');
        assert.equal(abortDates.length, 0,
          prefix + 'Received abort(s).');

        // check start/end sequence
        const loadStartDate = loadStartDates[0];
        assert.ok(loadStartDate <= loadEndDate,
          prefix + 'Received start before load end.');

        let firstProgressDate = null;
        let lastProgressDate = null;
        let firstLoadItemDate = null;
        let lastLoadItemDate = null;

        if (nDataOk !== 0) {
          // check progress sequence
          progressDates.sort();
          firstProgressDate = progressDates[0];
          lastProgressDate = progressDates[progressDates.length - 1];
          assert.ok(loadStartDate <= firstProgressDate,
            prefix + 'Received start before first progress.');
          assert.ok(loadEndDate >= lastProgressDate,
            prefix + 'Received end after last progress.');

          // check load item sequence
          loadItemDates.sort();
          firstLoadItemDate = loadItemDates[0];
          lastLoadItemDate = loadItemDates[loadItemDates.length - 1];
          assert.ok(loadStartDate <= firstLoadItemDate,
            prefix + 'Received start before first load item.');
          assert.ok(loadEndDate >= lastLoadItemDate,
            prefix + 'Received end after last load item.');
        }

        // check load or error event sequence
        if (nDataError === 0) {
          // load is sent if no error happened
          const loadDate = loadDates[0];
          assert.ok(loadStartDate <= loadDate,
            prefix + 'Received start before load.');
          assert.ok(loadDate >= lastProgressDate,
            prefix + 'Received load after last progress.');
          assert.ok(loadDate >= lastLoadItemDate,
            prefix + 'Received load after last load item.');
          assert.ok(loadEndDate >= loadDate,
            prefix + 'Received end after load.');
        } else {
          errorDates.sort();
          const firstErrorDate = errorDates[0];
          const lastErrorDate = errorDates[errorDates.length - 1];
          assert.ok(loadStartDate <= firstErrorDate,
            prefix + 'Received start before first error.');
          assert.ok(loadEndDate >= lastErrorDate,
            prefix + 'Received end after last error.');
        }
        resolve(true);
      };
      loader.onerror = function (/*event*/) {
        errorDates.push(new Date());
      };
      loader.onabort = function (/*event*/) {
        abortDates.push(new Date());
      };

      // launch load
      loader.load(data);
    });
  }

  /**
   * Tests for {@link MemoryLoader} events with single frame data.
   *
   * @function module:tests/io~memoryloader-events-single-frame
   */
  test(
    // eslint-disable-next-line @stylistic/js/max-len
    'MemoryLoader events single frame - #DWV-REQ-IO-04-001 DICOM load start event',
    async () => {
      // #0: 2 good dicom
      const data0 = [
        {
          data: b64urlToArrayBuffer(bbmri53323131),
          filename: 'bbmri-53323131.dcm'
        },
        {
          data: b64urlToArrayBuffer(bbmri53323275),
          filename: 'bbmri-53323275.dcm'
        }
      ];
      const nData0 = data0.length;
      const nDataOk0 = nData0;
      await checkLoad('0', data0, nData0, nDataOk0);

      // // #1: 2 not found (404) dicom
      // const data1 = [
      //   '/a.dcm',
      //   '/b.dcm',
      // ];
      // const nData1 = data1.length;
      // const nDataOk1 = 0;
      // await checkLoad('1', data1, nData1, nDataOk1);

      // // #2: 2 dicom, 1 not found (404, error in XHR request)
      // const data2 = [
      //   '/tests/data/bbmri-53323131.dcm',
      //   '/b.dcm',
      // ];
      // const nData2 = data2.length;
      // const nDataOk2 = 1;
      // await checkLoad('2', data2, nData2, nDataOk2);

      // #3: 2 dicom, 1 bad (bad prefix, error in loader)
      const data3 = [
        {
          data: b64urlToArrayBuffer(dwvTestSimple),
          filename: 'dwv-test-simple.dcm'
        },
        {
          data: b64urlToArrayBuffer(dwvTestBadPrefix),
          filename: 'dwv-test_bad-prefix.dcm',
        }
      ];
      const nData3 = data3.length;
      const nDataOk3 = 1;

      // console warn spy
      const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
      // run test
      await checkLoad('3', data3, nData3, nDataOk3);
      // expect warn call
      expect(consoleSpy).toHaveBeenNthCalledWith(
        1, 'Invalid DICM prefix (DIMC), trying to guess tansfer syntax.');
      const vr = String.fromCharCode(0, 0);
      expect(consoleSpy).toHaveBeenNthCalledWith(
        2, 'Unknown VR: ' + vr + ' (for tag 00000000), treating as \'UN\'');
      // reset spy
      consoleSpy.mockReset();
    }
  );

  /**
   * Tests for {@link MemoryLoader} events with multi frame data.
   *
   * @function module:tests/io~memoryloader-events-multi-frame
   */
  test(
    'MemoryLoader events multi frame - ' +
    '#DWV-REQ-IO-04-001 DICOM load start event',
    async () => {
      // #0: simple multi frame
      const data0 = [
        {
          data: b64urlToArrayBuffer(multiframeTest1),
          filename: 'multiframe-test1.dcm'
        }
      ];
      const nData0 = data0.length;
      const nDataOk0 = nData0;
      await checkLoad('0', data0, nData0, nDataOk0);

      // #1: encoded multi frame
      // TODO seems to cause problems to phantomjs...
      /*const data1 = [
            "/tests/data/multiframe-jpegloss-ge.dcm",
        ];
        const nData1 = data1.length;
        const nDataOk1 = nData1;
        await checkLoad("1", data1, nData1, nDataOk1);*/
    }
  );

  /**
   * Tests for {@link MemoryLoader} events with zipped data.
   * TODO check all good...
   *
   * @function module:tests/io~memoryloader-events-zip
   */
  test(
    'MemoryLoader events zip - #DWV-REQ-IO-04-001 DICOM load start event',
    async () => {
      // #0: simple zip
      const data0 = [
        {
          data: b64urlToArrayBuffer(bbmriZip),
          filename: 'bbmri.zip'
        }
      ];
      const nData0 = 2;
      const nDataOk0 = 2;
      await checkLoad('0', data0, nData0, nDataOk0);

      // // #1: bad link to zip
      // const data1 = [
      //   '/tests/data/a.zip',
      // ];
      // const nData1 = 1;
      // const nDataOk1 = 0;
      // await checkLoad('1', data1, nData1, nDataOk1);

      // #2: zip with erroneus data
      const data2 = [
        {
          data: b64urlToArrayBuffer(dwvTestBadZip),
          filename: 'dwv-test_bad.zip'
        }
      ];
      const nData2 = 2;
      const nDataOk2 = 1;

      // console warn spy
      const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
      // run test
      await checkLoad('2', data2, nData2, nDataOk2);
      // expect warn call
      expect(consoleSpy).toHaveBeenCalledWith(
        'Invalid DICM prefix (DIMC), trying to guess tansfer syntax.');
      // reset spy
      consoleSpy.mockReset();
    }
  );

  /**
   * Tests for {@link UrlsLoader} events with DCMDIR data.
   *
   * @function module:tests/io~urlsloader-events
   */
  // TODO...
  // test('Test UrlsLoader events for DCMDIR data.', () => {
  //   // #0: simple DCMDIR
  //   const data0 = [
  //     '/tests/data/bbmri.dcmdir',
  //   ];
  //   const nData0 = 4;
  //   const nDataOk0 = 4;
  //   await checkLoad('0', data0, nData0, nDataOk0);

  //   // #1: bad link to DCMDIR
  //   const data1 = [
  //     '/tests/data/a.dcmdir',
  //   ];
  //   const nData1 = 1;
  //   const nDataOk1 = 0;
  //   await checkLoad('1', data1, nData1, nDataOk1);

  //   // #2: DCMDIR with bad links -> TODO
  //   // #3: DCMDIR with erroneus data -> TODO
  // });

});