/*============================= classe de erros ============================*/
function ErrorTextWithVar(textElement) {
  this.textElement = textElement;
  this.errorId = 'error' + this.textElement.name;
}

ErrorTextWithVar.prototype.render = function(error) {
  var ERROR_ELEMENT = document.createElement('span');

  ERROR_ELEMENT.id = this.errorId;
  ERROR_ELEMENT.style.color = '#f44336';
  ERROR_ELEMENT.style.display = 'block';
  ERROR_ELEMENT.textContent = 'Erro: ' + error;

  return ERROR_ELEMENT;
};

ErrorTextWithVar.prototype.clear = function() {
  this.errorElement = document.getElementById(this.errorId);

  if (this.errorElement) {
    this.removeElement(this.errorElement);
  }
};

ErrorTextWithVar.prototype.show = function(error) {
  var ERROR_ELEMENT = this.render(error),
    PARENT = this.textElement.parentNode,
    NEXT_SIBLING = this.textElement.nextSibling;

  PARENT.insertBefore(ERROR_ELEMENT, NEXT_SIBLING);
};

ErrorTextWithVar.prototype.removeElement = function(element) {
  element.parentNode.removeChild(element);
};

/*=========================== classe de validação ==========================*/
function selectTextVar(e) {
  if (!this.isCursorOnly()) return;

  this.getText();
  var SELECTION = this.getSelection(),
    CHAR_BEFORE_CURSOR = SELECTION.textBefore.charAt(SELECTION.textBefore.length - 1),
    CHAR_AFTER_CURSOR = SELECTION.textAfter.charAt(0);

  if (CHAR_BEFORE_CURSOR == '}' && CHAR_AFTER_CURSOR == '{') return;

  var startMatch, start, end;

  if (CHAR_BEFORE_CURSOR + CHAR_AFTER_CURSOR == '{{') {
    start = SELECTION.textBefore.length - 1;
    end = start + SELECTION.textAfter.match(/^{[^{]*}}/)[0].length + 1;
  } else if (CHAR_BEFORE_CURSOR + CHAR_AFTER_CURSOR == '}}') {
    start = SELECTION.textBefore.match(/{{[^}]*}$/).index;
    end = SELECTION.textBefore.length + 1;
  }

  if (!start) {
    startMatch = SELECTION.textBefore.match(/{{[^}]*$/);

    if (!startMatch) return;
    start = startMatch.index;
  }

  if (!end) {
    end = startMatch.input.length + SELECTION.textAfter.match(/^[^{]*}}/)[0].length;
  }

  if (!end) return;

  this.selectText(start, end);
}

function TextWithVar(textElement) {
  this.init(textElement);
  this.initEvents();
}

TextWithVar.prototype.init = function(textElement) {
  this.textElement = textElement;
  this.error = new ErrorTextWithVar(this.textElement);
  this.varNamesList = [];
  this.styleCssValues = {
    success: '#4caf50',
    error: '#f44336',
  };
};

TextWithVar.prototype.initEvents = function() {
  this.textElement.onkeyup = selectTextVar.bind(this);

  this.textElement.onclick = selectTextVar.bind(this);

  this.textElement.onpaste = function(e) {
    this.error.clear();
    if (this.selectionContainsVar()) {
      e.preventDefault();
      this.error.show('A seleção contém partes de variáveis.');
    }
  }.bind(this);

  this.textElement.onkeydown = function(e) {
    var BACKSPACE = 8,
      DELETE = 46,
      ARROW_LEFT = 37,
      ARROW_RIGHT = 39,
      SELECTION = this.getSelection();

    this.getText();

    switch (e.which) {
      case BACKSPACE:
        if (this.isCursorOnly() && this.text.substr(SELECTION.start - 1, 1) == '}') {
          var objectMatch = SELECTION.textBefore.match(/{{[^}]*}}$/);
          this.selectText(objectMatch.index, objectMatch.index + objectMatch[0].length);
        }
        break;
      case DELETE:
        if (this.isCursorOnly() && this.text.substr(SELECTION.end, 1) == '{') {
          var objectMatch = SELECTION.textAfter.match(/^{{[^}]*}}/);
          this.selectText(SELECTION.textBefore.length, SELECTION.textBefore.length + objectMatch[0].length);
        }
        break;
      case ARROW_LEFT:
        if (this.isCursorOnly() && this.text.substr(SELECTION.start - 1, 1) == '}') {
          var objectMatch = SELECTION.textBefore.match(/{{[^}]*}}$/);
          this.selectText(objectMatch.index, objectMatch.index + objectMatch[0].length);
        }
        break;
      case ARROW_RIGHT:
        if (this.isCursorOnly() && this.text.substr(SELECTION.end, 1) == '{') {
          var objectMatch = SELECTION.textAfter.match(/^{{[^}]*}}/);
          this.selectText(SELECTION.textBefore.length, SELECTION.textBefore.length + objectMatch[0].length);
        }
        break;
    }

    this.error.clear();
    if (this.selectionContainsVar() && e.key.length == 1) {
      e.preventDefault();
      this.error.show('A seleção contém partes de variáveis.');
    }
  }.bind(this);
};

