var Log = function() {
  this.autoCloseFold = true;
  this.listeners = [];
  this.renderer = new Log.Renderer;
  this.children = new Log.Nodes(this);
  this.parts = {};
  this.folds = new Log.Folds(this);
  this.times = new Log.Times(this);
  return this;
};

Log.extend = function(one, other) {
  var name;
  for (name in other) {
    one[name] = other[name];
  }
  return one;
};

Log.extend(Log, {
  DEBUG: true,
  SLICE: 500,
  TIMEOUT: 25,
  FOLD: /fold:(start|end):([\w_\-\.]+)/,
  TIME: /time:(start|end):([\w_\-\.]+):?([\w_\-\.\=\,]*)/,
  create: function(options) {
    var listener, log, _i, _len, _ref;
    options || (options = {});
    log = new Log();
    if (options.limit) {
      log.listeners.push(log.limit = new Log.Limit(options.limit));
    }
    _ref = options.listeners || [];
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      listener = _ref[_i];
      log.listeners.push(listener);
    }
    return log;
  }
});

var newLineAtTheEndRegexp, newLineRegexp, rRegexp, removeCarriageReturns;

Log.Node = function(id, num) {
  this.id = id;
  this.num = num;
  this.key = Log.Node.key(this.id);
  this.children = new Log.Nodes(this);
  return this;
};

Log.extend(Log.Node, {
  key: function(id) {
    if (id) {
      return id.split('-').map(function(i) {
        return '000000'.concat(i).slice(-6);
      }).join('');
    }
  }
});

Log.extend(Log.Node.prototype, {
  addChild: function(node) {
    return this.children.add(node);
  },
  remove: function() {
    this.log.remove(this.element);
    return this.parent.children.remove(this);
  }
});

Object.defineProperty(Log.Node.prototype, 'log', {
  get: function() {
    var _ref;
    return this._log || (this._log = ((_ref = this.parent) != null ? _ref.log : void 0) || this.parent);
  }
});

Object.defineProperty(Log.Node.prototype, 'firstChild', {
  get: function() {
    return this.children.first;
  }
});

Object.defineProperty(Log.Node.prototype, 'lastChild', {
  get: function() {
    return this.children.last;
  }
});

Log.Nodes = function(parent) {
  if (parent) {
    this.parent = parent;
  }
  this.items = [];
  this.index = {};
  return this;
};

Log.extend(Log.Nodes.prototype, {
  add: function(item) {
    var ix, next, prev, _ref, _ref1;
    ix = this.position(item) || 0;
    this.items.splice(ix, 0, item);
    if (this.parent) {
      item.parent = this.parent;
    }
    prev = function(item) {
      while (item && !item.children.last) {
        item = item.prev;
      }
      return item != null ? item.children.last : void 0;
    };
    next = function(item) {
      while (item && !item.children.first) {
        item = item.next;
      }
      return item != null ? item.children.first : void 0;
    };
    if (item.prev = this.items[ix - 1] || prev((_ref = this.parent) != null ? _ref.prev : void 0)) {
      item.prev.next = item;
    }
    if (item.next = this.items[ix + 1] || next((_ref1 = this.parent) != null ? _ref1.next : void 0)) {
      item.next.prev = item;
    }
    return item;
  },
  remove: function(item) {
    this.items.splice(this.items.indexOf(item), 1);
    if (item.next) {
      item.next.prev = item.prev;
    }
    if (item.prev) {
      item.prev.next = item.next;
    }
    if (this.items.length === 0) {
      return this.parent.remove();
    }
  },
  position: function(item) {
    var ix, _i, _ref;
    for (ix = _i = _ref = this.items.length - 1; _i >= 0; ix = _i += -1) {
      if (this.items[ix].key < item.key) {
        return ix + 1;
      }
    }
  },
  indexOf: function() {
    return this.items.indexOf.apply(this.items, arguments);
  },
  slice: function() {
    return this.items.slice.apply(this.items, arguments);
  },
  each: function(func) {
    return this.items.slice().forEach(func);
  },
  map: function(func) {
    return this.items.map(func);
  }
});

