/**
 * @module Structure
 * @submodule Structure
 * @for p5
 * @requires core
 */

function structure(p5, fn){
  /**
   * Stops the code in <a href="#/p5/draw">draw()</a> from running repeatedly.
   *
   * By default, <a href="#/p5/draw">draw()</a> tries to run 60 times per
   * second. Calling `noLoop()` stops <a href="#/p5/draw">draw()</a> from
   * repeating. The draw loop can be restarted by calling
   * <a href="#/p5/loop">loop()</a>. <a href="#/p5/draw">draw()</a> can be run
   * once by calling <a href="#/p5/redraw">redraw()</a>.
   *
   * The <a href="#/p5/isLooping">isLooping()</a> function can be used to check
   * whether a sketch is looping, as in `isLooping() === true`.
   *
   * @method noLoop
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   // Turn off the draw loop.
   *   noLoop();
   *
   *   describe('A white half-circle on the left edge of a gray square.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Calculate the circle's x-coordinate.
   *   let x = frameCount;
   *
   *   // Draw the circle.
   *   // Normally, the circle would move from left to right.
   *   circle(x, 50, 20);
   * }
   *
   * @example
   * // Double-click to stop the draw loop.
   *
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   // Slow the frame rate.
   *   frameRate(5);
   *
   *   describe('A white circle moves randomly on a gray background. It stops moving when the user double-clicks.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Calculate the circle's coordinates.
   *   let x = random(0, 100);
   *   let y = random(0, 100);
   *
   *   // Draw the circle.
   *   // Normally, the circle would move from left to right.
   *   circle(x, y, 20);
   * }
   *
   * // Stop the draw loop when the user double-clicks.
   * function doubleClicked() {
   *   noLoop();
   * }
   *
   * @example
   * let startButton;
   * let stopButton;
   *
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   // Create the button elements and place them
   *   // beneath the canvas.
   *   startButton = createButton('▶');
   *   startButton.position(0, 100);
   *   startButton.size(50, 20);
   *   stopButton = createButton('◾');
   *   stopButton.position(50, 100);
   *   stopButton.size(50, 20);
   *
   *   // Set functions to call when the buttons are pressed.
   *   startButton.mousePressed(loop);
   *   stopButton.mousePressed(noLoop);
   *
   *   // Slow the frame rate.
   *   frameRate(5);
   *
   *   describe(
   *     'A white circle moves randomly on a gray background. Play and stop buttons are shown beneath the canvas. The circle stops or starts moving when the user presses a button.'
   *   );
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Calculate the circle's coordinates.
   *   let x = random(0, 100);
   *   let y = random(0, 100);
   *
   *   // Draw the circle.
   *   // Normally, the circle would move from left to right.
   *   circle(x, y, 20);
   * }
   */
  fn.noLoop = function() {
    this._loop = false;
  };

  /**
   * Resumes the draw loop after <a href="#/p5/noLoop">noLoop()</a> has been
   * called.
   *
   * By default, <a href="#/p5/draw">draw()</a> tries to run 60 times per
   * second. Calling <a href="#/p5/noLoop">noLoop()</a> stops
   * <a href="#/p5/draw">draw()</a> from repeating. The draw loop can be
   * restarted by calling `loop()`.
   *
   * The <a href="#/p5/isLooping">isLooping()</a> function can be used to check
   * whether a sketch is looping, as in `isLooping() === true`.
   *
   * @method loop
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   // Turn off the draw loop.
   *   noLoop();
   *
   *   describe(
   *     'A white half-circle on the left edge of a gray square. The circle starts moving to the right when the user double-clicks.'
   *   );
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Calculate the circle's x-coordinate.
   *   let x = frameCount;
   *
   *   // Draw the circle.
   *   circle(x, 50, 20);
   * }
   *
   * // Resume the draw loop when the user double-clicks.
   * function doubleClicked() {
   *   loop();
   * }
   *
   * @example
   * let startButton;
   * let stopButton;
   *
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   // Create the button elements and place them
   *   // beneath the canvas.
   *   startButton = createButton('▶');
   *   startButton.position(0, 100);
   *   startButton.size(50, 20);
   *   stopButton = createButton('◾');
   *   stopButton.position(50, 100);
   *   stopButton.size(50, 20);
   *
   *   // Set functions to call when the buttons are pressed.
   *   startButton.mousePressed(loop);
   *   stopButton.mousePressed(noLoop);
   *
   *   // Slow the frame rate.
   *   frameRate(5);
   *
   *   describe(
   *     'A white circle moves randomly on a gray background. Play and stop buttons are shown beneath the canvas. The circle stops or starts moving when the user presses a button.'
   *   );
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Calculate the circle's coordinates.
   *   let x = random(0, 100);
   *   let y = random(0, 100);
   *
   *   // Draw the circle.
   *   // Normally, the circle would move from left to right.
   *   circle(x, y, 20);
   * }
   */
  fn.loop = function() {
    if (!this._loop) {
      this._loop = true;
      if (this._setupDone) {
        this._draw();
      }
    }
  };

  /**
   * Returns `true` if the draw loop is running and `false` if not.
   *
   * By default, <a href="#/p5/draw">draw()</a> tries to run 60 times per
   * second. Calling <a href="#/p5/noLoop">noLoop()</a> stops
   * <a href="#/p5/draw">draw()</a> from repeating. The draw loop can be
   * restarted by calling <a href="#/p5/loop">loop()</a>.
   *
   * The `isLooping()` function can be used to check whether a sketch is
   * looping, as in `isLooping() === true`.
   *
   * @method isLooping
   * @returns {boolean}
   *
   * @example
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   describe('A white circle drawn against a gray background. When the user double-clicks, the circle stops or resumes following the mouse.');
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Draw the circle at the mouse's position.
   *   circle(mouseX, mouseY, 20);
   * }
   *
   * // Toggle the draw loop when the user double-clicks.
   * function doubleClicked() {
   *   if (isLooping() === true) {
   *     noLoop();
   *   } else {
   *     loop();
   *   }
   * }
   */
  fn.isLooping = function() {
    return this._loop;
  };

  /**
   * Runs the code in <a href="#/p5/draw">draw()</a> once.
   *
   * By default, <a href="#/p5/draw">draw()</a> tries to run 60 times per
   * second. Calling <a href="#/p5/noLoop">noLoop()</a> stops
   * <a href="#/p5/draw">draw()</a> from repeating. Calling `redraw()` will
   * execute the code in the <a href="#/p5/draw">draw()</a> function a set
   * number of times. `await` the result of `redraw` to make sure it has finished.
   *
   * The parameter, `n`, is optional. If a number is passed, as in `redraw(5)`,
   * then the draw loop will run the given number of times. By default, `n` is
   * 1.
   *
   * @method redraw
   * @param  {Integer} [n] number of times to run <a href="#/p5/draw">draw()</a>. Defaults to 1.
   * @returns {Promise<void>}
   *
   * @example
   * // Double-click the canvas to move the circle.
   *
   * let x = 0;
   *
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   // Turn off the draw loop.
   *   noLoop();
   *
   *   describe(
   *     'A white half-circle on the left edge of a gray square. The circle moves a little to the right when the user double-clicks.'
   *   );
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Draw the circle.
   *   circle(x, 50, 20);
   *
   *   // Increment x.
   *   x += 5;
   * }
   *
   * // Run the draw loop when the user double-clicks.
   * async function doubleClicked() {
   *   await redraw();
   * }
   *
   * @example
   * // Double-click the canvas to move the circle.
   *
   * let x = 0;
   *
   * function setup() {
   *   createCanvas(100, 100);
   *
   *   // Turn off the draw loop.
   *   noLoop();
   *
   *   describe(
   *     'A white half-circle on the left edge of a gray square. The circle hops to the right when the user double-clicks.'
   *   );
   * }
   *
   * function draw() {
   *   background(200);
   *
   *   // Draw the circle.
   *   circle(x, 50, 20);
   *
   *   // Increment x.
   *   x += 5;
   * }
   *
   * // Run the draw loop three times when the user double-clicks.
   * async function doubleClicked() {
   *   await redraw(3);
   * }
   */
  fn.redraw = async function(n) {
    if (this._inUserDraw || !this._setupDone) {
      return;
    }

    let numberOfRedraws = parseInt(n);
    if (isNaN(numberOfRedraws) || numberOfRedraws < 1) {
      numberOfRedraws = 1;
    }

    const context = this._isGlobal ? window : this;
    if (typeof context.draw === 'function') {
      if (typeof context.setup === 'undefined') {
        context.scale(context._pixelDensity, context._pixelDensity);
      }
      for (let idxRedraw = 0; idxRedraw < numberOfRedraws; idxRedraw++) {
        context.resetMatrix();
        if (this._accessibleOutputs.grid || this._accessibleOutputs.text) {
          this._updateAccsOutput();
        }
        if (this._renderer.isP3D) {
          this._renderer._update();
        }
        this.frameCount = context.frameCount + 1;
        await this._runLifecycleHook('predraw');
        this._inUserDraw = true;
        try {
          await context.draw();
        } finally {
          this._inUserDraw = false;
        }
        await this._runLifecycleHook('postdraw');
      }
      // Finish drawing
      await this._renderer.finishDraw?.();
    }
  };

  /**
   * Creates a new sketch in "instance" mode.
   *
   * All p5.js sketches are instances of the `p5` class. Put another way, all
   * p5.js sketches are objects with methods including `pInst.setup()`,
   * `pInst.draw()`, `pInst.circle()`, and `pInst.fill()`. By default, sketches
   * run in "global mode" to hide some of this complexity.
   *
   * In global mode, a default instance of the `p5` class is created
   * automatically. The default `p5` instance searches the web page's source
   * code for declarations of system functions such as `setup()`, `draw()`,
   * and `mousePressed()`, then attaches those functions to itself as methods.
   * Calling a function such as `circle()` in global mode actually calls the
   * default `p5` object's `pInst.circle()` method.
   *
   * It's often helpful to isolate the code within sketches from the rest of the
   * code on a web page. Two common use cases are web pages that use other
   * JavaScript libraries and web pages with multiple sketches. "Instance mode"
   * makes it easy to support both of these scenarios.
   *
   * Instance mode sketches support the same API as global mode sketches. They
   * use a function to bundle, or encapsulate, an entire sketch. The function
   * containing the sketch is then passed to the `p5()` constructor.
   *
   * The first parameter, `sketch`, is a function that contains the sketch. For
   * example, the statement `new p5(mySketch)` would create a new instance mode
   * sketch from a function named `mySketch`. The function should have one
   * parameter, `p`, that's a `p5` object.
   *
   * The second parameter, `node`, is optional. If a string is passed, as in
   * `new p5(mySketch, 'sketch-one')` the new instance mode sketch will become a
   * child of the HTML element with the id `sketch-one`. If an HTML element is
   * passed, as in `new p5(mySketch, myElement)`, then the new instance mode
   * sketch will become a child of the `Element` object called `myElement`.
   *
   * @method p5
   * @param {Object} sketch function containing the sketch.
   * @param {String|HTMLElement} node ID or reference to the HTML element that will contain the sketch.
   *
   * @example
   * // META:norender
   * // Declare the function containing the sketch.
   * function sketch(p) {
   *
   *   // Declare the setup() method.
   *   p.setup = function () {
   *     p.createCanvas(100, 100);
   *
   *     p.describe('A white circle drawn on a gray background.');
   *   };
   *
   *   // Declare the draw() method.
   *   p.draw = function () {
   *     p.background(200);
   *
   *     // Draw the circle.
   *     p.circle(50, 50, 20);
   *   };
   * }
   *
   * // Initialize the sketch.
   * new p5(sketch);
   *
   * @example
   * // META:norender
   * // Declare the function containing the sketch.
   * function sketch(p) {
   *   // Create the sketch's variables within its scope.
   *   let x = 50;
   *   let y = 50;
   *
   *   // Declare the setup() method.
   *   p.setup = function () {
   *     p.createCanvas(100, 100);
   *
   *     p.describe('A white circle moves randomly on a gray background.');
   *   };
   *
   *   // Declare the draw() method.
   *   p.draw = function () {
   *     p.background(200);
   *
   *     // Update x and y.
   *     x += p.random(-1, 1);
   *     y += p.random(-1, 1);
   *
   *     // Draw the circle.
   *     p.circle(x, y, 20);
   *   };
   * }
   *
   * // Initialize the sketch.
   * new p5(sketch);
   *
   * @example
   * // META:norender
   * // Declare the function containing the sketch.
   * function sketch(p) {
   *
   *   // Declare the setup() method.
   *   p.setup = function () {
   *     p.createCanvas(100, 100);
   *
   *     p.describe('A white circle drawn on a gray background.');
   *   };
   *
   *   // Declare the draw() method.
   *   p.draw = function () {
   *     p.background(200);
   *
   *     // Draw the circle.
   *     p.circle(50, 50, 20);
   *   };
   * }
   *
   * // Select the web page's body element.
   * let body = document.querySelector('body');
   *
   * // Initialize the sketch and attach it to the web page's body.
   * new p5(sketch, body);
   *
   * @example
   * // META:norender
   * // Declare the function containing the sketch.
   * function sketch(p) {
   *
   *   // Declare the setup() method.
   *   p.setup = function () {
   *     p.createCanvas(100, 100);
   *
   *     p.describe(
   *       'A white circle drawn on a gray background. The circle follows the mouse as the user moves.'
   *     );
   *   };
   *
   *   // Declare the draw() method.
   *   p.draw = function () {
   *     p.background(200);
   *
   *     // Draw the circle.
   *     p.circle(p.mouseX, p.mouseY, 20);
   *   };
   * }
   *
   * // Initialize the sketch.
   * new p5(sketch);
   *
   * @example
   * // META:norender
   * // Declare the function containing the sketch.
   * function sketch(p) {
   *
   *   // Declare the setup() method.
   *   p.setup = function () {
   *     p.createCanvas(100, 100);
   *
   *     p.describe(
   *       'A white circle drawn on a gray background. The circle follows the mouse as the user moves. The circle becomes black when the user double-clicks.'
   *     );
   *   };
   *
   *   // Declare the draw() method.
   *   p.draw = function () {
   *     p.background(200);
   *
   *     // Draw the circle.
   *     p.circle(p.mouseX, p.mouseY, 20);
   *   };
   *
   *   // Declare the doubleClicked() method.
   *   p.doubleClicked = function () {
   *     // Change the fill color when the user double-clicks.
   *     p.fill(0);
   *   };
   * }
   *
   * // Initialize the sketch.
   * new p5(sketch);
   */
}

if(typeof p5 !== 'undefined'){
  structure(p5, p5.prototype);
}

export { structure as default };
