import react, { useState } from 'react';
import { MapScope, Point } from './types';
import Map from './Map';
import Handlers from './Handlers';
import { atom } from 'jotai';
import { tileProviders } from './tile-providers';
import { lon2tile, lat2tile, tile2lat, tile2long } from '../../lib/geo';

/**
 * 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);
  }
);

export interface LiveMapProperties {
  scope: MapScope;
  numberOfTiledLayers?: number;
}

export const LiveMap: react.FC<LiveMapProperties> = (
  props: LiveMapProperties
) => {
  const [scope, setScope] = useState(props.scope);
  console.log(`LiveMap rendering: ${JSON.stringify(scope)}`);
  const transform = (t: Transformation) => {
    const deltaZoom = t.deltaZoom === null ? 1 : t.deltaZoom;
    const deltaZoomLevel = Math.log2(deltaZoom);

    const tileProvider = tileProviders[scope.tileProvider];

    const tilesZoom = Math.min(
      Math.max(Math.round(scope.zoom), tileProvider.minZoom),
      tileProvider.maxZoom
    );
    const softZoom = scope.zoom - tilesZoom;
    const relativeScale = 2 ** softZoom;
    const visibleTileSize = tileProvider.tileSize * relativeScale;

    // Values in pixels
    const actualDeltaShift =
      t.deltaShift === null ? { x: 0, y: 0 } : t.deltaShift;
    const actualZoomCenter =
      t.zoomCenter === null ? { x: 0, y: 0 } : t.zoomCenter;

    // Values in tiles (for the current zoom level)
    const actualDeltaShiftTiles = {
      x: actualDeltaShift.x / visibleTileSize,
      y: actualDeltaShift.y / visibleTileSize,
    };
    const actualZoomCenterTiles = {
      x: actualZoomCenter.x / visibleTileSize,
      y: actualZoomCenter.y / visibleTileSize,
    };
    const tilesCenter: Point = {
      x: lon2tile(scope.center.lon, tilesZoom),
      y: lat2tile(scope.center.lat, tilesZoom),
    };

    const newTilesCenter = {
      x:
        tilesCenter.x -
        actualDeltaShiftTiles.x -
        (window.innerWidth / 2 / visibleTileSize - actualZoomCenterTiles.x) *
          (deltaZoom - 1),
      y:
        tilesCenter.y -
        actualDeltaShiftTiles.y -
        (window.innerHeight / 2 / visibleTileSize - actualZoomCenterTiles.y) *
          (deltaZoom - 1),
    };

    const newScope: MapScope = {
      center: {
        lat: tile2lat(newTilesCenter.y, tilesZoom),
        lon: tile2long(newTilesCenter.x, tilesZoom),
      },
      zoom: deltaZoomLevel + scope.zoom,
      tileProvider: scope.tileProvider,
    };
    // console.log(
    //   `LiveMap transform: ${JSON.stringify(t)}, ${JSON.stringify(
    //     scope
    //   )} -> ${JSON.stringify(newScope)}, delta lat: ${
    //     newScope.center.lat - scope.center.lat
    //   }, delta lon: ${newScope.center.lon - scope.center.lon}`
    // );
    setScope(newScope);
  };

  return (
    <>
      <Handlers transformMap={transform} />
      <Map scope={scope} numberOfTiledLayers={props.numberOfTiledLayers} />
    </>
  );
};

export default LiveMap;