Object.defineProperty(Log.Nodes.prototype, 'first', {
  get: function() {
    return this.items[0];
  }
});

Object.defineProperty(Log.Nodes.prototype, 'last', {
  get: function() {
    return this.items[this.length - 1];
  }
});

Object.defineProperty(Log.Nodes.prototype, 'length', {
  get: function() {
    return this.items.length;
  }
});

Log.Part = function(id, num, string) {
  Log.Node.apply(this, arguments);
  this.string = string || '';
  this.string = this.string.replace(/\033\[1000D/gm, '\r');
  this.string = this.string.replace(/\r+\n/gm, '\n');
  this.strings = this.string.split(/^/gm) || [];
  this.slices = ((function() {
    var _results;
    _results = [];
    while (this.strings.length > 0) {
      _results.push(this.strings.splice(0, Log.SLICE));
    }
    return _results;
  }).call(this));
  return this;
};

Log.extend(Log.Part, {
  create: function(log, num, string) {
    var part;
    part = new Log.Part(num.toString(), num, string);
    log.addChild(part);
    return part.process(0, -1);
  }
});

Log.Part.prototype = Log.extend(new Log.Node, {
  remove: function() {},
  process: function(slice, num) {
    var node, span, spans, string, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3, _ref4,
      _this = this;
    _ref = this.slices[slice] || [];
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      string = _ref[_i];
      if ((_ref1 = this.log.limit) != null ? _ref1.limited : void 0) {
        return;
      }
      spans = [];
      _ref2 = Log.Deansi.apply(string);
      for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
        node = _ref2[_j];
        span = Log.Span.create(this, "" + this.id + "-" + (num += 1), num, node.text, node["class"]);
        span.render();
        spans.push(span);
      }
      if ((_ref3 = spans[0]) != null ? (_ref4 = _ref3.line) != null ? _ref4.cr : void 0 : void 0) {
        spans[0].line.clear();
      }
    }
    if (!(slice >= this.slices.length - 1)) {
      return setTimeout((function() {
        return _this.process(slice + 1, num);
      }), Log.TIMEOUT);
    }
  }
});

newLineAtTheEndRegexp = new RegExp("\n$");

newLineRegexp = new RegExp("\n");

rRegexp = new RegExp("\r");

removeCarriageReturns = function(string) {
  var index;
  index = string.lastIndexOf("\r");
  if (index === -1) {
    return string;
  }
  return string.substr(index + 1);
};

Log.Span = function(id, num, text, classes) {
  var fold, time, _ref;
  Log.Node.apply(this, arguments);
  if (fold = text.match(Log.FOLD)) {
    this.fold = true;
    this.event = fold[1];
    this.text = this.name = fold[2];
  } else if (time = text.match(Log.TIME)) {
    this.time = true;
    this.event = time[1];
    this.name = time[2];
    this.stats = time[3];
  } else {
    this.text = text;
    this.text = removeCarriageReturns(this.text);
    this.text = this.text.replace(newLineAtTheEndRegexp, '');
    this.nl = !!((_ref = text[text.length - 1]) != null ? _ref.match(newLineRegexp) : void 0);
    this.cr = !!text.match(rRegexp);
    this["class"] = this.cr && ['clears'] || classes;
  }
  return this;
};

Log.extend(Log.Span, {
  create: function(parent, id, num, text, classes) {
    var span;
    span = new Log.Span(id, num, text, classes);
    parent.addChild(span);
    return span;
  },
  render: function(parent, id, num, text, classes) {
    var span;
    span = this.create(parent, id, num, text, classes);
    return span.render();
  }
});

