/**
 * Clase auxiliar para la creación de nodos HTML, así como 
 * del ajuste del tamaño del canvas.
 * @author Melissa Méndez Servín.
 */

/**
 * Ajusta el tamaño del canvas de acuerdo al tamaño de la ventana.
 * @param {*} canvas 
 */
function resize(canvas) {
  var canvasWidth = canvas.width;
  var canvasHeight = canvas.height;

  if (canvasWidth !== window.innerWidth || canvasHeight !== window.innerHeight) {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
  }
}
function resizeContainerAndCanvas(canvas) {
  var container = document.getElementById("container2D");
  let scaleToFitX = window.innerWidth / container.offsetWidth;
  let scaleToFitY = window.innerHeight / container.offsetHeight;

  if (scaleToFitX < scaleToFitY) {
    container.style.left = "0px";
    container.style.top = "50%";
    container.style.transform = `scale(${scaleToFitX}) translate(0, -50%)`;
    canvas.width = window.innerWidth;
    canvas.height = container.offsetHeight * scaleToFitX;
    return scaleToFitX;
  }
  else {
    container.style.left = "50%";
    container.style.top = "0px";
    container.style.transform = `scale(${scaleToFitY}) translate(-50%, 0)`;
    canvas.width = container.offsetWidth * scaleToFitY;
    canvas.height = window.innerHeight;
    return scaleToFitY;
  }
}
function resizeContainer2D() {
  var container = document.getElementById("container2D");
  let scaleToFitX = window.innerWidth / container.offsetWidth;
  let scaleToFitY = window.innerHeight / container.offsetHeight;

  if (scaleToFitX < scaleToFitY) {
    container.style.left = "0px";
    container.style.top = "50%";
    container.style.transform = `scale(${scaleToFitX}) translate(0, -50%)`;
  }
  else {
    container.style.left = "50%";
    container.style.top = "0px";
    container.style.transform = `scale(${scaleToFitY}) translate(-50%, 0)`;
  }
}
/**
 * **** Arreglar traslación.
 * @param {*} container 
 * @param {*} position 
 */
function scaleToFitContainer(container, position) {
  let scaleToFitX = window.innerWidth / container.offsetWidth;
  let scaleToFitY = window.innerHeight / container.offsetHeight;
  scaleToFitX = (scaleToFitX > 1) ? 1 : scaleToFitX;
  scaleToFitY = (scaleToFitY > 1) ? 1 : scaleToFitY;

  container.style.left = position.x + "%";
  container.style.top = position.y + "%";
  if (scaleToFitX < scaleToFitY) {
    container.style.transform = `scale(${scaleToFitX}) translate(-${position.x}%, 0)`;
  }else {
    container.style.transform = `scale(${scaleToFitY}) translate(-${position.x}%, 0)`;
  }
}
function resizeSquareCanvas(canvas) {
  let scaleToFitX = window.innerWidth / canvas.width;
  let scaleToFitY = window.innerHeight / canvas.height;
  if (scaleToFitX < scaleToFitY) {
    canvas.width = window.innerWidth;
    canvas.height = window.innerWidth;
  }
  else {
    canvas.width = window.innerHeight;
    canvas.height = window.innerHeight;
  }
}
/**
  * Se considera que el container es el id #ui-container-center
  * o algun estilo que ajuste la coordenada x.
  * @param {*} container 
  * @param {*} position 
*/
function resizeAndCenterX(container, y_position) {
 let scaleToFitX = window.innerWidth / container.offsetWidth;
 let scaleToFitY = window.innerHeight / container.offsetHeight;
  if(y_position.bottom == undefined){
    container.style.top = y_position.top + "%";
  }else{
    container.style.bottom = y_position.bottom + "%";
  }

 scaleToFitX = (scaleToFitX > 1) ? 1 : scaleToFitX;
 scaleToFitY = (scaleToFitY > 1) ? 1 : scaleToFitY;

 if (scaleToFitX < scaleToFitY) {
   container.style.transform = `translate(-50%, 0%) scale(${scaleToFitX})`;
 }
 else {
   container.style.transform = `translate(-50%, 0%) scale(${scaleToFitY})`;
 }
}
/**
 * Se crea un slider a el elemento del DOM dado.
 * @param {HTMLElement} parent el padre del slider en el DOM.
 * @param {*} id el id del elemento.
 * @param {*} min el valor mínimos en el slider.
 * @param {*} max el valor máximo en el slider.
 * @param {*} fn la función que se ejecutará cada vez que se mueva el slider.
 * @param {*} value el valor inicial del slider.
 * @param {*} step las unidades mínima con la que se mueve el slider.
 */
