import React, { useEffect, useRef } from 'react';
import { IPoint } from '../../Interfaces/IPoint';
import { BAR_WIDTH } from '../Bar/Bar';

interface ICanvasProps {
  width: number
  height: number
  draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void
  className?: string
  style?: React.CSSProperties
};

function UseCanvas(draw: (context: CanvasRenderingContext2D, frameCount: number, scale: number, translatePos: IPoint) => void): React.RefObject<HTMLCanvasElement> {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const frameCount = useRef(0);
  const translatePos = useRef({
    x: 0,
    y: 0
  });

  const startDragOffset = useRef({
    x: 0,
    y: 0
  });

  const scale = useRef(1.0);
  const scaleMultiplier = useRef(0.8);
  const dragging = useRef(false);

  useEffect(() => {
    const canvas = canvasRef.current;

    let animationFrameId = 0;
    if (canvas === null) {
      return;
    }

    const context = canvas.getContext('2d');

    // add event listeners to handle screen drag
    function MouseDown(evt: MouseEvent): void {
      dragging.current = true;
      startDragOffset.current.x = evt.clientX - translatePos.current.x;
      startDragOffset.current.y = evt.clientY - translatePos.current.y;
    }

    function MouseUp(evt: MouseEvent): void {
      dragging.current = false;
    }

    function MouseMove(evt: MouseEvent): void {
      if (!dragging.current || context === null) {
        return;
      }

      translatePos.current.x = evt.clientX - startDragOffset.current.x;
      translatePos.current.y = evt.clientY - startDragOffset.current.y;
      frameCount.current++;
      draw(context, frameCount.current, scale.current, translatePos.current);
    }
    function HandleScroll(evt: WheelEvent): void {
      if (context === null) {
        return;
      }

      if (evt.deltaY >= 0) {
        scale.current *= scaleMultiplier.current;
      } else {
        scale.current /= scaleMultiplier.current;
      }

      draw(context, frameCount.current, scale.current, translatePos.current);
      evt.preventDefault();
    }
    canvas.addEventListener('mousedown', MouseDown);
    window.addEventListener('mouseup', MouseUp);
    canvas.addEventListener('mousemove', MouseMove);
    canvas.addEventListener('wheel', HandleScroll);

    function Render(): void {
      if (context === null) {
        return;
      }

      frameCount.current++;
      draw(context, frameCount.current, scale.current, translatePos.current);
      animationFrameId = window.requestAnimationFrame(Render);
    }

    Render();

    return () => {
      window.cancelAnimationFrame(animationFrameId);
      canvas.removeEventListener('mousedown', MouseDown);
      window.removeEventListener('mouseup', MouseUp);
      canvas.removeEventListener('mousemove', MouseMove);
      canvas.removeEventListener('wheel', HandleScroll);
    };
  }, [draw]);

  return canvasRef;
}

function UseSVGAutoResizer(
  setViewer: React.Dispatch<React.SetStateAction<Viewer>>
): void {
  React.useEffect(() => {
    function OnResize(): void {
      setViewer({
        viewerWidth: window.innerWidth - BAR_WIDTH,
        viewerHeight: window.innerHeight
      });
    }
    window.addEventListener('resize', OnResize);

    return () => {
      window.removeEventListener('resize', OnResize);
    };
  });
}

interface Viewer {
  viewerWidth: number
  viewerHeight: number
}

export function Canvas({ width, height, draw, style, className }: ICanvasProps): JSX.Element {
  const canvasRef = UseCanvas(draw);

  const [{ viewerWidth, viewerHeight }, setViewer] = React.useState<Viewer>({
    viewerWidth: width,
    viewerHeight: height
  });

  UseSVGAutoResizer(setViewer);

  return (
    <canvas
      ref={canvasRef}
      style={style}
      width={viewerWidth}
      height={viewerHeight}
      className={className}
    />
  );
}