Log.Span.prototype = Log.extend(new Log.Node, {
  render: function() {
    var tail;
    if (this.time && this.event === 'end' && this.prev) {
      if (Log.DEBUG) {
        console.log("S.0 insert " + this.id + " after prev " + this.prev.id);
      }
      this.nl = this.prev.nl;
      this.log.insert(this.data, {
        after: this.prev.element
      });
      this.line = this.prev.line;
    } else if (!this.fold && this.prev && !this.prev.fold && !this.prev.nl) {
      if (Log.DEBUG) {
        console.log("S.1 insert " + this.id + " after prev " + this.prev.id);
      }
      this.log.insert(this.data, {
        after: this.prev.element
      });
      this.line = this.prev.line;
    } else if (!this.fold && this.next && !this.next.fold && !this.next.time) {
      if (Log.DEBUG) {
        console.log("S.2 insert " + this.id + " before next " + this.next.id);
      }
      this.log.insert(this.data, {
        before: this.next.element
      });
      this.line = this.next.line;
    } else {
      this.line = Log.Line.create(this.log, [this]);
      this.line.render();
    }
    if (this.nl && (tail = this.tail).length > 0) {
      this.split(tail);
    }
    if (this.time) {
      return this.log.times.add(this);
    }
  },
  remove: function() {
    Log.Node.prototype.remove.apply(this);
    if (this.line) {
      return this.line.remove(this);
    }
  },
  split: function(spans) {
    var line, span, _i, _len;
    if (Log.DEBUG) {
      console.log("S.4 split [" + (spans.map(function(span) {
        return span.id;
      }).join(', ')) + "]");
    }
    for (_i = 0, _len = spans.length; _i < _len; _i++) {
      span = spans[_i];
      this.log.remove(span.element);
    }
    line = Log.Line.create(this.log, spans);
    line.render();
    if (line.cr) {
      return line.clear();
    }
  },
  clear: function() {
    if (this.prev && this.isSibling(this.prev) && this.isSequence(this.prev)) {
      this.prev.clear();
      return this.prev.remove();
    }
  },
  isSequence: function(other) {
    return this.parent.num - other.parent.num === this.log.children.indexOf(this.parent) - this.log.children.indexOf(other.parent);
  },
  isSibling: function(other) {
    var _ref, _ref1;
    return ((_ref = this.element) != null ? _ref.parentNode : void 0) === ((_ref1 = other.element) != null ? _ref1.parentNode : void 0);
  },
  siblings: function(type) {
    var siblings, span;
    siblings = [];
    while ((span = (span || this)[type]) && this.isSibling(span)) {
      siblings.push(span);
    }
    return siblings;
  }
});

Object.defineProperty(Log.Span.prototype, 'data', {
  get: function() {
    return {
      id: this.id,
      type: 'span',
      text: this.text,
      "class": this["class"],
      time: this.time
    };
  }
});

Object.defineProperty(Log.Span.prototype, 'line', {
  get: function() {
    return this._line;
  },
  set: function(line) {
    if (this.line) {
      this.line.remove(this);
    }
    this._line = line;
    if (this.line) {
      return this.line.add(this);
    }
  }
});

Object.defineProperty(Log.Span.prototype, 'element', {
  get: function() {
    return document.getElementById(this.id);
  }
});

Object.defineProperty(Log.Span.prototype, 'head', {
  get: function() {
    return this.siblings('prev').reverse();
  }
});

Object.defineProperty(Log.Span.prototype, 'tail', {
  get: function() {
    return this.siblings('next');
  }
});

Log.Line = function(log) {
  this.log = log;
  this.spans = [];
  return this;
};

Log.extend(Log.Line, {
  create: function(log, spans) {
    var line, span, _i, _len;
    if ((span = spans[0]) && span.fold) {
      line = new Log.Fold(log, span.event, span.name);
    } else {
      line = new Log.Line(log);
    }
    for (_i = 0, _len = spans.length; _i < _len; _i++) {
      span = spans[_i];
      span.line = line;
    }
    return line;
  }
});

