# Source: Mouse.js

    /*
     * Copyright 2024 The Ray Optics Simulation authors and contributors
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    import geometry from './geometry.js';

    /**
     * The Mouse class encapsulates the state of the mouse within the scene.
     * @class
     */
    class Mouse {
      static CLICK_EXTENT_LINE = 10;
      static CLICK_EXTENT_POINT = 10;
      static TOUCHSCREEN_EXTENT_RATIO = 2;
      static SNAP_TO_DIRECTION_LOCK_LIMIT = 30;

      /**
       * Create a Mouse object.
       * @param {geometry.point} pos - The position of the mouse within the scene.
       * @param {Scene} scene - The scene in which the mouse operates.
       * @param {boolean} isTouch - Indicates whether the mouse input comes from a touch screen.
       * @param {number} overrideGrid - Indicates how the grid is overridden (0: not overridden, 1: invert the on/off of the grid, 2: always off).
       */
      constructor(pos, scene, isTouch = false, overrideGrid = 0) {
        this.pos = pos;
        this.scene = scene;
        this.isTouch = isTouch;
        this.overrideGrid = overrideGrid;
      }

      /**
       * Calculates the effective click extent based on whether the target is a point or a line,
       * and adjusts for touchscreen input if applicable.
       * @param {boolean} isPoint - Specifies whether the target is a point (true) or a line (false).
       * @returns {number} The calculated click extent.
       */
      getClickExtent(isPoint) {
        let clickExtent;
        if (isPoint) {
          clickExtent = Mouse.CLICK_EXTENT_POINT / this.scene.scale;
        } else {
          clickExtent = Mouse.CLICK_EXTENT_LINE / this.scene.scale;
        }
        if (this.isTouch) {
          clickExtent *= Mouse.TOUCHSCREEN_EXTENT_RATIO;
        }
        return clickExtent;
      }

      /**
       * Determines if the mouse is currently over a given point.
       * @param {geometry.point} point - The point to check against the mouse position.
       * @returns {boolean} True if the mouse is on the given point, false otherwise.
       */
      isOnPoint(point) {
        return geometry.distanceSquared(this.pos, point) < this.getClickExtent(true) * this.getClickExtent(true);
      }

      /**
       * Determines if the snapped mouse is currently over a given point.
       * @param {geometry.point} point - The point to check against the snapped mouse position.
       * @returns {boolean} True if the snapped mouse is on the given point, false otherwise.
       */
      snapsOnPoint(point) {
        return geometry.distanceSquared(this.getPosSnappedToGrid(), point) < this.getClickExtent(true) * this.getClickExtent(true);
      }

      /**
       * Determines if the mouse is currently over a given line segment.
       * @param {geometry.line} segment - The line segment to check against the mouse position.
       * @returns {boolean} True if the mouse is on the given line segment, false otherwise.
       */
      isOnSegment(segment) {
        var d_per = Math.pow((this.pos.x - segment.p1.x) * (segment.p1.y - segment.p2.y) + (this.pos.y - segment.p1.y) * (segment.p2.x - segment.p1.x), 2) / ((segment.p1.y - segment.p2.y) * (segment.p1.y - segment.p2.y) + (segment.p2.x - segment.p1.x) * (segment.p2.x - segment.p1.x)); // Similar to the distance between the mouse and the line
        var d_par = (segment.p2.x - segment.p1.x) * (this.pos.x - segment.p1.x) + (segment.p2.y - segment.p1.y) * (this.pos.y - segment.p1.y); // Similar to the projected point of the mouse on the line
        return d_per < this.getClickExtent() * this.getClickExtent() && d_par >= 0 && d_par <= geometry.segmentLengthSquared(segment);
      }

      /**
       * Determines if the mouse is currently over a given line.
       * @param {geometry.line} line - The line to check against the mouse position.
       * @returns {boolean} True if the mouse is on the given line, false otherwise.
       */
      isOnLine(line) {
        var d_per = Math.pow((this.pos.x - line.p1.x) * (line.p1.y - line.p2.y) + (this.pos.y - line.p1.y) * (line.p2.x - line.p1.x), 2) / ((line.p1.y - line.p2.y) * (line.p1.y - line.p2.y) + (line.p2.x - line.p1.x) * (line.p2.x - line.p1.x)); // Similar to the distance between the mouse and the line
        return d_per < this.getClickExtent() * this.getClickExtent();
      }

      /**
       * Get the mouse position snapped to the grid if the grid is enabled.
       * @returns {geometry.point} The mouse position snapped to the grid.
       */
      getPosSnappedToGrid() {
        //console.log(this.overrideGrid)
        if ((this.overrideGrid === 0 && this.scene.snapToGrid) || (this.overrideGrid === 1 && !this.scene.snapToGrid)) {
          return geometry.point(
            Math.round(this.pos.x / this.scene.gridSize) * this.scene.gridSize,
            Math.round(this.pos.y / this.scene.gridSize) * this.scene.gridSize
          );
        } else {
          return this.pos;
        }
      }

      /**
       * Get the mouse position snapped to a set of suggested directions.
       * @param {geometry.point} basePoint - The base point for the snapping.
       * @param {Array} directions - The directions to snap to, each a vector with x and y components.
       * @param {Object} snapContext - The object storing the internal state of the snapping process.
       * @returns {geometry.point} The mouse position snapped to some direction.
       */
      getPosSnappedToDirection(basePoint, directions, snapContext) {
        const pos = this.getPosSnappedToGrid();
        var x = pos.x - basePoint.x;
        var y = pos.y - basePoint.y;

        if (snapContext && snapContext.locked) {
          // The snap has been locked
          var k = (directions[snapContext.i0].x * x + directions[snapContext.i0].y * y) / (directions[snapContext.i0].x * directions[snapContext.i0].x + directions[snapContext.i0].y * directions[snapContext.i0].y);
          return geometry.point(basePoint.x + k * directions[snapContext.i0].x, basePoint.y + k * directions[snapContext.i0].y);
        } else {
          var i0;
          var d_sq;
          var d0_sq = Infinity;
          for (var i = 0; i < directions.length; i++) {
            d_sq = (directions[i].y * x - directions[i].x * y) * (directions[i].y * x - directions[i].x * y) / (directions[i].x * directions[i].x + directions[i].y * directions[i].y);
            if (d_sq < d0_sq) {
              d0_sq = d_sq;
              i0 = i;
            }
          }

          if (snapContext && x * x + y * y > Mouse.SNAP_TO_DIRECTION_LOCK_LIMIT * Mouse.SNAP_TO_DIRECTION_LOCK_LIMIT / this.scene.scale / this.scene.scale) {
            // lock the snap
            snapContext.locked = true;
            snapContext.i0 = i0;
          }

          var k = (directions[i0].x * x + directions[i0].y * y) / (directions[i0].x * directions[i0].x + directions[i0].y * directions[i0].y);
          return geometry.point(basePoint.x + k * directions[i0].x, basePoint.y + k * directions[i0].y);
        }
      }
    }

    export default Mouse;