TextWithVar.prototype.setVarList = function(varNamesList) {
  this.varNamesList = varNamesList;
};

TextWithVar.prototype.getText = function() {
  return (this.text = this.textElement.value || this.textElement.textContent);
};

TextWithVar.prototype.getSelection = function() {
  var ELEMENT = this.textElement;
  this.getText();

  var start = ELEMENT.selectionStart,
    end = ELEMENT.selectionEnd,
    text = (ELEMENT.value || ELEMENT.textContent).substr(start, end - start),
    textBefore = (ELEMENT.value || ELEMENT.textContent).substr(0, start),
    textAfter = (ELEMENT.value || ELEMENT.textContent).substr(end, (ELEMENT.value || ELEMENT.textContent).length);

  return {
    start: start,
    end: end,
    text: text,
    textBefore: textBefore,
    textAfter: textAfter,
  };
};

TextWithVar.prototype.getValidation = function() {
  var OPEN_BRACE_LENGTH = this.text.match(/[{]/g) ? this.text.match(/[{]/g).length : { length: 0 },
    CLOSE_BRACE_LENGTH = this.text.match(/[}]/g) ? this.text.match(/[}]/g).length : { length: 0 };

  return [
    {
      error: this.text.match(/[^{][{][^{]/),
      objectMatch: this.text.match(/[^{][{][^{]/),
      message: 'Existem chaves de abertura sozinhas {, estas chaves só funcionam em duplas {{.',
    },
    {
      error: this.text.match(/[^}][}][^}]/),
      objectMatch: this.text.match(/[^}][}][^}]/),
      message: 'Existem chaves de fechamento sozinhas }, estas chaves só funcionam em duplas }}.',
    },
    {
      error: this.text.match(/[{][{][^}]*[{][{][^{]*[}][}]/),
      objectMatch: this.text.match(/[{][{][^}]*[{][{][^{]*[}][}]/),
      message: 'Existe chaves de abertura dentro de uma variável. Ex: {{varia{{vel}}.',
    },
    {
      error: this.text.match(/[{][{][^}]*[}][}][^{]*[}][}]/),
      objectMatch: this.text.match(/[{][{][^}]*[}][}][^{]*[}][}]/),
      message: 'Existe chaves de fechamento dentro de uma variável. Ex: {{varia}}vel}}.',
    },
    {
      error: this.text.match(/{{[^{}]*[^a-z0-9{}_]+[^{}]*}}/),
      objectMatch: this.text.match(/{{[^{}]*[^a-z0-9{}_]+[^{}]*}}/),
      message:
        'Existem caracteres inválidos nos nomes das variáveis, são aceitos apenas números de 0 a 9, letras de a a z minúsculas e _ .',
    },
    {
      error: this.text.match(/{{}}/),
      objectMatch: this.text.match(/{{}}/),
      message: 'Existe um ou mais identificadores de variáveis vazios {{}}.',
    },
    {
      error: this.text.match(/[}][}][}]/),
      objectMatch: this.text.match(/[}][}][}]/),
      message: 'Existe um caracter de fechamento com 3 chaves }}}, estas chaves só funcionam em duplas.',
    },
    {
      error: this.text.match(/[{][{][{]/),
      objectMatch: this.text.match(/[{][{][{]/),
      message: 'Existe um caracter de abertura com 3 chaves {{{, estas chaves só funcionam em duplas.',
    },
    {
      error: OPEN_BRACE_LENGTH > CLOSE_BRACE_LENGTH,
      objectMatch: this.text.match(/[{][{][^{]*$/),
      message: 'O numero de aberturas é maior que o de fechamentos.',
    },
    {
      error: OPEN_BRACE_LENGTH < CLOSE_BRACE_LENGTH,
      objectMatch: this.text.match(/^[^}]*[}][}]/),
      message: 'O numero de fechamentos é maior que o de aberturas.',
    },
  ];
};

TextWithVar.prototype.textContainsVarName = function(varName) {
  var TEXT = this.getText();
  return TEXT.indexOf('{{' + varName + '}}') != -1;
};

TextWithVar.prototype.validateVarNames = function() {
  var varNamesInText;
  var VALID_VAR_NAMES_LIST = this.varNamesList;

  if (VALID_VAR_NAMES_LIST.length) {
    varNamesInText = this.text.match(/{{[0-9a-z_]+}}/g);
  }

  if (!varNamesInText) return;

  for (var i = 0; i < varNamesInText.length; i++) {
    var varNameWithoutBraces = varNamesInText[i].replace('{{', '').replace('}}', '');

    if (VALID_VAR_NAMES_LIST.indexOf(varNameWithoutBraces) == -1) {
      this.error.show('O nome de variável ' + varNamesInText[i] + ' é inválido.');
      var start = this.text.indexOf(varNamesInText[i]),
        end = start + varNamesInText[i].length;

      this.selectText(start, end);
      break;
    }
  }
};

TextWithVar.prototype.validText = function() {
  this.error.clear();
  this.getText();

  var VALIDATIONS = this.getValidation();

  for (var i = 0; i < VALIDATIONS.length; i++) {
    var VALIDATION = VALIDATIONS[i];

    if (VALIDATION.error) {
      this.error.show(VALIDATION.message);
      this.selectText(VALIDATION.objectMatch.index, VALIDATION.objectMatch.index + VALIDATION.objectMatch[0].length);

      this.textElement.style.borderColor = this.styleCssValues.error;
      return false;
    }
  }

  if (this.validateVarNames()) {
    return false;
  }

  this.textElement.style.borderColor = this.styleCssValues.success;
  return true;
};

TextWithVar.prototype.selectText = function(start, end) {
  this.textElement.focus();
  this.textElement.selectionStart = start;
  this.textElement.selectionEnd = end;
};

TextWithVar.prototype.setCursorAtPosition = function(index) {
  this.selectText(index, index);
};

TextWithVar.prototype.isCursorOnly = function() {
  var SELECTION = this.getSelection();
  return SELECTION.start == SELECTION.end;
};

TextWithVar.prototype.selectionContainsVar = function() {
  var SELECTION = this.getSelection();

  return SELECTION.text && /[{}]/g.test(SELECTION.text);
};

TextWithVar.prototype.insertVarNameInText = function(varName) {
  var VAR_CODE = '{{' + varName + '}}',
    ELEMENT = this.textElement,
    SELECTION = this.getSelection(),
    MAXLENGTH = ELEMENT.getAttribute('maxlength'),
    LENGTH = (typeof ELEMENT.value != 'undefined') ? ELEMENT.value.length : ELEMENT.textContent.length;

  if(MAXLENGTH && LENGTH+VAR_CODE.length > Number(MAXLENGTH)) {
    return 'Variavel excede a quantidade de caracteres disponíveis';
  }

  this.error.clear();

  if (this.selectionContainsVar()) {
    this.error.show('A seleção contém partes de variáveis');
    this.selectText(SELECTION.start, SELECTION.start + SELECTION.text.length);

    return false;
  }

  if (typeof ELEMENT.value != 'undefined') {
    ELEMENT.value = SELECTION.textBefore + VAR_CODE + SELECTION.textAfter;
  } else {
    ELEMENT.textContent = SELECTION.textBefore + VAR_CODE + SELECTION.textAfter;
  }

  this.setCursorAtPosition(SELECTION.start + VAR_CODE.length);
  ELEMENT.dispatchEvent(new Event('input'));
};

export default TextWithVar;