Log.extend(Log.Line.prototype, {
  add: function(span) {
    var ix;
    if (span.cr) {
      this.cr = true;
    }
    if (this.spans.indexOf(span) > -1) {

    } else if ((ix = this.spans.indexOf(span.prev)) > -1) {
      return this.spans.splice(ix + 1, 0, span);
    } else if ((ix = this.spans.indexOf(span.next)) > -1) {
      return this.spans.splice(ix, 0, span);
    } else {
      return this.spans.push(span);
    }
  },
  remove: function(span) {
    var ix;
    if ((ix = this.spans.indexOf(span)) > -1) {
      return this.spans.splice(ix, 1);
    }
  },
  render: function() {
    var fold;
    if ((fold = this.prev) && fold.event === 'start' && fold.active) {
      if (this.next && !this.next.fold) {
        if (Log.DEBUG) {
          console.log("L.0 insert " + this.id + " before next " + this.next.id);
        }
        return this.element = this.log.insert(this.data, {
          before: this.next.element
        });
      } else {
        if (Log.DEBUG) {
          console.log("L.0 insert " + this.id + " into fold " + fold.id);
        }
        fold = this.log.folds.folds[fold.name].fold;
        return this.element = this.log.insert(this.data, {
          into: fold
        });
      }
    } else if (this.prev) {
      if (Log.DEBUG) {
        console.log("L.1 insert " + this.spans[0].id + " after prev " + this.prev.id);
      }
      return this.element = this.log.insert(this.data, {
        after: this.prev.element
      });
    } else if (this.next) {
      if (Log.DEBUG) {
        console.log("L.2 insert " + this.spans[0].id + " before next " + this.next.id);
      }
      return this.element = this.log.insert(this.data, {
        before: this.next.element
      });
    } else {
      if (Log.DEBUG) {
        console.log("L.3 insert " + this.spans[0].id + " into #log");
      }
      return this.element = this.log.insert(this.data);
    }
  },
  clear: function() {
    var cr, _i, _len, _ref, _results;
    _ref = this.crs;
    _results = [];
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      cr = _ref[_i];
      _results.push(cr.clear());
    }
    return _results;
  }
});

Object.defineProperty(Log.Line.prototype, 'id', {
  get: function() {
    var _ref;
    return (_ref = this.spans[0]) != null ? _ref.id : void 0;
  }
});

Object.defineProperty(Log.Line.prototype, 'data', {
  get: function() {
    return {
      type: 'paragraph',
      nodes: this.nodes
    };
  }
});

Object.defineProperty(Log.Line.prototype, 'nodes', {
  get: function() {
    return this.spans.map(function(span) {
      return span.data;
    });
  }
});

Object.defineProperty(Log.Line.prototype, 'prev', {
  get: function() {
    var _ref;
    return (_ref = this.spans[0].prev) != null ? _ref.line : void 0;
  }
});

Object.defineProperty(Log.Line.prototype, 'next', {
  get: function() {
    var _ref;
    return (_ref = this.spans[this.spans.length - 1].next) != null ? _ref.line : void 0;
  }
});

Object.defineProperty(Log.Line.prototype, 'crs', {
  get: function() {
    return this.spans.filter(function(span) {
      return span.cr;
    });
  }
});

Log.Fold = function(log, event, name) {
  Log.Line.apply(this, arguments);
  this.fold = true;
  this.event = event;
  this.name = name;
  return this;
};

Log.Fold.prototype = Log.extend(new Log.Line, {
  render: function() {
    var element, _ref;
    if (this.prev && this.prev.element) {
      if (Log.DEBUG) {
        console.log("F.1 insert " + this.id + " after prev " + this.prev.id);
      }
      element = this.prev.element;
      this.element = this.log.insert(this.data, {
        after: element
      });
    } else if (this.next) {
      if (Log.DEBUG) {
        console.log("F.2 insert " + this.id + " before next " + this.next.id);
      }
      element = this.next.element || this.next.element.parentNode;
      this.element = this.log.insert(this.data, {
        before: element
      });
    } else {
      if (Log.DEBUG) {
        console.log("F.3 insert " + this.id);
      }
      this.element = this.log.insert(this.data);
    }
    if (this.span.next && ((_ref = this.span.prev) != null ? _ref.isSibling(this.span.next) : void 0)) {
      this.span.prev.split([this.span.next].concat(this.span.next.tail));
    }
    return this.active = this.log.folds.add(this.data);
  }
});

Object.defineProperty(Log.Fold.prototype, 'id', {
  get: function() {
    return "fold-" + this.event + "-" + this.name;
  }
});