function Slider(parent, id, min, max, fn, value = 1, step = 1, colors, unit = "", positive=false) {
  this.id = id;
  this.min = min.toFixed(1);
  this.max = max.toFixed(1);
  this.fn = fn;
  this.initValue = value;
  this.unit = unit || "";
  this.parent = parent;
  if (!colors)
    colors = ["#008b8b", "#c6d3d3"];

  let control = parent.appendChild(document.createElement("div"));
  control.className = (positive) ? "positive-control" : "control";
  this.parent = control;
  this.label = control.appendChild(document.createElement("div"));
  this.label.className = "label";
  this.label.innerHTML = id + " ";

  let slider = control.appendChild(document.createElement("INPUT"));
  slider.setAttribute("type", "range");
  slider.setAttribute("min", this.min);
  slider.setAttribute("max", this.max);
  slider.setAttribute("step", step);
  slider.value = this.initValue;
  slider.id = id;
  slider.className = "in-line";
  slider.style.background = getSliderLinearGradientColor(min, max, this.initValue, colors[0], colors[1]);
  this.slider = slider;

  let slider_value = control.appendChild(document.createElement("div"));
  slider_value.className = "slider-value";
  //Ponemos el valor actual
  slider_value.innerHTML = this.initValue + unit;

  this.slider_value = slider_value;
  //Actualizamos el valor
  this.slider.oninput = function () {
    var val = parseFloat(slider.value);
    slider_value.innerHTML = val + unit;
    slider.style.background = getSliderLinearGradientColor(min, max, val, colors[0], colors[1]);
    fn(val);
  }
}
Slider.prototype.setStyle = function (style, style_value, parent) {
  if(style)
    this.slider.className = style;
  if(style_value)
    this.slider_value.className = style_value;
  if(parent)
    this.parent = parent;
}
Slider.prototype.disabled = function (disable) { this.slider.disabled = disable };

/**
 * Actualiza el backgraound y la etiqueta del valor ligada al slider.
 * @param {*} slider el slider.
 * @param {*} slider_value la etiqueta del valor del slider.
 * @param {*} value el valor del slider.
 */
function updateSlider(control, value, unit = "") {
  control.slider_value.innerHTML = value + unit;
  control.slider.value = value;
  control.slider.style.background = getSliderLinearGradientColor(control.slider.min, control.slider.max, value);

}
Slider.prototype.updateSlider = function (value, unit = "") {
  this.slider_value.innerHTML = value + unit;
  this.slider.value = value;
  this.slider.style.background = getSliderLinearGradientColor(this.slider.min, this.slider.max, value);
}
/**
 * Devuelve el valor linear-gradient del background, de acuerdo al valores del slider
 * dados.
 * @param {*} min el valor mínimo en el slider.
 * @param {*} max el valor máximo en el slider.
 * @param {*} value el valor actual del slider.
 */
function getSliderLinearGradientColor(min, max, value, color1, color2) {
  var color1 = color1 || "#008b8b";
  var color2 = color2 || "#e1e4e6";
  var hundred = max - min;
  var percentage = ((value - min) * 100) / hundred;
  percentage = (percentage < 0) ? -percentage : percentage;
  return 'linear-gradient(90deg, ' + color1 + ' ' + percentage + '%, ' + color2 + ' ' + percentage + '%)';
}
/**
 * Se crea un slider a el elemento del DOM dado.
 * @param {HTMLElement} parent el padre del slider en el DOM.
 * @param {*} attributes los atributos con la información del slider.
 */
