/**

    * @class

    * @description Une librairie qui permet de créer un canvas sur lequel dessiner avec la souris ou avec le doigt (sur mobile). Plusieurs instances peuvent cohabiter sur la même page.

    * @author TIMMatane

    * @version 1.0.0 (Janvier 2020)

    */

class SimpleCanvasJS {
  /**

     * @description Une librairie qui permet de créer un canvas sur lequel dessiner avec la souris ou avec le doigt (sur mobile). Plusieurs instances peuvent cohabiter sur la même page.

     * @param {number} width La largeur du canvas en pixels

     * @param {number} height La hauteur du canvas en pixels

     * @param {HTMLElement} container Le conteneur dans lequel ajouter le canvas

     * @param {string | null} [fillColor = null] La couleur de remplissage. La valeur null défini le fond comme transparent

     * @param {string} [brushColor = #000000] La couleur de la brosse

     * @param {number}[brushSize = 1] La dimension de la brosse

     * @returns {SimpleCanvasJS} La référence au SimpleCanvasJS créé

     * @example <caption>1) Créer un canvas de 250X250 avec remplissage en blanc, trait en rouge et d'une grosseur de 5 pixels et la placer dans un conteneur</caption>

     *let dessin = new SimpleCanvasJS(250, 250, document.querySelector("#canvasDiv"), "#ffffff", "#ff0000", 5);

     *dessin.brush("#00ff00", 5); //changer la brosse par défaut

     *dessin.fill("#ff0000"); //remplir le canvas d'une couleur

     */

  constructor(width, height, container = document.body, fillColor = null, brushColor = "#000000", brushSize = 1) {
    //dimensions

    this.width = width;

    this.height = height;

    /**

       * @member SimpleCanvasJS#canvas

       * @type {HTMLCanvasElement}

       * @description Référence a l'élément canvas de SimpleCanvasJS

       */

    this.canvas = document.createElement("canvas");

    this.canvas.setAttribute("width", this.width);

    this.canvas.setAttribute("height", this.height);

    //ajout de la classe de base d'un SimpleCanvasJS

    this.canvas.classList.add("SimpleCanvasJS");

    /**

       * @member SimpleCanvasJS#context

       * @type {CanvasRenderingContext2D}

       * @description Référence au contexte 2D du canvas

       */

    this.context = this.canvas.getContext("2d");

    /**

      * @member SimpleCanvasJS#fillColor

      * @type {string | null}

      * @description Couleur hexadecimale de remplissage

      */

    this.fillColor = fillColor;

    if (this.fillColor) {
      this.fill(this.fillColor);
    }

    this.brushColor = brushColor;

    this.brushSize = brushSize;

    //apliquer la brosse par défaut

    this.brush(this.brushColor, this.brushSize);

    this.#setupEvents();

    this.lastXPos = 0;

    this.lastYpos = 0;

    this.drawing = false;

    //ajout du canvas dans son conteneur

    container.appendChild(this.canvas);
  }

  /**

     * @description Change la brosse utilisée

     * @param {string} brushColor La couleur de la brosse

     * @param {number} brushSize La dimension de la brosse

     */

  brush(brushColor, brushSize) {
    this.brushColor = brushColor;

    this.brushSize = brushSize;

    this.context.lineWidth = brushSize;

    this.context.strokeStyle = brushColor;
  }

  /**

     * @description Remplir l'arrière pland du canvas d'une couleur

     * @param {string} [fillColor = this.fillColor] La couleur voulue

     */

  fill(fillColor = this.fillColor) {
    this.fillColor = fillColor;

    this.context.fillStyle = fillColor;

    this.context.fillRect(0, 0, this.width, this.height);
  }

  /**

     * @description Vider la canvas de son contenu

     * @param {boolean} [refill = true] Est-ce qu'une couleur de fond doit être utilisée après avoir vidé le canvas?

     */

  clear(refill = true) {
    this.context.clearRect(0, 0, this.width, this.height);

    if (refill) {
      this.fill(this.fillColor);
    }
  }

  /**

     * @description Sauvegarde le contenu du canvas  

     * @param {HTMLImageElement} imageElement L'image devant afficher le contenu sauvegardé (base64)

     * @param {Function} loadCallback La fonction devant être appelée lorsque le chargement de l'image est terminé

     * 

     *@example <caption>1) Sauvegarde du contenu du canvas et placement dans une image</caption>

    * let dessin = new SimpleCanvasJS(250, 250, document.querySelector("#canvasDiv"), "#ffffff", "#ff0000", 5);

    * dessin.save(document.querySelector('img'))

    *

    * @example <caption>2) Sauvegarde du contenu du canvas et placement dans une image avec fonction appelée lorsque tout est chargé</caption>

    * let dessin = new SimpleCanvasJS(250, 250, document.querySelector("#canvasDiv"), "#ffffff", "#ff0000", 5);

    * dessin.save(document.querySelector('img'), function(){

    *    console.log("Chargement de l'image complété");

    * })

    * 

     * @returns {string | undefined} Retourne l'image en chaîne de caractère base64 ou undefined si une image est passée en paramètre

     */

