import react, { useRef } from 'react';
import { useAtom } from 'jotai';

import { Point } from './types';
import './Handler.css';
import { handlersConfig } from './config';
import { relativeCoordinateSystemAtom, Transformation } from './LiveMap';

/**
 *
 * See also {@link components/map/Map!relativeCoordinateSystemAtom}.
 *
 */
export interface HandlersProperties {
  transformMap: (t: Transformation) => void;
}
/**
 *
 * @param props
 * @returns A div with the following handlers
 * * mouseLeave, mouseDown and mouseUp to track the mouse state
 * * mouseMove to shift the map if the mouse is down
 * * doubleClick to zoom in
 * * wheel to zoom in and out
 * * touchStart, touchEnd and touchCancel to track touch state
 * * touchMove to shift the map (single finger) or shift and zoom (two fingers).
 *
 * Communication with the parent `<Map>` is done through {@link components/map/Map!relativeCoordinateSystemAtom}.
 */
export const Handlers: react.FC<HandlersProperties> = (
  props: HandlersProperties
) => {
  const genericHandler = (event: any) => {
    // console.log(`Log - Event:  ${event.type}`);
    // if (event.clientX !== undefined) {
    //   console.log(
    //     `Mouse : ${event.clientX}, ${event.clientY}, target: ${event.target}`
    //   );
    //   console.log(
    //     `mouseState: ' ${JSON.stringify(mouseState)} (+${
    //       Date.now() - mouseState.timestamp
    //     }ms) `
    //   );
    //   return;
    //}
  };

  /**
   *
   * Mouse handlers
   *
   */
  const initialMouseState = {
    down: false,
    starting: { x: -1, y: -1 },
    timestamp: 0,
  };

  const mouseState = useRef(initialMouseState);

  const mouseLeaveHandler = (event: any) => {
    genericHandler(event);
    mouseState.current = initialMouseState;
  };

  const mouseDownHandler = (event: any) => {
    genericHandler(event);
    mouseState.current = {
      down: true,
      starting: { x: event.clientX, y: event.clientY },
      timestamp: 0,
    };
  };

  const mouseUpHandler = (event: any) => {
    genericHandler(event);
    mouseState.current = initialMouseState;
  };

  const mouseMoveHandler = (event: any) => {
    if (
      mouseState.current.down &&
      Date.now() - mouseState.current.timestamp >
        handlersConfig.mouseMoveThrottleDelay &&
      (event.clientX - mouseState.current.starting.x) ** 2 +
        (event.clientY - mouseState.current.starting.y) ** 2 >
        100
    ) {
      genericHandler(event);
      if (mouseState.current.down) {
        props.transformMap({
          deltaShift: {
            x: event.clientX - mouseState.current.starting.x,
            y: event.clientY - mouseState.current.starting.y,
          },
          deltaZoom: null,
          zoomCenter: null,
        });
        mouseState.current = {
          down: true,
          starting: {
            x: event.clientX,
            y: event.clientY,
          },
          timestamp: Date.now(),
        };
      }
    }
  };

  /**
   *
   * Double click
   *
   */

  const doubleClickHandler = (event: any) => {
    genericHandler(event);
    props.transformMap({
      deltaShift: null,
      deltaZoom: Math.SQRT2,
      zoomCenter: {
        x: event.clientX,
        y: event.clientY,
      },
    });
  };

  /**
   *
   * Wheel handler
   *
   */

  const initialWheelState = {
    timestamp: 0,
  };

  const wheelState = useRef(initialWheelState);

  const wheelEventHandler = (event: any) => {
    genericHandler(event);
    if (
      event.deltaMode === WheelEvent.DOM_DELTA_PIXEL &&
      Date.now() - wheelState.current.timestamp >
        handlersConfig.wheelThrottleDelay
    ) {
      props.transformMap({
        deltaShift: null,
        deltaZoom: event.deltaY < 0 ? Math.SQRT2 : Math.SQRT1_2,
        zoomCenter: {
          x: event.clientX,
          y: event.clientY,
        },
      });
      wheelState.current = {
        timestamp: Date.now(),
      };
    }
  };

  /**
   *
   * Touch handlers
   *
   */

  interface TouchState {
    state: 'up' | 'pointer' | 'double';
    touches: Point[];
    distance: number;
    timestamp: number;
  }

  const initialTouchState: TouchState = {
    state: 'up',
    touches: [],
    distance: -1,
    timestamp: 0,
  };

  const touchState = useRef<TouchState>(initialTouchState);

  const touchCancelHandler = (event: any) => {
    genericHandler(event);
    touchState.current = initialTouchState;
  };

  const touchEndHandler = touchCancelHandler;

  const touchStartHandler = (event: any) => {
    genericHandler(event);
    if (event.touches.length === 2) {
      touchState.current = {
        state: 'double',
        touches: [
          { x: event.touches[0].screenX, y: event.touches[0].screenY },
          { x: event.touches[1].screenX, y: event.touches[1].screenY },
        ],
        distance: Math.sqrt(
          (event.touches[0].screenX - event.touches[1].screenX) ** 2 +
            (event.touches[0].screenY - event.touches[1].screenY) ** 2
        ),
        timestamp: Date.now(),
      };
    } else if (event.touches.length === 1) {
      touchState.current = {
        state: 'pointer',
        touches: [{ x: event.touches[0].screenX, y: event.touches[0].screenY }],
        timestamp: Date.now(),
        distance: -1,
      };
    }
  };

  const touchMoveHandler = (event: any) => {
    if (
      touchState.current.state === 'double' &&
      Date.now() - touchState.current.timestamp >
        handlersConfig.doubleTouchMoveThrottleDelay
    ) {
      if (event.touches.length === 2) {
        genericHandler(event);
        const newDistance = Math.sqrt(
          (event.touches[0].screenX - event.touches[1].screenX) ** 2 +
            (event.touches[0].screenY - event.touches[1].screenY) ** 2
        );
        const factor = newDistance / touchState.current.distance;
        //     console.log(`+++++++++ ZOOM Factor is ${factor} ++++++++++`);
        touchState.current = {
          state: 'double',
          touches: [
            { x: event.touches[0].screenX, y: event.touches[0].screenY },
            { x: event.touches[1].screenX, y: event.touches[1].screenY },
          ],
          distance: newDistance,
          timestamp: Date.now(),
        };
        const previousCenter = {
          x:
            (touchState.current.touches[0].x +
              touchState.current.touches[1].x) /
            2,
          y:
            (touchState.current.touches[0].y +
              touchState.current.touches[1].y) /
            2,
        };
        const currentCenter = {
          x: (event.touches[0].screenX + event.touches[1].screenX) / 2,
          y: (event.touches[0].screenY + event.touches[1].screenY) / 2,
        };
        props.transformMap({
          deltaShift: {
            x: currentCenter.x - previousCenter.x,
            y: currentCenter.y - previousCenter.y,
          },
          deltaZoom: factor,
          zoomCenter: {
            x: currentCenter.x - previousCenter.x,
            y: currentCenter.y - previousCenter.y,
          },
        });
      }
    } else if (
      touchState.current.state === 'pointer' &&
      Date.now() - touchState.current.timestamp >
        handlersConfig.singleTouchMoveThrottleDelay
    ) {
      if (event.touches.length === 1) {
        genericHandler(event);
        props.transformMap({
          deltaShift: {
            x: event.touches[0].screenX - touchState.current.touches[0].x,
            y: event.touches[0].screenY - touchState.current.touches[0].y,
          },
          deltaZoom: null,
          zoomCenter: null,
        });
        touchState.current = {
          state: 'pointer',
          touches: [
            {
              x: event.touches[0].screenX,
              y: event.touches[0].screenY,
            },
          ],
          timestamp: Date.now(),
          distance: -1,
        };
      }
    }
  };

  return (
    <div
      className='handler'
      role='presentation'
      onMouseDown={mouseDownHandler}
      onMouseMove={mouseMoveHandler}
      onMouseUp={mouseUpHandler}
      onMouseLeave={mouseLeaveHandler}
      onDoubleClick={doubleClickHandler}
      onWheel={wheelEventHandler}
      onTouchEnd={touchEndHandler}
      onTouchCancel={touchCancelHandler}
      onTouchStart={touchStartHandler}
      onTouchMove={touchMoveHandler}
    />
  );
};

export default Handlers;