function BigSlider(parent, id, min, max, fn, value = 1, step = 1, colors, unit = "") {
  let new_control = parent.appendChild(document.createElement("div"));
  new_control.className = "big-control";
  let label = new_control.appendChild(document.createElement("div"));
  label.className = "in-line-label";
  label.innerHTML = id + " ";

  this.min = min.toFixed(1);
  this.max = max.toFixed(1);
  let slider = new_control.appendChild(document.createElement("INPUT"));
  slider.setAttribute("type", "range");
  slider.setAttribute("min", this.min);
  slider.setAttribute("max", this.max);
  slider.setAttribute("step", step);
  slider.value = value;
  slider.id = id;
  slider.className = "big-in-line";

  if (unit)
    slider.style.width = "60%";

  let slider_value = new_control.appendChild(document.createElement("div"));
  slider_value.className = "slider-value";
  if (!colors)
    colors = ["#008b8b", "#c6d3d3"];
  const color1 = colors[0];
  const color2 = colors[1];
  slider.style.background = getSliderLinearGradientColor(min, max, value, color1, color2);

  //Ponemos el valor actual
  slider_value.innerHTML = value + unit;

  this.slider = slider;
  this.slider_value = slider_value;
  //Actualizamos el valor
  this.slider.oninput = function () {
    var val = parseFloat(slider.value);
    slider_value.innerHTML = val + unit;
    slider.style.background = getSliderLinearGradientColor(min, max, val, color1, color2);
    fn(val);
  }
}
/**
 * Se crea un slider a el elemento del DOM dado.
 * @param {HTMLElement} parent el padre del slider en el DOM.
 * @param {*} attributes los atributos con la información del slider.
 */
function SliderOnly(parent, id, min, max, fn, value = 1, step = 1, colors) {
  let new_control = parent.appendChild(document.createElement("div"));
  new_control.className = "big-control";
  this.control = new_control;
  this.min = min.toFixed(1);
  this.max = max.toFixed(1);
  let slider = new_control.appendChild(document.createElement("INPUT"));
  slider.setAttribute("type", "range");
  slider.setAttribute("min", this.min);
  slider.setAttribute("max", this.max);
  slider.setAttribute("step", step);
  slider.value = value;
  slider.id = id;
  slider.className = "big-in-line";

  if (!colors)
    colors = ["#008b8b", "#c6d3d3"];
  const color1 = colors[0];
  const color2 = colors[1];
  slider.style.background = getSliderLinearGradientColor(min, max, value, color1, color2);

  this.slider = slider;
  //Actualizamos el valor
  this.slider.oninput = function () {
    var val = parseFloat(slider.value);
    slider.style.background = getSliderLinearGradientColor(min, max, val, color1, color2);
    fn(val);
  }
}
SliderOnly.prototype.setStyle = function (style) {
  this.slider.className = style;
}
/**
 * Se agrega un botón el elemento del DOM dado, con la finalidad de 
 * reiniciar el render.
 * @param {*} reset la función para reinciar que se aplicará al hacer click.
 * @param {*} negative botón negativo por default.
 */
function createResetButton(reset, negative = true, name = "Reset") {
  let button_block = container.appendChild(document.createElement("div"));
  button_block.className = "reset-block";

  let button = button_block.appendChild(document.createElement("INPUT"));
  button.setAttribute("type", "button");
  button.setAttribute("value", name);
  button.className = (!negative) ? "basic-bttn" : "negative-bttn";
  button.onclick = function () { return reset(); }
}
function Button(parent, fn, label, style, disable = false) {
  this.button = parent.appendChild(document.createElement("INPUT"));
  this.button.setAttribute("type", "button");
  this.button.setAttribute("value", label);
  this.button.className = "basic-bttn " + style;
  this.button.disabled = disable;
  this.button.onclick = function () { return fn(); }
}
Button.prototype.updateState = function (step, min, max) {
  if (step < min)
    this.button.disabled = true;
  else if (step > max)
    this.button.disabled = true;
  else
    this.button.disabled = false;
}
Button.prototype.disabled = function (disable) { this.button.disabled = disable };
/**
 * 
 * @param {HTMLElement} parent el padre del botón en el DOM.
 * @param {*} fn 
 * @param {*} label 
 */
function createButton(parent, fn, label) {
  let button = parent.appendChild(document.createElement("INPUT"));
  button.setAttribute("type", "button");
  button.setAttribute("value", label);
  button.className = "negative-bttn";

  button.onclick = function () { return fn(); }
  return button;
}
/**
 * Se crea un checkbox a el elemento del DOM dado.
 * @param {HTMLElement} parent el padre del checkbox en el DOM.
 * @param {*} id el id del elemento.
 * @param {*} text el texto con el que se etiquetará el checker.
 * @param {*} fn (opcional) la función que se ejecutará cuando se haga click sobre 
 *               la caja.
 * @param {*} checked el valor inicial de la caja. 
 */