Object.defineProperty(Log.Fold.prototype, 'span', {
  get: function() {
    return this.spans[0];
  }
});

Object.defineProperty(Log.Fold.prototype, 'data', {
  get: function() {
    return {
      type: 'fold',
      id: this.id,
      event: this.event,
      name: this.name
    };
  }
});

Log.prototype = Log.extend(new Log.Node, {
  set: function(num, string) {
    if (this.parts[num]) {
      return console.log("part " + num + " exists");
    } else {
      this.parts[num] = true;
      return Log.Part.create(this, num, string);
    }
  },
  insert: function(data, pos) {
    this.trigger('insert', data, pos);
    return this.renderer.insert(data, pos);
  },
  remove: function(node) {
    this.trigger('remove', node);
    return this.renderer.remove(node);
  },
  hide: function(node) {
    this.trigger('hide', node);
    return this.renderer.hide(node);
  },
  trigger: function() {
    var args, ix, listener, _i, _len, _ref, _results;
    args = [this].concat(Array.prototype.slice.apply(arguments));
    _ref = this.listeners;
    _results = [];
    for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) {
      listener = _ref[ix];
      _results.push(listener.notify.apply(listener, args));
    }
    return _results;
  }
});

Log.Listener = function() {};

Log.extend(Log.Listener.prototype, {
  notify: function(log, event) {
    if (this[event]) {
      return this[event].apply(this, [log].concat(Array.prototype.slice.call(arguments, 2)));
    }
  }
});

Log.Folds = function(log) {
  this.log = log;
  this.folds = {};
  return this;
};

Log.extend(Log.Folds.prototype, {
  add: function(data) {
    var fold, _base, _name;
    fold = (_base = this.folds)[_name = data.name] || (_base[_name] = new Log.Folds.Fold);
    fold.receive(data, {
      autoCloseFold: this.log.autoCloseFold
    });
    return fold.active;
  }
});

Log.Folds.Fold = function() {
  return this;
};

Log.extend(Log.Folds.Fold.prototype, {
  receive: function(data, options) {
    this[data.event] = data.id;
    if (this.start && this.end && !this.active) {
      return this.activate(options);
    }
  },
  activate: function(options) {
    var fragment, nextSibling, node, parentNode, toRemove, _i, _len, _ref;
    options || (options = {});
    if (Log.DEBUG) {
      console.log("F.n - activate " + this.start);
    }
    toRemove = this.fold.parentNode;
    parentNode = toRemove.parentNode;
    nextSibling = toRemove.nextSibling;
    parentNode.removeChild(toRemove);
    fragment = document.createDocumentFragment();
    _ref = this.nodes;
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      node = _ref[_i];
      fragment.appendChild(node);
    }
    this.fold.appendChild(fragment);
    parentNode.insertBefore(toRemove, nextSibling);
    this.fold.setAttribute('class', this.classes(options['autoCloseFold']));
    return this.active = true;
  },
  classes: function(autoCloseFold) {
    var classes;
    classes = this.fold.getAttribute('class').split(' ');
    classes.push('fold');
    if (!autoCloseFold) {
      classes.push('open');
    }
    if (this.fold.childNodes.length > 2) {
      classes.push('active');
    }
    return classes.join(' ');
  }
});

Object.defineProperty(Log.Folds.Fold.prototype, 'fold', {
  get: function() {
    return this._fold || (this._fold = document.getElementById(this.start));
  }
});

Object.defineProperty(Log.Folds.Fold.prototype, 'nodes', {
  get: function() {
    var node, nodes;
    node = this.fold;
    nodes = [];
    while ((node = node.nextSibling) && node.id !== this.end) {
      nodes.push(node);
    }
    return nodes;
  }
});

Log.Times = function(log) {
  this.log = log;
  this.times = {};
  return this;
};

Log.extend(Log.Times.prototype, {
  add: function(node) {
    var time, _base, _name;
    time = (_base = this.times)[_name = node.name] || (_base[_name] = new Log.Times.Time);
    return time.receive(node);
  },
  duration: function(name) {
    if (this.times[name]) {
      return this.times[name].duration;
    }
  }
});

