Source: vga.flows.js

/**
 * Clase gestora del flujo de ejecución del algoritmo
 */
class __FlowManager {
  #steps;
  #actualStep;
  #stepCount;
  #stepIndex;
  #lineaActual;
  #stepInitialized;
  #canBack;
  #canNext;
  #playing;
  #player;

  /**
   * Constructor por defecto
   * @constructor
   */
  constructor() {
    this.steps = new Array(0);
    this.actualStep = {
      backward: new Array(0),
      execution: new Array(0),
      exit: new Array(0),
    };
    this.stepCount = 0;
    this.stepIndex = 0;
    this.lineaActual = -1;
    this.stepInitialized = false;
    this.canBack = false;
    this.canNext = false;
    this.playing = false;
  }

  /**
   * Inicializa el gestor
   */
  startFlow() {
    this.steps = new Array(0);
    this.stepCount = 0;
    this.stepIndex = 0;
    this.stepInitialized = false;
  }

  /**
   * Finaliza el gestor
   */
  finishFlow() {
    this.stepCount = this.steps.length;
    this.stepIndex = 0;
    this.canBack = false;
    this.canNext = true;
    this.steps[this.stepIndex].execution.forEach((el) =>
      this.executeAction(el)
    );
  }

  /**
   * Crea un nuevo paso en el gestor
   */
  startStep() {
    if (this.stepInitialized) return;

    this.actualStep = {
      backward: new Array(0),
      execution: new Array(0),
      exit: new Array(0),
    };
    this.stepInitialized = true;
  }

  /**
   * Finaliza el paso creado
   */
  finishStep() {
    if (!this.stepInitialized) return;
    this.steps.push(this.actualStep);
    this.stepInitialized = false;
  }

  /**
   * Vuelve al primer paso del algoritmo
   */
  reset() {
    for (var i = this.stepIndex; i > 0; i--) {
      this.steps[i].backward.forEach((el) => this.executeAction(el));
      this.steps[i].exit.forEach((el) => this.executeAction(el));
    }
    this.stepIndex = 0;
    this.steps[this.stepIndex].execution.forEach((el) =>
      this.executeAction(el)
    );
  }

  /**
   * Retrocede un paso en el algotimo
   */
  back() {
    if (this.stepIndex <= 0) return;

    this.steps[this.stepIndex].backward.forEach((el) => this.executeAction(el));
    this.steps[this.stepIndex].exit.forEach((el) => this.executeAction(el));

    this.stepIndex--;

    this.steps[this.stepIndex].execution.forEach((el) =>
      this.executeAction(el)
    );
  }

  /**
   * Avanza un paso en el algoritmo
   */
  next() {
    if (this.stepIndex >= this.stepCount - 1) return;

    this.steps[this.stepIndex].exit.forEach((el) => this.executeAction(el));

    this.stepIndex++;

    this.steps[this.stepIndex].execution.forEach((el) =>
      this.executeAction(el)
    );
  }

  /**
   * Va al último paso en el algoritmo
   */
  last() {
    for (var i = this.stepIndex; i < this.stepCount - 1; i++) {
      this.steps[i].execution.forEach((el) => this.executeAction(el));
      this.steps[i].exit.forEach((el) => this.executeAction(el));
    }
    this.stepIndex = this.stepCount - 1;
    this.steps[this.stepIndex].execution.forEach((el) =>
      this.executeAction(el)
    );
  }

  /**
   * Alterna el avance automático de pasos
   */
  trogglePlay() {
    if (!this.playing) {
      this.playing = true;
      this.player = setInterval(() => {
        if (this.stepIndex >= this.stepCount - 1) {
          $("#player").attr("src", "/images/iconos/play.svg");
          clearTimeout(this.player);
          return;
        }
        this.next();
      }, 1000);
      $("#player").attr("src", "/images/iconos/pause.svg");
    } else {
      this.playing = false;
      if (this.player != undefined) {
        clearInterval(this.player);
      }
      $("#player").attr("src", "/images/iconos/play.svg");
    }
  }

  /**
   * Acción de seleccionar una línea de código
   * @param {number} line Línea a seleccionar
   */
  static selectLine(line) {
    var id = "#lineaCodigo" + line;

    $(id).removeClass("codigo").addClass("codigoSeleccionado");
  }

  /**
   * Acción de deseleccionar una línea de código
   * @param {number} line
   */
  static unselectLine(line) {
    var id = "#lineaCodigo" + line;

    $(id).removeClass("codigoSeleccionado").addClass("codigo");
  }

