// node-xml
// SOURCE: https://github.com/dylang/node-xml
// LICENSE: https://github.com/dylang/node-xml/blob/master/LICENSE

var escapeForXML = require('./escapeForXML');
var Stream = require('stream').Stream;

var DEFAULT_INDENT = '    ';

function xml(input, options) {

  if (typeof options !== 'object') {
    options = {
      indent: options
    };
  }

  var stream = options.stream ? new Stream() : null,
    output = "",
    interrupted = false,
    indent = !options.indent ? ''
      : options.indent === true ? DEFAULT_INDENT
        : options.indent,
    instant = true;


  function delay(func) {
    if (!instant) {
      func();
    } else {
      process.nextTick(func);
    }
  }

  function append(interrupt, out) {
    if (out !== undefined) {
      output += out;
    }
    if (interrupt && !interrupted) {
      stream = stream || new Stream();
      interrupted = true;
    }
    if (interrupt && interrupted) {
      var data = output;
      delay(function () { stream.emit('data', data) });
      output = "";
    }
  }

  function add(value, last) {
    format(append, resolve(value, indent, indent ? 1 : 0), last);
  }

  function end() {
    if (stream) {
      var data = output;
      delay(function () {
        stream.emit('data', data);
        stream.emit('end');
        stream.readable = false;
        stream.emit('close');
      });
    }
  }

  function addXmlDeclaration(declaration) {
    var encoding = declaration.encoding || 'UTF-8',
      attr = { version: '1.0', encoding: encoding };

    if (declaration.standalone) {
      attr.standalone = declaration.standalone
    }

    add({ '?xml': { _attr: attr } });
    output = output.replace('/>', '?>');
  }

  // disable delay delayed
  delay(function () { instant = false });

  if (options.declaration) {
    addXmlDeclaration(options.declaration);
  }

  if (input && input.forEach) {
    input.forEach(function (value, i) {
      var last;
      if (i + 1 === input.length)
        last = end;
      add(value, last);
    });
  } else {
    add(input, end);
  }

  if (stream) {
    stream.readable = true;
    return stream;
  }
  return output;
}

function element(/*input, …*/) {
  var input = Array.prototype.slice.call(arguments),
    self = {
      _elem: resolve(input)
    };

  self.push = function (input) {
    if (!this.append) {
      throw new Error("not assigned to a parent!");
    }
    var that = this;
    var indent = this._elem.indent;
    format(this.append, resolve(
      input, indent, this._elem.icount + (indent ? 1 : 0)),
      function () { that.append(true) });
  };

  self.close = function (input) {
    if (input !== undefined) {
      this.push(input);
    }
    if (this.end) {
      this.end();
    }
  };

  return self;
}

function create_indent(character, count) {
  return (new Array(count || 0).join(character || ''))
}

function resolve(data, indent, indent_count) {
  indent_count = indent_count || 0;
  var indent_spaces = create_indent(indent, indent_count);
  var name;
  var values = data;
  var interrupt = false;

  if (typeof data === 'object') {
    var keys = Object.keys(data);
    name = keys[0];
    values = data[name];

    if (values && values._elem) {
      values._elem.name = name;
      values._elem.icount = indent_count;
      values._elem.indent = indent;
      values._elem.indents = indent_spaces;
      values._elem.interrupt = values;
      return values._elem;
    }
  }

  var attributes = [],
    content = [];

  var isStringContent;

  function get_attributes(obj) {
    var keys = Object.keys(obj);
    keys.forEach(function (key) {
      attributes.push(attribute(key, obj[key]));
    });
  }

  switch (typeof values) {
    case 'object':
      if (values === null) break;

      if (values._attr) {
        get_attributes(values._attr);
      }

      if (values._cdata) {
        content.push(
          ('<![CDATA[' + values._cdata).replace(/\]\]>/g, ']]]]><![CDATA[>') + ']]>'
        );
      }

      if (values.forEach) {
        isStringContent = false;
        content.push('');
        values.forEach(function (value) {
          if (typeof value == 'object') {
            var _name = Object.keys(value)[0];

            if (_name == '_attr') {
              get_attributes(value._attr);
            } else {
              content.push(resolve(
                value, indent, indent_count + 1));
            }
          } else {
            //string
            content.pop();
            isStringContent = true;
            content.push(escapeForXML(value));
          }

        });
        if (!isStringContent) {
          content.push('');
        }
      }
      break;

    default:
      //string
      content.push(escapeForXML(values));

  }

  return {
    name: name,
    interrupt: interrupt,
    attributes: attributes,
    content: content,
    icount: indent_count,
    indents: indent_spaces,
    indent: indent
  };
}

function format(append, elem, end) {

  if (typeof elem != 'object') {
    return append(false, elem);
  }

  var len = elem.interrupt ? 1 : elem.content.length;

  function proceed() {
    while (elem.content.length) {
      var value = elem.content.shift();

      if (value === undefined) continue;
      if (interrupt(value)) return;

      format(append, value);
    }

    append(false, (len > 1 ? elem.indents : '')
      + (elem.name ? '</' + elem.name + '>' : '')
      + (elem.indent && !end ? '\n' : ''));

    if (end) {
      end();
    }
  }

  function interrupt(value) {
    if (value.interrupt) {
      value.interrupt.append = append;
      value.interrupt.end = proceed;
      value.interrupt = false;
      append(true);
      return true;
    }
    return false;
  }

  append(false, elem.indents
    + (elem.name ? '<' + elem.name : '')
    + (elem.attributes.length ? ' ' + elem.attributes.join(' ') : '')
    + (len ? (elem.name ? '>' : '') : (elem.name ? '/>' : ''))
    + (elem.indent && len > 1 ? '\n' : ''));

  if (!len) {
    return append(false, elem.indent ? '\n' : '');
  }

  if (!interrupt(elem)) {
    proceed();
  }
}

function attribute(key, value) {
  return key + '=' + '"' + escapeForXML(value) + '"';
}

module.exports = xml;
module.exports.element = module.exports.Element = element;