import react, { memo } from 'react';
import { isEqual, range } from 'lodash';

import { Rectangle, TileKeyObject } from './types';
import tileUri from './uris';
import Tile from './Tile';
import { tileSetConfig } from './config';

/**
 * @hidden
 */
export const thisIsAModule = true;

/**
 *
 */

declare global {
  var cacheForTileSet: any;
}

//export {};

globalThis.cacheForTileSet = new Map();

export interface TileSetProperties {
  /** A partial Tile key object specifying the provider and zoom level */
  keyObject: TileKeyObject;
  /** The current viewport expressed in tiles coordinates */
  viewPort?: Rectangle;
}

/**
 * A lazily loaded set of tiles.
 *
 * This component is rather dumb and is mainly a sparse array of tiles.
 *
 * New tiles are added to the array when the viewport is updated and they stay in the array until
 * the component is destroyed.
 *
 * This component has no need to know the number nor the size of its tiles: tiles can be added when needed and
 * its unit is the tile size (the parent component needs to transform its enclosing SVG group to adapt its units)
 *
 * The `globalThis.cacheForTiledLayer` global variable is used as a cache to store tiles without being subject
 * to re-initialization when components are unmounted/remounted.
 *
 * This cache is a map of map, the first key identifying the `<TiledLayer` and the second one for `<Tile>`s.
 *
 * Idea stolen [on the web](https://dev.to/tiagof/react-re-mounting-vs-re-rendering-lnh)
 *
 *
 */
export const TileSet: react.FC<TileSetProperties> = memo(
  (props: TileSetProperties) => {
    // console.log(`Rendering TiledLayer: ${JSON.stringify(props)}`);

    const key = tileUri({
      provider: props.keyObject.provider,
      zoomLevel: props.keyObject.zoomLevel,
    });

    const tiles: any = globalThis.cacheForTileSet.get(key) ?? new Map();
    if (props.viewPort !== undefined) {
      range(props.viewPort.topLeft.y, props.viewPort.bottomRight.y + 1).forEach(
        (row) => {
          range(
            props.viewPort!.topLeft.x,
            props.viewPort!.bottomRight.x + 1
          ).forEach((col) => {
            const keyObject = {
              provider: props.keyObject.provider,
              zoomLevel: props.keyObject.zoomLevel,
              x: props.keyObject.x + col,
              y: props.keyObject.y + row,
            };
            const key = tileUri(keyObject);
            if (!tiles.has(key)) {
              tiles.set(
                key,
                <Tile
                  key={key}
                  keyObject={{
                    provider: props.keyObject.provider,
                    zoomLevel: props.keyObject.zoomLevel,
                    x: col,
                    y: row,
                  }}
                />
              );
            } else {
              const tile = tiles.get(key);
              tiles.delete(key);
              tiles.set(key, tile);
            }
          });
        }
      );
      if (tiles.size > tileSetConfig.cacheSizePerLayer) {
        const oldestTileKeys = [...tiles.keys()];
        oldestTileKeys.splice(-tileSetConfig.cacheSizePerLayer);
        oldestTileKeys.forEach((tileKey) => {
          tiles.delete(tileKey);
        });
      }
      globalThis.cacheForTileSet.set(key, tiles);
      if (globalThis.cacheForTileSet > tileSetConfig.numberOfCachedLayers) {
        const oldestCachedLayerKeys = [...globalThis.cacheForTileSet.keys()];
        oldestCachedLayerKeys.slice(-tileSetConfig.numberOfCachedLayers);
        oldestCachedLayerKeys.forEach((cachedLayerKey) => {
          globalThis.cacheForTileSet.delete(cachedLayerKey);
        });
      }
    }

    return <>{Array.from(tiles.values())}</>;
  },
  isEqual
);

export default TileSet;
