type DragCallback = (dx: number, dy: number) => void;
type ZoomCallback = (scale: number, focusX: number, focusY: number) => void;
type TapCallback = (dx: number, dy: number) => void;

export interface Callbacks {
  drag?: DragCallback;
  zoom?: ZoomCallback;
  tap?: TapCallback;
}

interface MyTouch {
  identifier: number;
  lastX: number;
  lastY: number;
  dx: number;
  dy: number;
}

interface ScaleTracker {
  focusX: number;
  focusY: number;
  distance: number;
}

export class TouchTracker {
  touches: Array<MyTouch> = [];
  callbacks: Callbacks;
  el: HTMLElement;

  scaleTracker?: ScaleTracker;

  distance = 0;

  constructor(el: HTMLElement, callbacks: Callbacks) {
    this.callbacks = callbacks;
    this.el = el;
  }

  private removeTouch(identifier: number) {
    let index = this.touches.findIndex((t) => t.identifier == identifier);
    if (index >= 0) {
      this.touches.splice(index, 1);
    }
  }

  private getTouch(identifier: number): MyTouch | null {
    let index = this.touches.findIndex((t) => t.identifier == identifier);
    if (index >= 0) return this.touches[index];
    else return null;
  }

  private getX(touch: Touch) {
    const rect = this.el.getBoundingClientRect();
    return touch.clientX - rect.left;
  }
  private getY(touch: Touch) {
    const rect = this.el.getBoundingClientRect();
    return touch.clientY - rect.top;
  }

  start(list: TouchList) {
    for (var i = 0; i < list.length; i++) {
      let touch = list[i];
      this.removeTouch(touch.identifier);
      this.touches.push({
        lastX: this.getX(touch),
        lastY: this.getY(touch),
        dx: 0,
        dy: 0,
        identifier: touch.identifier,
      });
    }

    if (this.touches.length == 1) {
      this.distance = 0;
    }

    this.updateFocus();
  }

  updateFocus() {
    if (this.touches.length < 2) {
      this.scaleTracker = undefined;
      return;
    }

    let t1 = this.touches[0];
    let t2 = this.touches[1];

    let focusX = (t1.lastX + t2.lastX) / 2;
    let focusY = (t1.lastY + t2.lastY) / 2;
    let distance = Math.sqrt(
      Math.pow(t2.lastX - t1.lastX, 2) + Math.pow(t2.lastY - t1.lastY, 2),
    );
    this.scaleTracker = { focusX, focusY, distance };
  }

  end(list: TouchList) {
    console.log("end");
    for (var i = 0; i < list.length; i++) {
      this.removeTouch(list[i].identifier);
    }
    this.updateFocus();

    if (this.touches.length <= 0 && this.distance == 0) {
      let touch = list[0];
      const rect = this.el.getBoundingClientRect();
      //console.log('kkkkkk', el.offsetLeft, el.offsetTop)
      this.callbacks?.tap?.(
        touch.clientX - rect.left,
        touch.clientY - rect.top,
      );
    }
  }

  move(list: TouchList) {
    for (var i = 0; i < list.length; i++) {
      let touch = list[i];
      let mytouch = this.getTouch(touch.identifier);
      if (mytouch == null) continue;
      mytouch.dx = this.getX(touch) - mytouch.lastX;
      mytouch.dy = this.getY(touch) - mytouch.lastY;
      mytouch.lastX = this.getX(touch);
      mytouch.lastY = this.getY(touch);

      if (this.scaleTracker) {
        let t1 = this.touches[0];
        let t2 = this.touches[1];

        let focusX = (t1.lastX + t2.lastX) / 2;
        let focusY = (t1.lastY + t2.lastY) / 2;
        let distance = Math.sqrt(
          Math.pow(t2.lastX - t1.lastX, 2) + Math.pow(t2.lastY - t1.lastY, 2),
        );
        let dx = focusX - this.scaleTracker.focusX;
        let dy = focusY - this.scaleTracker.focusY;

        let scale = distance / this.scaleTracker.distance;

        console.log(`dx=${dx}, dy=${dy}, scale=${scale}, distance=${distance}`);

        this.callbacks.drag?.(dx, dy);
        this.callbacks.zoom?.(scale, focusX, focusY);

        this.scaleTracker.focusX = focusX;
        this.scaleTracker.focusY = focusY;
        this.scaleTracker.distance = distance;

        this.distance += Math.abs(dx) + Math.abs(dy);
      } else {
        //if(mytouch.dx > 0 || mytouch.dy >0) {
        this.callbacks.drag?.(mytouch.dx, mytouch.dy);
        // }
        this.distance += Math.abs(mytouch.dx) + Math.abs(mytouch.dy);
      }
    }
  }
}

export class Gesture {
  constructor(el: HTMLElement, callbacks: Callbacks) {
    var dx, dy;
    var isDown = false;
    var lastX: number = 0;
    var lastY: number = 0;
    var distance = 0;

    el.addEventListener("mousedown", (e) => {
      isDown = true;
      lastX = e.clientX;
      lastY = e.clientY;
      distance = 0;
    });

    document.addEventListener("mouseup", (e) => {
      isDown = false;
      if (distance == 0 && e.target == el) {
        callbacks.tap?.(e.offsetX, e.offsetY);
      }
    });

    document.addEventListener("mousemove", (e) => {
      if (isDown) {
        e.preventDefault();
        dx = e.clientX - lastX;
        dy = e.clientY - lastY;
        lastX = e.clientX;
        lastY = e.clientY;
        distance += Math.abs(dx) + Math.abs(dy);
        callbacks?.drag?.(dx, dy);
      }
    });

    el.addEventListener("wheel", (e) => {
      e.preventDefault();
      let scale = e.deltaY * -0.01 + 1;
      callbacks.zoom?.(scale, e.offsetX, e.offsetY);
    });

    el.addEventListener("click", (e) => {
      e.preventDefault();
      //callbacks.tap?.(e.offsetX, e.offsetY)
    });

    const tracker = new TouchTracker(el, callbacks);

    el.addEventListener("touchstart", (e) => {
      e.preventDefault();
      tracker.start(e.changedTouches);
    });

    el.addEventListener("touchmove", (e) => {
      tracker.move(e.changedTouches);
    });

    el.addEventListener("touchend", (e) => {
      tracker.end(e.changedTouches);
    });
    el.addEventListener("touchcancel", (e) => {
      tracker.end(e.changedTouches);
    });
  }
}