Log.Times.Time = function() {
  return this;
};

Log.extend(Log.Times.Time.prototype, {
  receive: function(node) {
    this[node.event] = node;
    if (Log.DEBUG) {
      console.log("T.0 - " + node.event + " " + node.name);
    }
    if (this.start && this.end) {
      return this.finish();
    }
  },
  finish: function() {
    var element;
    if (Log.DEBUG) {
      console.log("T.1 - finish " + this.start.name);
    }
    element = document.getElementById(this.start.id);
    if (element) {
      return this.update(element);
    }
  },
  update: function(element) {
    element.setAttribute('class', 'duration');
    element.setAttribute('title', "This command finished after " + this.duration + " seconds.");
    return element.lastChild.nodeValue = "" + this.duration + "s";
  }
});

Object.defineProperty(Log.Times.Time.prototype, 'duration', {
  get: function() {
    var duration;
    duration = this.stats.duration / 1000 / 1000 / 1000;
    return duration.toFixed(2);
  }
});

Object.defineProperty(Log.Times.Time.prototype, 'stats', {
  get: function() {
    var stat, stats, _i, _len, _ref;
    if (!(this.end && this.end.stats)) {
      return {};
    }
    stats = {};
    _ref = this.end.stats.split(',');
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      stat = _ref[_i];
      stat = stat.split('=');
      stats[stat[0]] = stat[1];
    }
    return stats;
  }
});

