# Source: sceneObjs/CircleObjMixin.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';
    import Simulator from '../Simulator.js';

    /**
     * The mixin for the scene objects that are defined by a circle.
     * @template {typeof BaseSceneObj} T
     * @param {T} Base
     * @returns {T}
     */
    const CircleObjMixin = Base => class extends Base {

      move(diffX, diffY) {
        // Move the first point
        this.p1.x = this.p1.x + diffX;
        this.p1.y = this.p1.y + diffY;
        // Move the second point
        this.p2.x = this.p2.x + diffX;
        this.p2.y = this.p2.y + diffY;
      }

      onConstructMouseDown(mouse, ctrl, shift) {
        if (!this.constructionPoint) {
          // Initialize the construction stage.
          this.constructionPoint = mouse.getPosSnappedToGrid();
          this.p1 = this.constructionPoint;
          this.p2 = this.constructionPoint;
        }
        if (shift) {
          this.p2 = mouse.getPosSnappedToDirection(this.constructionPoint, [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: -1 }]);
        } else {
          this.p2 = mouse.getPosSnappedToGrid();
        }
      }

      onConstructMouseMove(mouse, ctrl, shift) {
        if (shift) {
          this.p2 = mouse.getPosSnappedToDirection(this.constructionPoint, [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: -1 }]);
        } else {
          this.p2 = mouse.getPosSnappedToGrid();
        }

        this.p1 = this.constructionPoint;
      }

      onConstructMouseUp(mouse, ctrl, shift) {
        if (!mouse.snapsOnPoint(this.p1)) {
          delete this.constructionPoint;
          return {
            isDone: true
          };
        }
      }

      checkMouseOver(mouse) {
        let dragContext = {};
        if (mouse.isOnPoint(this.p1) && geometry.distanceSquared(mouse.pos, this.p1) <= geometry.distanceSquared(mouse.pos, this.p2)) {
          dragContext.part = 1;
          dragContext.targetPoint = geometry.point(this.p1.x, this.p1.y);
          return dragContext;
        }
        if (mouse.isOnPoint(this.p2)) {
          dragContext.part = 2;
          dragContext.targetPoint = geometry.point(this.p2.x, this.p2.y);
          return dragContext;
        }
        if (Math.abs(geometry.distance(this.p1, mouse.pos) - geometry.segmentLength(this)) < mouse.getClickExtent()) {
          const mousePos = mouse.getPosSnappedToGrid();
          dragContext.part = 0;
          dragContext.mousePos0 = mousePos; // Mouse position when the user starts dragging
          dragContext.mousePos1 = mousePos; // Mouse position at the last moment during dragging
          dragContext.snapContext = {};
          return dragContext;
        }
      }

      onDrag(mouse, dragContext, ctrl, shift) {
        if (dragContext.part == 1) {
          // Dragging the center point
          const basePoint = dragContext.originalObj.p2;

          this.p1 = shift ? mouse.getPosSnappedToDirection(basePoint, [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: -1 }, { x: (dragContext.originalObj.p2.x - dragContext.originalObj.p1.x), y: (dragContext.originalObj.p2.y - dragContext.originalObj.p1.y) }]) : mouse.getPosSnappedToGrid();
          this.p2 = basePoint;
        }
        if (dragContext.part == 2) {
          // Dragging the point on the circumference
          const basePoint = dragContext.originalObj.p1;

          this.p2 = shift ? mouse.getPosSnappedToDirection(basePoint, [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: -1 }, { x: (dragContext.originalObj.p2.x - dragContext.originalObj.p1.x), y: (dragContext.originalObj.p2.y - dragContext.originalObj.p1.y) }]) : mouse.getPosSnappedToGrid();
          this.p1 = basePoint;
        }
        if (dragContext.part == 0) {
          // Dragging the entire circle

          if (shift) {
            var mousePos = mouse.getPosSnappedToDirection(dragContext.mousePos0, [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: (dragContext.originalObj.p2.x - dragContext.originalObj.p1.x), y: (dragContext.originalObj.p2.y - dragContext.originalObj.p1.y) }, { x: (dragContext.originalObj.p2.y - dragContext.originalObj.p1.y), y: -(dragContext.originalObj.p2.x - dragContext.originalObj.p1.x) }], dragContext.snapContext);
          } else {
            var mousePos = mouse.getPosSnappedToGrid();
            dragContext.snapContext = {}; // Unlock the dragging direction when the user release the shift key
          }

          const mouseDiffX = dragContext.mousePos1.x - mousePos.x; // The X difference between the mouse position now and at the previous moment
          const mouseDiffY = dragContext.mousePos1.y - mousePos.y; // The Y difference between the mouse position now and at the previous moment The Y difference between the mouse position now and at the previous moment
          // Move the center point
          this.p1.x = this.p1.x - mouseDiffX;
          this.p1.y = this.p1.y - mouseDiffY;
          // Move the point on the circumference
          this.p2.x = this.p2.x - mouseDiffX;
          this.p2.y = this.p2.y - mouseDiffY;
          // Update the mouse position
          dragContext.mousePos1 = mousePos;
        }
      }

      /**
       * Check if a ray intersects the circle.
       * In the child class, this can be called from the `checkRayIntersects` method.
       * @param {Ray} ray - The ray.
       * @returns {Point} The intersection point, or null if there is no intersection.
       */
      checkRayIntersectsShape(ray) {
        const rp_temp = geometry.lineCircleIntersections(geometry.line(ray.p1, ray.p2), geometry.circle(this.p1, this.p2));
        const rp_exist = [];
        const rp_lensq = [];
        for (let i = 1; i <= 2; i++) {
          rp_exist[i] = geometry.intersectionIsOnRay(rp_temp[i], ray) && geometry.distanceSquared(rp_temp[i], ray.p1) > Simulator.MIN_RAY_SEGMENT_LENGTH_SQUARED * this.scene.lengthScale * this.scene.lengthScale;
          rp_lensq[i] = geometry.distanceSquared(ray.p1, rp_temp[i]);
        }

        if (rp_exist[1] && ((!rp_exist[2]) || rp_lensq[1] < rp_lensq[2])) {
          return rp_temp[1];
        }
        if (rp_exist[2] && ((!rp_exist[1]) || rp_lensq[2] < rp_lensq[1])) {
          return rp_temp[2];
        }
      }
    };

    export default CircleObjMixin;