function Checkbox(parent, id, text, fn = null, checked = false) {
  let new_control = parent.appendChild(document.createElement("div"));
  new_control.className = "check-control";

  this.checkbox = new_control.appendChild(document.createElement("INPUT"));
  this.checkbox.setAttribute("type", "checkbox");
  this.checkbox.setAttribute("value", id);
  this.checkbox.checked = checked;

  let label = new_control.appendChild(document.createElement("label"));
  label.innerHTML = text;
  label.className = "checkbox-label";

  if (fn)
    this.checkbox.onclick = function () { return fn(); };
}
/**
 * Devuelve si la casilla está marcada o no.
 */
Checkbox.prototype.checked = function () {
  return this.checkbox.checked;
}
Checkbox.prototype.disabled = function (disable) { this.checkbox.disabled = disable };

function InputElement(parent, style, initialValue, fn) {
  this.parent = parent;
  var ele = parent.appendChild(document.createElement("input"));
  ele.className = style;
  this.value = initialValue;
  ele.value = this.value;
  ele.type = "text";
  this.ele = ele;
  this.ele.onchange = function () {
    fn();
  }
}
InputElement.prototype.setValue = function (val) {
  this.ele.value = val;
}
InputElement.prototype.getValue = function () {
  return this.ele.value;
}
InputElement.prototype.getNum = function () {
  return parseFloat(this.ele.value);
}
function MatrixElement(parent, style, num) {
  this.parent = parent;
  this.ele = parent.appendChild(document.createElement("div"));
  this.ele.className = style;
  this.num = (num != undefined) ? num : Math.floor(Math.random() * 19) - 9;
  this.ele.innerHTML = this.num + "";
}
MatrixElement.prototype.setNum = function (num) {
  this.num = (num != undefined) ? num : Math.floor(Math.random() * 19) - 9;
  this.ele.innerHTML = this.num + "";
}

/**
 * 
 * @param {*} parent 
 * @param {*} position 
 * @param {*} math_text 
 */
function TextBox(parent, position, text = "", style = null) {
  this.parent = parent;
  this.position = position;
  this.text = text;
  this.box = parent.appendChild(document.createElement("div"));
  this.box.className = (style) ? style : "author";
  this.box.innerHTML = text;
  setElementPosition(this.box, position);
}
TextBox.prototype.setPosition = function (position) {
  setElementPosition(this.box, position);
}
TextBox.prototype.setText = function (text) {
  this.text = text;
  this.box.innerHTML = text;
}

/**
 * 
 * @param {*} parent 
 * @param {*} position 
 * @param {*} math_text 
 */
function MathBox(parent, position, math_text = "", style = null) {
  this.parent = parent;
  this.position = position;
  this.text = math_text;
  this.box = parent.appendChild(document.createElement("div"));
  this.box.className = style || "katex-box";
  setElementPosition(this.box, position);
  if (math_text)
    setMathBox(this.box, math_text);
}
MathBox.prototype.setPosition = function (position) {
  setElementPosition(this.box, position);
}
MathBox.prototype.setText = function (text) {
  this.text = text;
  this.box.innerHTML = renderLatex(text);
}
MathBox.prototype.hideLabel = function () {
  setElementPosition(this.box, { left: -100, top: -100 });
}
/**
 * Crea un elemento en el DOM, en particular pasa usar katex.
 * @param {*} parent   el elemento padre al que se le agregará la etiqueta
 * @param {*} position objeto con la posición de la etiqueta en porcentaje, 
 *                     con los atributos a usar: left, right, bottom o up. 
 *                      Ej. {bottom: 9, left: 15};
 *                     se especifica type:"px" para una medida en específico
 */
function createMathBox(parent, position, math_text) {
  let math_label = parent.appendChild(document.createElement("div"));
  math_label.className = "math-label";
  setElementPosition(math_label, position);
  if (math_text)
    setMathBox(math_label, math_text);
  return math_label;
}
function setElementPosition(element, position) {
  var stylePosition = "";
  const type = (position.type == undefined) ? '%' : position.type;
  if (position.top != undefined)
    stylePosition += `top:${position.top}` + type + ';';
  if (position.left != undefined)
    stylePosition += `left:${position.left}` + type + ';';
  if (position.bottom != undefined)
    stylePosition += `bottom:${position.bottom}` + type + ';';
  if (position.right != undefined)
    stylePosition += `right:${position.right}` + type + ';';

  element.style = stylePosition;
}
function setMathBox(label, math_text) {
  label.innerHTML = renderLatex(math_text);
}
/**
 * Renderiza una expresión TeX con katex.
 * @param {String} s la expresión TeX.
 * @return {String} la expersión renderizada.
 */
