import react from 'react';
import { atom } from 'jotai';

import { Point, MapScope } from './types';
import LayerStack from './LayerStack';
import { tileProviders } from './tile-providers';
import { lon2tile, lat2tile } from '../../lib/geo';

export interface MapProperties {
  scope: MapScope;
  numberOfTiledLayers: number;
}

/**
 * Definition of a coordinate system
 *
 * The coordinate system is shifted and zoomed (from the viewport origin)
 *
 */
export interface CoordinateSystem {
  /** Zoom relative to the origin */
  zoom: number;
  /** Origin's shift (in pixels) */
  shift: Point;
}

const initialCoordinateSystem: CoordinateSystem = {
  zoom: 1,
  shift: { x: 0, y: 0 },
};

/** An atom to store the map coordinates system */
export const coordinateSystemAtom = atom(initialCoordinateSystem);

/**
 * Description of coordinates system transformation
 */
export interface Transformation {
  /** New translation to apply */
  deltaShift: Point | null;
  /** Zoom factor to apply */
  deltaZoom: number | null;
  /** Center of the new zoom to apply */
  zoomCenter: Point | null;
}

/**
 * A write only atom to translate and zoom the coordinate system
 */
export const relativeCoordinateSystemAtom = atom(
  null,
  (get, set, t: Transformation) => {
    const actualDeltaShift =
      t.deltaShift === null ? { x: 0, y: 0 } : t.deltaShift;
    const actualDeltaZoom = t.deltaZoom === null ? 1 : t.deltaZoom;
    const actualZoomCenter =
      t.zoomCenter === null ? { x: 0, y: 0 } : t.zoomCenter;
    const coordinateSystem = get(coordinateSystemAtom);
    var newCoordinateSystem = {
      shift: {
        x:
          coordinateSystem.shift.x +
          actualDeltaShift.x +
          (coordinateSystem.shift.x - actualZoomCenter.x) *
            (actualDeltaZoom - 1),
        y:
          coordinateSystem.shift.y +
          actualDeltaShift.y +
          (coordinateSystem.shift.y - actualZoomCenter.y) *
            (actualDeltaZoom - 1),
      },
      zoom: coordinateSystem.zoom * actualDeltaZoom,
    };
    set(coordinateSystemAtom, newCoordinateSystem);
  }
);

/**
 *
 * @returns A `<Map>` component
 *
 */
export const Map: react.FC<MapProperties> = (props: MapProperties) => {
  const centerPX = {
    x: window.innerWidth / 2,
    y: window.innerHeight / 2,
  };

  const tileProvider = tileProviders[props.scope.tileProvider];

  const tilesZoom = Math.min(
    Math.max(Math.round(props.scope.zoom), tileProvider.minZoom),
    tileProvider.maxZoom
  );
  const tilesCenter: Point = {
    x: lon2tile(props.scope.center.lon, tilesZoom),
    y: lat2tile(props.scope.center.lat, tilesZoom),
  };
  const softZoom = props.scope.zoom - tilesZoom;
  const relativeScale = 2 ** softZoom;
  const visibleTileSize = tileProvider.tileSize * relativeScale;
  const nbTilesLeft = window.innerWidth / 2 / visibleTileSize;
  const nbTilesTop = window.innerHeight / 2 / visibleTileSize;
  const firstTileLeft = Math.floor(tilesCenter.x - nbTilesLeft);
  const firstTileTop = Math.floor(tilesCenter.y - nbTilesTop);

  return (
    <>
      <LayerStack
        numberOfTiledLayers={props.numberOfTiledLayers}
        keyObject={{
          provider: props.scope.tileProvider,
          zoomLevel: tilesZoom,
          x: firstTileLeft,
          y: firstTileTop,
        }}
        coordinateSystem={{
          shift: {
            x: -((tilesCenter.x - nbTilesLeft) % 1) * visibleTileSize,
            y: -((tilesCenter.y - nbTilesTop) % 1) * visibleTileSize,
          },
          zoom: relativeScale,
        }}
      />
    </>
  );
};

export default Map;