  /**
   * Añade una nueva acción que se ejecutará al entrar al paso
   * @param {action} action Acción a añadir
   */
  includeAction(action) {
    this.actualStep.execution.push(action);
  }

  /**
   * Añade una nueva acción que se ejecutará al entrar volver al paso anterior
   * @param {action} action Acción a añadir
   */
  includeBackAction(action) {
    this.actualStep.backward.push(action);
  }

  /**
   * Añade una nueva acción que se ejecutará al salir del paso hacia adelante
   * @param {action} action Acción a añadir
   */
  includeExitAction(action) {
    this.actualStep.exit.push(action);
  }

  /**
   * Valor que se setará en la entrada del paso para la celda de la tabla indicada
   * @param {string} table Nombre de la tabla
   * @param {number} row Número de la fila
   * @param {number} column Número de la columna
   * @param {object} value Nuevo valor
   */
  newCellValue(table, row, column, value) {
    this.includeAction({
      action: "cellValue",
      values: [table, row, column, value],
    });
  }

  /**
   * Valor que se seteará cuando se vuelva para el paso anterior del paso actual para la celda de la tabla indicada
   * @param {string} table Nombre de la tabla
   * @param {number} row Número de la fila
   * @param {number} column Número de la columna
   * @param {object} value Valor viejo
   */
  oldCellValue(table, row, column, value) {
    this.includeBackAction({
      action: "cellValue",
      values: [table, row, column, value],
    });
  }

  /**
   * Selecciona la línea de código en la entrada del paso
   * @param {number} line Número de línea
   */
  selectSourceLine(line) {
    this.includeExitAction({ action: "unselectSourceLine", values: [line] });
    this.includeAction({ action: "selectSourceLine", values: [line] });
  }

  /**
   * Selecciona para ese paso la celda de la tabla indicada
   * @param {string} table Nombre de la tabla
   * @param {number} row Número de la fila
   * @param {number} column Número de la columna
   */
  selectCell(table, row, column) {
    this.includeExitAction({
      action: "unselectCell",
      values: [table, row, column],
    });
    this.includeAction({ action: "selectCell", values: [table, row, column] });
  }

  /**
   * Selecciona una celda permanentemente de la tabla indicada
   * @param {string} table Nombre de la tabla
   * @param {number} row Número de la fila
   * @param {number} column Número de la columna
   */
  selectCellPermanent(table, row, column) {
    this.includeBackAction({
      action: "unselectCell",
      values: [table, row, column],
    });
    this.includeAction({ action: "selectCell", values: [table, row, column] });
  }

  /**
   * Valor que se seteará cuando se entre en el paso para la variable indicada
   * @param {string} name Nombre de la variable
   * @param {object} value Valor nuevo
   */
  newResultValue(name, value) {
    this.includeAction({ action: "resultValue", values: [name, value] });
  }

  /**
   * Valor que se seteará cuando se vuelva para el paso anterior para una variable dada
   * @param {string} name Nombre de la variable
   * @param {object} value Valor antiguo
   */
  oldResultValue(name, value) {
    this.includeBackAction({ action: "resultValue", values: [name, value] });
  }

  /**
   * Aplica el estilo al elemento del grafo indicado al entrar en el paso
   * @param {string} graph Nombre del grafo
   * @param {string} elementId Identificador de elemento o grupo
   * @param {object} style Estilo a aplicar
   */
  setGraphStyle(graph, elementId, style) {
    this.includeAction({
      action: "setGraphStyle",
      values: [graph, elementId, style],
    });
    this.includeBackAction({
      action: "unsetGraphStyle",
      values: [graph, elementId, style],
    });
  }

  /**
   * Quita el estilo al elemento del grafo indicado al entrar en el paso
   * @param {string} graph Nombre del grafo
   * @param {string} elementId Identificador de elemento o grupo
   * @param {object} style Esilo a quitar
   */
  unsetGraphStyle(graph, elementId, style) {
    this.includeAction({
      action: "unsetGraphStyle",
      values: [graph, elementId, style],
    });
    this.includeBackAction({
      action: "setGraphStyle",
      values: [graph, elementId, style],
    });
  }

  /**
   * Valor que se setará en la entrada del paso para la barra del gráfico indicado
   * @param {string} chart Nombre de la gráfico
   * @param {number} id Número de barra
   * @param {object} value Nuevo valor
   */
  newBarValue(chart, id, value) {
    this.includeAction({ action: "barValue", values: [chart, id, value] });
  }