Log.Deansi = {
  CLEAR_ANSI: /(?:\033)(?:\[0?c|\[[0356]n|\[7[lh]|\[\?25[lh]|\(B|H|\[(?:\d+(;\d+){,2})?G|\[(?:[12])?[JK]|[DM]|\[0K)/gm,
  apply: function(string) {
    var nodes,
      _this = this;
    if (!string) {
      return [];
    }
    string = string.replace(this.CLEAR_ANSI, '');
    nodes = ansiparse(string).map(function(part) {
      return _this.node(part);
    });
    return nodes;
  },
  node: function(part) {
    var classes, node;
    node = {
      type: 'span',
      text: part.text
    };
    if (classes = this.classes(part)) {
      node["class"] = classes.join(' ');
    }
    return node;
  },
  classes: function(part) {
    var result;
    result = [];
    result = result.concat(this.colors(part));
    if (result.length > 0) {
      return result;
    }
  },
  colors: function(part) {
    var colors;
    colors = [];
    if (part.foreground) {
      colors.push(part.foreground);
    }
    if (part.background) {
      colors.push("bg-" + part.background);
    }
    if (part.bold) {
      colors.push('bold');
    }
    if (part.italic) {
      colors.push('italic');
    }
    if (part.underline) {
      colors.push('underline');
    }
    return colors;
  },
  hidden: function(part) {
    if (part.text.match(/\r/)) {
      part.text = part.text.replace(/^.*\r/gm, '');
      return true;
    }
  }
};

Log.Limit = function(max_lines) {
  this.max_lines = max_lines || 1000;
  return this;
};

Log.Limit.prototype = Log.extend(new Log.Listener, {
  count: 0,
  insert: function(log, node, pos) {
    if (node.type === 'paragraph' && !node.hidden) {
      return this.count += 1;
    }
  }
});

Object.defineProperty(Log.Limit.prototype, 'limited', {
  get: function() {
    return this.count >= this.max_lines;
  }
});

Log.Renderer = function() {
  this.frag = document.createDocumentFragment();
  this.para = this.createParagraph();
  this.span = this.createSpan();
  this.text = document.createTextNode('');
  this.fold = this.createFold();
  return this;
};

Log.extend(Log.Renderer.prototype, {
  insert: function(data, pos) {
    var after, before, into, node;
    node = this.render(data);
    if (into = pos != null ? pos.into : void 0) {
      if (typeof into === 'String') {
        into = document.getElementById(pos != null ? pos.into : void 0);
      }
      if (pos != null ? pos.prepend : void 0) {
        this.prependTo(node, into);
      } else {
        this.appendTo(node, into);
      }
    } else if (after = pos != null ? pos.after : void 0) {
      if (typeof after === 'String') {
        after = document.getElementById(pos);
      }
      this.insertAfter(node, after);
    } else if (before = pos != null ? pos.before : void 0) {
      if (typeof before === 'String') {
        before = document.getElementById(pos != null ? pos.before : void 0);
      }
      this.insertBefore(node, before);
    } else {
      this.insertBefore(node);
    }
    return node;
  },
  hide: function(node) {
    node.setAttribute('class', this.addClass(node.getAttribute('class'), 'hidden'));
    return node;
  },
  remove: function(node) {
    if (node) {
      node.parentNode.removeChild(node);
    }
    return node;
  },
  render: function(data) {
    var frag, node, type, _i, _len;
    if (data instanceof Array) {
      frag = this.frag.cloneNode(true);
      for (_i = 0, _len = data.length; _i < _len; _i++) {
        node = data[_i];
        node = this.render(node);
        if (node) {
          frag.appendChild(node);
        }
      }
      return frag;
    } else {
      data.type || (data.type = 'paragraph');
      type = data.type[0].toUpperCase() + data.type.slice(1);
      return this["render" + type](data);
    }
  },
  renderParagraph: function(data) {
    var node, para, type, _i, _len, _ref;
    para = this.para.cloneNode(true);
    if (data.id) {
      para.setAttribute('id', data.id);
    }
    if (data.hidden) {
      para.setAttribute('style', 'display: none;');
    }
    _ref = data.nodes || [];
    for (_i = 0, _len = _ref.length; _i < _len; _i++) {
      node = _ref[_i];
      type = node.type[0].toUpperCase() + node.type.slice(1);
      node = this["render" + type](node);
      para.appendChild(node);
    }
    return para;
  },
  renderFold: function(data) {
    var fold;
    fold = this.fold.cloneNode(true);
    fold.setAttribute('id', data.id || ("fold-" + data.event + "-" + data.name));
    fold.setAttribute('class', "fold-" + data.event);
    if (data.event === 'start') {
      fold.lastChild.lastChild.nodeValue = data.name;
    } else {
      fold.removeChild(fold.lastChild);
    }
    return fold;
  },
  renderSpan: function(data) {
    var span;
    span = this.span.cloneNode(true);
    if (data.id) {
      span.setAttribute('id', data.id);
    }
    if (data["class"]) {
      span.setAttribute('class', data["class"]);
    }
    span.lastChild.nodeValue = data.text || '';
    return span;
  },
  renderText: function(data) {
    var text;
    text = this.text.cloneNode(true);
    text.nodeValue = data.text;
    return text;
  },
  createParagraph: function() {
    var para;
    para = document.createElement('p');
    para.appendChild(document.createElement('a'));
    return para;
  },
  createFold: function() {
    var fold;
    fold = document.createElement('div');
    fold.appendChild(this.createSpan());
    fold.lastChild.setAttribute('class', 'fold-name');
    return fold;
  },
  createSpan: function() {
    var span;
    span = document.createElement('span');
    span.appendChild(document.createTextNode(' '));
    return span;
  },
  insertBefore: function(node, other) {
    var log;
    if (other) {
      return other.parentNode.insertBefore(node, other);
    } else {
      log = document.getElementById('log');
      return log.insertBefore(node, log.firstChild);
    }
  },
  insertAfter: function(node, other) {
    if (other.nextSibling) {
      return this.insertBefore(node, other.nextSibling);
    } else {
      return this.appendTo(node, other.parentNode);
    }
  },
  prependTo: function(node, other) {
    if (other.firstChild) {
      return other.insertBefore(node, other.firstChild);
    } else {
      return appendTo(node, other);
    }
  },
  appendTo: function(node, other) {
    return other.appendChild(node);
  },
  addClass: function(classes, string) {
    if (classes != null ? classes.indexOf(string) : void 0) {
      return;
    }
    if (classes) {
      return "" + classes + " " + string;
    } else {
      return string;
    }
  }
});

window.Log = Log;