# Source: sceneObjs/other/Drawing.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 BaseSceneObj from '../BaseSceneObj.js';
    import i18next from 'i18next';
    import geometry from '../../geometry.js';

    /**
     * Drawing tool
     *
     * Tools -> Other -> Drawing
     * @class
     * @extends BaseSceneObj
     * @memberof sceneObjs
     * @property {Array<Array<number>>} strokes - The strokes of the drawing. Each element represents a stroke, which is an array of coordinates ordered as `[x1, y1, x2, y2, ...]`. The coordinates are rounded to reduce the size of the JSON data.
     * @property {boolean} isDrawing - Whether the user is drawing (before "stop drawing" is clicked).
     * @property {boolean} isMouseDown - Temperary indication of whether the mouse is down (during the drawing stage).
     */
    class Drawing extends BaseSceneObj {
      static type = 'Drawing';
      static serializableDefaults = {
        strokes: [],
        isDrawing: false
      };

      populateObjBar(objBar) {
        objBar.setTitle(i18next.t('main:tools.Drawing.title'));
        if (this.isDrawing) {
          objBar.createButton(i18next.t('simulator:sceneObjs.Drawing.finishDrawing'), function (obj) {
            obj.isDrawing = false;
            if (obj.scene.editor.isConstructing) {
              obj.scene.editor.isConstructing = false;
            }
          }, true);
        }
      }

      draw(canvasRenderer, isAboveLight, isHovered) {
        const ctx = canvasRenderer.ctx;
        const ls = canvasRenderer.lengthScale;

        ctx.strokeStyle = isHovered ? 'cyan' : 'white';
        ctx.lineWidth = 1 * ls;
        ctx.beginPath();
        for (const stroke of this.strokes) {
          ctx.moveTo(stroke[0], stroke[1]);
          for (let i = 2; i < stroke.length; i += 2) {
            ctx.lineTo(stroke[i], stroke[i + 1]);
          }
        }
        ctx.stroke();
      }

      move(diffX, diffY) {
        let roundedDiffX = this.round(diffX);
        let roundedDiffY = this.round(diffY);
        for (const stroke of this.strokes) {
          for (let i = 0; i < stroke.length; i += 2) {
            stroke[i] += roundedDiffX;
            stroke[i + 1] += roundedDiffY;
          }
        }
      }

      onConstructMouseDown(mouse, ctrl, shift) {
        if (!this.isDrawing) {
          // Initialize the drawing
          this.isDrawing = true;
          this.strokes = [];
        }
        const mousePos = mouse.getPosSnappedToGrid();
        this.strokes.push([this.round(mousePos.x), this.round(mousePos.y)]);
        this.isMouseDown = true;
      }

      onConstructMouseMove(mouse, ctrl, shift) {
        const mousePos = mouse.getPosSnappedToGrid();
        if (!this.isMouseDown) return;
        if (this.strokes.length === 0 || this.strokes[this.strokes.length - 1].length < 2) return;

        const distanceSq = (this.strokes[this.strokes.length - 1][this.strokes[this.strokes.length - 1].length - 2] - mousePos.x) ** 2 + (this.strokes[this.strokes.length - 1][this.strokes[this.strokes.length - 1].length - 1] - mousePos.y) ** 2;

        if (distanceSq < 4 * this.scene.lengthScale * this.scene.lengthScale) return;

        this.strokes[this.strokes.length - 1].push(this.round(mousePos.x), this.round(mousePos.y));
      }

      onConstructMouseUp(mouse, ctrl, shift) {
        this.isMouseDown = false;
      }

      onConstructUndo() {
        if (this.strokes.length < 2) {
          return {
            isCancelled: true
          }
        } else {
          this.strokes.pop();
        }
      }

      checkMouseOver(mouse) {
        let dragContext = {};
        for (const stroke of this.strokes) {
          for (let i = 0; i < stroke.length - 2; i += 2) {
            if (mouse.isOnSegment(geometry.line(geometry.point(stroke[i], stroke[i + 1]), geometry.point(stroke[i + 2], stroke[i + 3])))) {
              const mousePos = mouse.getPosSnappedToGrid();
              const roundedMousePos = geometry.point(this.round(mousePos.x), this.round(mousePos.y));
              dragContext.part = 0;
              dragContext.mousePos0 = roundedMousePos; // Mouse position when the user starts dragging
              dragContext.mousePos1 = roundedMousePos; // Mouse position at the last moment during dragging
              dragContext.snapContext = {};
              return dragContext;
            }
          }
        }
      }

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

        const roundedMousePos = geometry.point(this.round(mousePos.x), this.round(mousePos.y));

        var mouseDiffX = dragContext.mousePos1.x - roundedMousePos.x; // The X difference between the mouse position now and at the previous moment
        var mouseDiffY = dragContext.mousePos1.y - roundedMousePos.y; // The Y difference between the mouse position now and at the previous moment

        if (dragContext.part == 0) {
          for (const stroke of this.strokes) {
            for (let i = 0; i < stroke.length; i += 2) {
              stroke[i] -= mouseDiffX;
              stroke[i + 1] -= mouseDiffY;
            }
          }
        }

        // Update the mouse position
        dragContext.mousePos1 = roundedMousePos;
      }

      /* Utility functions */

      /**
       * Round the coordinates of the strokes to integers times the length scale (to reduce the size of the JSON data).
       */
      round(num) {
        return Math.round(num / this.scene.lengthScale) * this.scene.lengthScale;
      }
    };

    export default Drawing;