  /**
   * Valor que se seteará cuando se vuelva para el paso anterior del paso actualpara la barra del gráfico indicado
   * @param {string} chart Nombre de la gráfico
   * @param {number} id Número de barra
   * @param {object} value Valor viejo
   */
  oldBarValue(chart, id, value) {
    this.includeBackAction({ action: "barValue", values: [chart, id, value] });
  }

  /**
   * Selecciona para ese paso la barra del gráfico indicado
   * @param {string} chart Nombre de la gráfico
   * @param {number} id Número de barra
   */
  selectBar(chart, id, color = "red") {
    this.includeExitAction({
      action: "unselectBar",
      values: [chart, id],
    });
    this.includeAction({ action: "selectBar", values: [chart, id] });
  }

  /**
   * Selecciona una barra permanentemente del gráfico indicado
   * @param {string} chart Nombre de la gráfico
   * @param {number} id Número de barra
   */
  selectBarPermanent(chart, id, color = "red") {
    this.includeBackAction({
      action: "unselectBar",
      values: [chart, id],
    });
    this.includeAction({ action: "selectBar", values: [chart, id, color] });
  }

  /**
   * Deselecciona para ese paso la barra del gráfico indicado
   * @param {string} chart Nombre de la gráfico
   * @param {number} id Número de barra
   */
  unselectBar(chart, id) {
    if (OutputData.get(chart).getBarStyle(id) !== undefined) {
      this.includeBackAction({
        action: "setBarStyle",
        values: [chart, id, OutputData.get(chart).getBarStyle(id)],
      });
    }
    this.includeAction({ action: "unselectBar", values: [chart, id] });
  }

  action = {
    selectCellAction: function (table, row, column) {
      OutputData.get(table).selectCell(row, column);
    },
    unselectCellAction: function (table, row, column) {
      OutputData.get(table).unselectCell(row, column);
    },
    unselectSourceLineAction: function (line) {
      __FlowManager.unselectLine(line);
    },
    selectSourceLineAction: function (line) {
      __FlowManager.selectLine(line);
    },
    cellValueAction: function (table, row, column, value) {
      OutputData.get(table).setValue(row, column, value);
    },
    resultValueAction: function (name, value) {
      OutputData.get(name).setData(value);
    },
    setGraphStyleAction: function (graph, id, style) {
      OutputData.get(graph).setStyleToElement(id, style);
    },
    unsetGraphStyleAction: function (graph, id, style) {
      OutputData.get(graph).unsetStyleToElement(id, style);
    },
    barValueAction: function (chart, id, value) {
      OutputData.get(chart).setBarValue(id, value);
    },
    selectBarAction: function (chart, id, color) {
      OutputData.get(chart).selectBar(id, color);
    },
    unselectBarAction: function (chart, id) {
      OutputData.get(chart).unselectBar(id);
    },
    setBarStyleAction: function (chart, id, style) {
      OutputData.get(chart).setBarStyle(id, style);
    },
  };

  /**
   * Ejecuta la acción dada
   * @param {action} actualAction Acción a ejecutar
   */
  executeAction = function (actualAction) {
    switch (actualAction.action) {
      case "selectCell":
        this.action.selectCellAction.apply(actualAction, actualAction.values);
        break;
      case "unselectCell":
        this.action.unselectCellAction.apply(actualAction, actualAction.values);
        break;
      case "unselectSourceLine":
        this.action.unselectSourceLineAction.apply(
          actualAction,
          actualAction.values
        );
        break;
      case "selectSourceLine":
        this.action.selectSourceLineAction.apply(
          actualAction,
          actualAction.values
        );
        break;
      case "cellValue":
        this.action.cellValueAction.apply(actualAction, actualAction.values);
        break;
      case "resultValue":
        this.action.resultValueAction.apply(actualAction, actualAction.values);
        break;
      case "setGraphStyle":
        this.action.setGraphStyleAction.apply(
          actualAction,
          actualAction.values
        );
        break;
      case "unsetGraphStyle":
        this.action.unsetGraphStyleAction.apply(
          actualAction,
          actualAction.values
        );
        break;
      case "barValue":
        this.action.barValueAction.apply(actualAction, actualAction.values);
        break;
      case "selectBar":
        this.action.selectBarAction.apply(actualAction, actualAction.values);
        break;
      case "unselectBar":
        this.action.unselectBarAction.apply(actualAction, actualAction.values);
        break;
      case "setBarStyle":
        this.action.setBarStyleAction.apply(actualAction, actualAction.values);
        break;
      default:
        break;
    }
  };
}

/**
 * Instancia del gestor del flujo del algoritmo
 * @constant
 * @type {__FlowManager}
 */
const FlowManager = new __FlowManager();