'use strict';

var htmlparser2 = require('htmlparser2');

// https://www.w3.org/TR/html/syntax.html#writing-html-documents-elements
var voidElements = [
  'area',
  'base',
  'br',
  'col',
  'embed',
  'hr',
  'img',
  'input',
  'keygen',
  'link',
  'menuitem',
  'meta',
  'param',
  'source',
  'track',
  'wbr' ];

var format = function(html, ref) {
  if ( ref === void 0 ) ref = {};
  var sortAttributes = ref.sortAttributes; if ( sortAttributes === void 0 ) sortAttributes = function (names) { return names; };

  var elements = [];
  var indentSize = 2;

  var currentDepth = 0;

  var increaseCurrentDepth = function () {
    currentDepth++;
  };

  var decreaseCurrentDepth = function () {
    currentDepth--;
  };

  var getIndentation = function (size) {
    return ' '.repeat(size);
  };

  var getIndentationForDepth = function (depth) {
    return getIndentation(indentSize * depth);
  };

  var getCurrentIndentation = function () {
    return getIndentationForDepth(currentDepth);
  };

  var getAttributeIndentation = function (tagName) {
    return getIndentation(indentSize * currentDepth - 1);
  };

  var append = function (content) {
    elements.push(content);
  };

  var appendLineBreak = function () {
    append('\n');
  };

  var appendIndentation = function (depth) {
    append(getIndentationForDepth(depth));
  };

  var appendCurrentIndentation = function () {
    append(getCurrentIndentation());
  };

  var appendOpeningTag = function (name) {
    append('<' + name);
  };

  var appendClosingTagOnSameLine = function (closeWith) {
    if ( closeWith === void 0 ) closeWith = '>';

    append(closeWith);
  };

  var appendClosingTagOnNewLine = function (closeWith) {
    if ( closeWith === void 0 ) closeWith = '>';

    appendLineBreak();
    appendIndentation(currentDepth - 1);
    append(closeWith);
  };

  var getAttributeAsString = function (name, value) {
    if (value.length === 0) {
      return name;
    }

    return (name + "=\"" + value + "\"");
  };

  var appendAttribute = function (name, value) {
    var attribute = ' ' + name;

    if (value.length > 0) {
      attribute += "=\"" + value + "\"";
    }

    append(attribute);
  };

  var appendAttributeOnNewLine = function (name, value, tagName) {
    appendLineBreak();
    append(getAttributeIndentation(tagName));
    appendAttribute(name, value);
  };

  var appendAttributes = function (attributes, tagName) {
    var names = sortAttributes(Object.keys(attributes));

    if (names.length === 1) {
      appendAttribute(names[0], attributes[names[0]]);
    }

    if (names.length <= 1) {
      return;
    }

    for (var name of names) {
      appendAttributeOnNewLine(name, attributes[name], tagName);
    }
  };

  var appendClosingTag = function (attributes, closeWith) {
    if (Object.keys(attributes).length <= 1) {
      appendClosingTagOnSameLine(closeWith);

      return;
    }
    appendClosingTagOnNewLine(closeWith);
  };

  var render = function () {
    return elements.join('');
  };

  var isXmlDirective = function (name) {
    return name === '?xml';
  };

  var isVoidTagName = function (name) {
    return voidElements.indexOf(name) !== -1;
  };

  // https://www.w3.org/TR/html52/infrastructure.html#space-characters
  // defines "space characters" to include SPACE, TAB, LF, FF, and CR.
  var trimText = function (text) {
    return text.replace(/^[ \t\n\f\r]+|[ \t\n\f\r]+$/g, '');
  };

  var extractAttributesFromString = function (content) {
    var attributes = {};

    var pieces = content.split(/\s/);
    // Remove tag name.
    delete pieces[0];

    pieces.forEach(function (element) {
      if (element.length === 0) {
        return;
      }
      if (element.indexOf('=') === -1) {
        attributes[element] = '';
      }
    });

    var attributesRegex = /(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|[>"']))+.)["']?/gim;

    var result;
    while ((result = attributesRegex.exec(content))) {
      attributes[result[1]] = result[2];
    }

    return attributes;
  };

  var parser = new htmlparser2.Parser(
    {
      onprocessinginstruction: function(name, data) {
        var closingTag = '>';
        if (isXmlDirective(name)) {
          closingTag = '?>';
        }

        appendLineBreak();
        appendCurrentIndentation();
        increaseCurrentDepth();
        appendOpeningTag(name);

        var attributes = extractAttributesFromString(data);
        appendAttributes(attributes, name);
        appendClosingTag(attributes, closingTag);
        decreaseCurrentDepth();
      },
      onopentag: function(name, attributes) {
        appendLineBreak();
        appendCurrentIndentation();
        increaseCurrentDepth();
        appendOpeningTag(name);

        appendAttributes(attributes, name);
        appendClosingTag(attributes, '>');
      },
      ontext: function(text) {
        var trimmed = trimText(text);
        if (trimmed.length === 0) {
          return;
        }

        appendLineBreak();
        appendCurrentIndentation();
        append(trimmed);
      },
      onclosetag: function(tagname) {
        var isVoidTag = isVoidTagName(tagname);
        if (isVoidTagName(tagname) === false) {
          appendLineBreak();
        }
        decreaseCurrentDepth();
        if (isVoidTag === true) {
          return;
        }
        appendCurrentIndentation();
        append(("</" + tagname + ">"));
      },
      oncomment: function(data) {
        // Only display conditional comments.
        if (!data.startsWith('[')) {
          return;
        }
        appendLineBreak();
        appendCurrentIndentation();
        append('<!--');
        append(data);
        append('-->');
      },
    },
    {
      lowerCaseTags: false,
      recognizeSelfClosing: true,
    }
  );
  parser.write(html);
  parser.end();

  appendLineBreak();

  return render();
};

module.exports = format;