  save(imageElement, loadCallback) {
    if (imageElement) {
      imageElement.onload = loadCallback || null;

      imageElement.src = this.canvas.toDataURL();

      imageElement.width = this.width;

      imageElement.height = this.height;
    } else {
      return this.canvas.toDataURL();
    }
  }

  /**

     * @description Détruit l'instance SimpleCanvasJS créé

     * 

     * <br><br><em><b>Note</b>: Afin de détruire le canvas, la variable de l'instance devrait aussi être nullifiée.</em>

    * @example <caption>Destruction d'un SimpleCanvasJS associé à une variable</caption>

    * dessin.destroy();

    * dessin = null //nullifie l'instance

    *

    * //ou en une seule ligne

    *

    * dessin = dessin.destroy()

    * @returns null

     */

  destroy() {
    //enlever le canvas du DOM

    this.canvas.parentNode.removeChild(this.canvas);

    this.brush = this.#destroyed;

    this.fill = this.#destroyed;

    this.clear = this.#destroyed;

    this.save = this.#destroyed;

    delete this;

    return null;
  }

  /*******************************************************************

    *************************** PRIVATES **************************

    *******************************************************************/

  #setupEvents() {
    this.#pointerDown = this.#onPointerDown.bind(this);

    this.#pointerMove = this.#onPointerMove.bind(this);

    this.#pointerUp = this.#onPointerUp.bind(this);

    this.#pointerMoveOutside = this.#onPointerMoveOutside.bind(this);

    this.#pointerUpOutside = this.#onPointerUpOutside.bind(this);

    this.canvas.onmousedown = this.canvas.ontouchstart = this.#pointerDown;

    //  this.canvas.addEventListener("touchstart", this.#pointerDown, { passive: false });
  }

  #pointerDown = null;

  #onPointerDown(e) {
    this.drawing = true;

    let penXPos = e.type == "touchstart" ? e.touches[0].pageX : e.pageX;

    let penYPos = e.type == "touchstart" ? e.touches[0].pageY : e.pageY;

    //   document.body.classList.add("noscroll");

    this.lastXPos = penXPos - this.canvas.offsetLeft;

    this.lastYPos = penYPos - this.canvas.offsetTop;

    document.body.onmousemove = document.body.ontouchmove = this.#pointerMoveOutside;

    this.canvas.onmousemove = this.canvas.ontouchmove = this.#pointerMove;

    this.canvas.onmouseup = this.canvas.ontouchend = this.#pointerUp;

    document.body.onmouseup = document.body.ontouchend = this.#pointerUpOutside;

    if (e.type == "touchstart") {
      e.stopPropagation();

      e.preventDefault();
    }
  }

  #pointerMove = null;

  #onPointerMove(e) {
    if (this.drawing) {
      this.context.beginPath();

      this.context.moveTo(this.lastXPos, this.lastYPos);

      let penXPos = e.type == "touchmove" ? e.touches[0].pageX : e.pageX;

      let penYPos = e.type == "touchmove" ? e.touches[0].pageY : e.pageY;

      let xPos = (this.lastXPos = penXPos - this.canvas.offsetLeft);

      let yPos = (this.lastYPos = penYPos - this.canvas.offsetTop);

      this.context.lineTo(xPos, yPos);

      this.context.closePath();

      this.context.stroke();

      if (e.type == "touchmove") {
        e.stopPropagation();

        e.preventDefault();
      }
    }
  }

  #pointerMoveOutside = null;

  #onPointerMoveOutside(e) {
    if (this.drawing) {
      if (e.target != this.canvas) {
        this.canvas.onmousemove = this.canvas.ontouchmove = null;

        document.body.onmousemove = document.body.ontouchmove = null;
      }
    }

    if (e.type == "touchmove") {
      e.stopPropagation();

      e.preventDefault();
    }
  }

  #pointerUp = null;

  #onPointerUp(e) {
    this.drawing = false;

    // document.body.classList.remove("noscroll");

    this.canvas.onmousemove = this.canvas.ontouchmove = null;

    document.body.onmousemove = document.body.ontouchmove = null;

    this.canvas.onmouseup = this.canvas.ontouchend = null;
  }

  #pointerUpOutside = null;

  #onPointerUpOutside(e) {
    this.drawing = false;

    //document.body.classList.remove("noscroll");

    this.canvas.onmousemove = this.canvas.ontouchmove = null;

    document.body.onmousemove = document.body.ontouchmove = null;

    this.canvas.onmouseup = this.canvas.ontouchend = null;
  }

  #destroyed() {
    console.warn("Le canvas a été détruit. Il est impossible de l'utiliser.");
  }
}

export default SimpleCanvasJS;