import TileGrid from '../../../../../src/ol/tilegrid/TileGrid.js';
import TileSource from '../../../../../src/ol/source/Tile.js';
import UTFGrid, {CustomTile} from '../../../../../src/ol/source/UTFGrid.js';
import {
  fromLonLat,
  get as getProjection,
  transformExtent,
} from '../../../../../src/ol/proj.js';

describe('ol.source.UTFGrid', function () {
  const url = 'spec/ol/data/utfgrid.json';
  let tileJson = null;

  // Load and parse the UTFGrid fixture
  before(function (done) {
    const client = new XMLHttpRequest();
    client.addEventListener('load', function () {
      tileJson = JSON.parse(this.responseText);
      done();
    });
    client.addEventListener('error', function () {
      done(new Error('Failed to fetch ' + url));
    });
    client.open('GET', url);
    client.send();
  });

  after(function () {
    tileJson = null;
  });

  function getUTFGrid() {
    return new UTFGrid({
      url: url,
    });
  }

  describe('constructor', function () {
    it('needs to be constructed with url option', function () {
      const source = new UTFGrid({url: url});
      expect(source).to.be.an(UTFGrid);
      expect(source).to.be.an(TileSource);

      expect(function () {
        // no options: will throw
        return new UTFGrid();
      }).to.throwException();

      expect(function () {
        // no url-option: will throw
        return new UTFGrid({});
      }).to.throwException();

      expect(getUTFGrid()).to.be.an(UTFGrid);
    });
  });

  describe('change event (ready)', function () {
    it('is fired when the source is ready', function (done) {
      const source = new UTFGrid({
        url: url,
      });
      expect(source.getState()).to.be('loading');
      expect(source.tileGrid).to.be(null);

      source.on('change', function (event) {
        if (source.getState() === 'ready') {
          expect(source.tileGrid).to.be.an(TileGrid);
          done();
        }
      });
    });
  });

  describe('change event (error)', function (done) {
    it('is fired when the source fails to initialize', function (done) {
      const source = new UTFGrid({
        url: 'Bogus UTFGrid URL',
      });
      expect(source.getState()).to.be('loading');
      expect(source.tileGrid).to.be(null);

      source.on('change', function (event) {
        if (source.getState() === 'error') {
          expect(source.tileGrid).to.be(null);
          done();
        }
      });
    });
  });

  describe('#handleTileJSONResponse', function () {
    it('sets up a tileGrid', function () {
      const source = getUTFGrid();
      expect(source.getTileGrid()).to.be(null);
      // call the handleTileJSONResponse method with our
      // locally available tileJson (from `before`)
      source.handleTileJSONResponse(tileJson);

      const tileGrid = source.getTileGrid();
      expect(tileGrid).to.not.be(null);
      expect(tileGrid).to.be.an(TileGrid);
    });

    it('sets up a tilegrid with expected extent', function () {
      const source = getUTFGrid();
      // call the handleTileJSONResponse method with our
      // locally available tileJson (from `before`)
      source.handleTileJSONResponse(tileJson);

      const tileGrid = source.getTileGrid();
      const extent = tileGrid.getExtent();

      const proj4326 = getProjection('EPSG:4326');
      const proj3857 = getProjection('EPSG:3857');
      const expectedExtent4326 = tileJson.bounds;
      const expectedExtent3857 = transformExtent(
        expectedExtent4326,
        proj4326,
        proj3857
      );
      expect(extent).to.eql(proj3857.getExtent());
      expect(extent[0]).to.roughlyEqual(expectedExtent3857[0], 1e-8);
      expect(extent[1]).to.roughlyEqual(expectedExtent3857[1], 1e-8);
      expect(extent[2]).to.roughlyEqual(expectedExtent3857[2], 1e-8);
      expect(extent[3]).to.roughlyEqual(expectedExtent3857[3], 1e-8);
    });

    it('sets up a tilegrid with expected minZoom', function () {
      const source = getUTFGrid();
      // call the handleTileJSONResponse method with our
      // locally available tileJson (from `before`)
      source.handleTileJSONResponse(tileJson);

      const tileGrid = source.getTileGrid();
      const minZoom = tileGrid.getMinZoom();
      expect(minZoom).to.eql(tileJson.minzoom);
    });

    it('sets up a tilegrid with expected maxZoom', function () {
      const source = getUTFGrid();
      // call the handleTileJSONResponse method with our
      // locally available tileJson (from `before`)
      source.handleTileJSONResponse(tileJson);

      const tileGrid = source.getTileGrid();
      const maxZoom = tileGrid.getMaxZoom();
      expect(maxZoom).to.eql(tileJson.maxzoom);
    });

    it('sets up a template', function () {
      const source = getUTFGrid();
      expect(source.getTemplate()).to.be(undefined);

      // call the handleTileJSONResponse method with our
      // locally available tileJson (from `before`)
      source.handleTileJSONResponse(tileJson);

      const template = source.getTemplate();
      expect(template).to.not.be(undefined);
      expect(template).to.be(tileJson.template);
    });

    it('sets up correct attribution', function () {
      const source = getUTFGrid();
      expect(source.getAttributions()).to.be(null);

      // call the handleTileJSONResponse method with our
      // locally available tileJson (from `before`)
      source.handleTileJSONResponse(tileJson);

      const attributions = source.getAttributions();
      expect(attributions).to.not.be(null);
      expect(typeof attributions).to.be('function');
    });

    it('sets correct state', function () {
      const source = getUTFGrid();
      expect(source.getState()).to.be('loading');

      // call the handleTileJSONResponse method with our
      // locally available tileJson (from `before`)
      source.handleTileJSONResponse(tileJson);

      expect(source.getState()).to.be('ready');
    });
  });

  describe('#forDataAtCoordinateAndResolution', function () {
    let source = null;
    const bonn3857 = fromLonLat([7.099814, 50.733992]);
    const noState3857 = [0, 0];
    const resolutionZoom1 = 78271.51696402048;

    let gridJson110 = null;
    // Called once for this describe section, this method will request a local
    // grid for one tile (1/1/0) and store the result in a variable. This allows
    // us to overwrite getTile in a way that removes the dependency on an
    // external service. See below in the `beforeEach`-method.
    before(function (done) {
      const client = new XMLHttpRequest();
      client.addEventListener('load', function () {
        gridJson110 = JSON.parse(this.responseText);
        done();
      });
      client.addEventListener('error', function () {
        done(new Error('Failed to fetch local grid.json'));
      });
      client.open('GET', 'spec/ol/data/mapbox-geography-class-1-1-0.grid.json');
      client.send();
    });
    after(function () {
      gridJson110 = null;
    });

    beforeEach(function () {
      source = getUTFGrid();
      // call the handleTileJSONResponse method with our
      // locally available tileJson (from `before`)
      source.handleTileJSONResponse(tileJson);

      // Override getTile method to not depend on the external service. The
      // signature of the method is kept the same, but the returned tile will
      // always be for [1, 1, 0].
      source.getTile = function (z, x, y, pixelRatio, projection) {
        const tileCoord = [1, 1, 0]; // overwritten to match our stored JSON
        const urlTileCoord = this.getTileCoordForTileUrlFunction(
          tileCoord,
          projection
        );
        const tileUrl = this.tileUrlFunction_(
          urlTileCoord,
          pixelRatio,
          projection
        );
        const tile = new CustomTile(
          tileCoord,
          tileUrl !== undefined ? 0 : 4, // IDLE : EMPTY
          tileUrl !== undefined ? tileUrl : '',
          this.tileGrid.getTileCoordExtent(tileCoord),
          true
        ); // always preemptive, so loading doesn't happen automatically
        // manually call handleLoad_ with our local JSON data
        tile.handleLoad_(gridJson110);
        return tile;
      };
    });
    afterEach(function () {
      source = null;
    });

    it('calls callback with data if found', function (done) {
      const callback = function (data) {
        expect(arguments).to.have.length(1);
        expect(data).to.not.be(null);
        expect('admin' in data).to.be(true);
        expect(data.admin).to.be('Germany');
        done();
      };
      source.forDataAtCoordinateAndResolution(
        bonn3857,
        resolutionZoom1,
        callback,
        true
      );
    });

    it('calls callback with `null` if not found', function (done) {
      const callback = function (data) {
        expect(arguments).to.have.length(1);
        expect(data).to.be(null);
        done();
      };
      source.forDataAtCoordinateAndResolution(
        noState3857,
        resolutionZoom1,
        callback,
        true
      );
    });
  });
});