function renderLatex(s, displayMode = false) {
  var html = katex.renderToString(s, {
    throwOnError: false,
    displayMode: displayMode
  });
  return html;
}
/**
 * Se renderiza texto html.
 * @param {*} label 
 * @param {*} text 
 */
function setSimpleHTMLText(label, text) {
  label.innerHTML = text;
}
/**
 * Componente para seleccionar un color.
 * @param {*} parent 
 * @param {*} id 
 * @param {*} fn 
 * @param {*} initial_color 
 */
function Color(parent, id, fn, initial_color) {
  let new_control = parent.appendChild(document.createElement("div"));
  new_control.className = "control-color";
  let label = new_control.appendChild(document.createElement("label"));
  label.className = "label";
  label.innerHTML = id + " ";
  var color = new_control.appendChild(document.createElement("input"));
  color.type = "color";
  color.value = initial_color || "#FF0000";
  color.id = id;
  this.color = color;

  color.oninput = function () {
    fn(hexToRgbNormalized(color.value));
  }

}
Color.prototype.disabled = function (disable) { this.color.disabled = disable };
Color.prototype.setColor = function(rgb){
  this.color.value = rgbNormToHex(rgb);
}
function Select(parent, id, fn, values, curr_value){
  let new_control = parent.appendChild(document.createElement("div"));
  //new_control.className = "select-parent";

  let select = new_control.appendChild(document.createElement("select"));
  select.id = id;
  var opt;
  Object.keys(values).forEach(element => {
    opt = select.appendChild(document.createElement("option"));
    opt.value = element;
    opt.innerHTML = element;
    
  });
  if(curr_value)
    select.value = curr_value;
  this.select = select;
  select.onchange = function(){
    fn(select.value);
  }  
}
Select.prototype.disabled = function (disable) { this.select.disabled = disable };

function addLabel(parent, text){
  let new_control = parent.appendChild(document.createElement("div"))
  new_control.className = "control-label";
  let label = new_control.appendChild(document.createElement("label"));
  label.className = "simple-label";
  label.innerHTML = text+ " ";
}
function randomColor() {
  function random() {
    return Math.floor(Math.random() * 255);
  }
  return `rgb(${random()}, ${random()}, ${random()})`;
}
function hexToRgbNormalized(hex) {
  return [parseInt(hex.substring(1, 3), 16) / 255,
  parseInt(hex.substring(3, 5), 16) / 255,
  parseInt(hex.substring(5, 7), 16) / 255];
}
function rgbNormToHex(rgb){
    function getHexNumber(dec){
      let h = dec.toString(16);
      return (h.length == 1) ? "0" + h : h;
    }  
    var r = Math.round(rgb[0] * 255);
    var g = Math.round(rgb[1] * 255);
    var b = Math.round(rgb[2] * 255);
    
    return "#" + getHexNumber(r) + getHexNumber(g) + getHexNumber(b);
}
function DarkMode(draw_callback, controls=[], on=false){
  let check_control = document.getElementById("right-options");
  this.checkbox = new Checkbox(check_control, "Obsc-mode", "Fondo Obscuro", draw_callback, on);
  controls.push(check_control);
  this.controls = controls;
}
DarkMode.prototype.check = function(gl){
  let on = this.checkbox.checked();
  var string = (on) ? "true" : "false";
  var alpha_channel = (on) ? 1 : 0; 
  gl.clearColor(0, 0, 0, alpha_channel);
  const lc = this.controls.length;
  for(var i = 0; i < lc; i++){
    this.controls[i].setAttribute("obscure-mode", string);
  }
}
export {
  resize, resizeContainer2D, scaleToFitContainer,
  resizeSquareCanvas, resizeContainerAndCanvas, resizeAndCenterX,
  Slider, BigSlider, SliderOnly,
  Checkbox,
  Select,
  createResetButton, createButton, Button,
  createMathBox,
  setMathBox, setElementPosition,
  setSimpleHTMLText, addLabel,
  MathBox, MatrixElement, TextBox, InputElement,
  randomColor,
  Color, hexToRgbNormalized, rgbNormToHex,
  DarkMode
};