import {
  MODE_MASS_GENERATION,
  MODE_OBJECT_SELECT,
  MODE_PAN_VIEW,
  POINTER_HISTORY_SIZE,
  ZOOM_IN_FACTOR,
  ZOOM_OUT_FACTOR
} from './config.js';

export class Pointer {
  sim = undefined;

  pointerHistory = [];
  suppressClick = false;

  constructor(sim) {
    this.sim = sim;

    // Monitor mouse movements
    const {canvas} = this.sim.display;

    window.addEventListener('pointermove', e => {
      if (this.sim.getOption('debug.cursorInfo')) {
        this.sim.info['pointermove'] = [`${e.clientX.toPrecision(6)}, `, `${e.clientY.toPrecision(6)}`];
      }
      this.handlePointerMove({x: e.clientX, y: e.clientY});
    });

    canvas.addEventListener('pointerdown', e => {
      this.handlePointerDown({x: e.clientX, y: e.clientY});
    });

    window.addEventListener('pointerup', e => {
      this.handlePointerUp({x: e.clientX, y: e.clientY});
    });

    // window.addEventListener('pointerleave', e => {
    //   this.handlePointerUp({x: e.clientX, y: e.clientY});
    // });

    // Monitor wheel events
    canvas.addEventListener('wheel', e => {
      const factor = e.deltaY < 0 ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;
      const {x, y} = this.sim.screenToSim(e.clientX, e.clientY);
      this.sim.scheduleZoom({x, y}, factor);
    });

    window.addEventListener('focus', () => {
      // console.log('window focus');
    });
    window.addEventListener('blur', () => {
      // console.log('window blur');
    });
  }

  handlePointerDown({x: clientX, y: clientY, touches}) {
    // TODO: Check if pointer was already down; 
    // equivalently, e.touches.length > 1

    // Global pinch to zoom? Or only in certain modes?

    this.updatePointer({x: clientX, y: clientY});

    switch (this.sim.getCurrentMode()) {
      case MODE_MASS_GENERATION: {
        const {x, y} = this.sim.screenToSim(clientX, clientY)
        this.sim.system.handlePointerDown({x, y});
        break;
      }
      case MODE_PAN_VIEW: {
        this.sim.panning.handlePointerDown({x: clientX, y: clientY});
        break;
      }
      case MODE_OBJECT_SELECT: {
        this.sim.select.handlePointerDown({x: clientX, y: clientY});
        break;
      }
    }
  }

  // Handle cursor (mouse or touch) movement
  handlePointerMove({x: clientX, y: clientY}) {
    // TODO: If e.touches.length > 1, user may be engaging pinch to zoom
    this.updatePointer({x: clientX, y: clientY});

    switch (this.sim.getCurrentMode()) {
      case MODE_MASS_GENERATION: {
        const {x, y} = this.sim.screenToSim(clientX, clientY);
        this.sim.system.handlePointerMove({x, y});
        break;
      }
      case MODE_PAN_VIEW: {
        this.sim.panning.handlePointerMove({x: clientX, y: clientY});
        break;
      }
      case MODE_OBJECT_SELECT: {
        this.sim.select.handlePointerMove({x: clientX, y: clientY});
        break;
      }
    }
  }

  handlePointerUp({x: clientX, y: clientY}) {
    switch (this.sim.getCurrentMode()) {
      case MODE_MASS_GENERATION: {
        const {x, y} = this.sim.screenToSim(clientX, clientY);
        this.sim.system.handlePointerUp({x, y});
        break;
      }
      case MODE_PAN_VIEW: {
        this.sim.panning.handlePointerUp({x: clientX, y: clientY});
        break;
      }
      case MODE_OBJECT_SELECT: {
        this.sim.select.handlePointerUp({x: clientX, y: clientY});
        break;
      }
    }
  }

  frame() {
    // Add another entry for the current pointer position
    const {pointerHistory} = this;
    if (pointerHistory.length) {
      const currentPointer = pointerHistory[pointerHistory.length - 1];
      this.updatePointer(currentPointer);
    }
  }

  getPointerVelocity(points = POINTER_HISTORY_SIZE) {
    // Average over pointer history
    if (this.pointerHistory.length < 2) {
      return this.latestVelocity;
    }
    points = Math.min(points, POINTER_HISTORY_SIZE, this.pointerHistory.length);
    const start = this.pointerHistory[this.pointerHistory.length - points];
    const end = this.pointerHistory[this.pointerHistory.length - 1];
    const dt = (end.t - start.t);
    return {
      x: (end.x - start.x) / dt,
      y: (end.y - start.y) / dt,
      dt
    };
  }

  updatePointer({x, y}) {
    const t = this.sim.rawTime;
    while (this.pointerHistory.length >= POINTER_HISTORY_SIZE) {
      this.pointerHistory.shift();
    }
    const v = this.getPointerVelocity();
    this.pointerHistory.push({t, x, y, v});
  }

  get latestVelocity() {
    const latestPointer = this.pointerHistory[this.pointerHistory.length - 1];
    return {
      x: 0,
      y: 0,
      ...latestPointer?.v
    }
  }
}
