if (typeof console === 'undefined') {
  console = {};
  console.log = function (msg) {
    //alert(msg);
  }
}


// ---------------------------------------------------

Iterator = function (obj) {
  var i, chld, len, pub, mth, 
      result  = {}, 
      BIND    = Iterator.bind;

  if (obj instanceof Array) {
    this.children = obj;
  }
  else if (typeof(obj) === 'object' && obj !== null) {
    this.children = [];
    chld = this.children;
    for (i in obj) {
      if (obj.hasOwnProperty(i)) {
        chld[chld.length] = obj[i];
      }
    }
  }

//console.log([typeof obj, ' => (', (this.children !== null ? this.children.length : 'null'), ')'].join(''));

  pub = Iterator.publics;
  for (i = 0, len = pub.length; i < len; ++i) {
    mth = pub[i];
    result[mth] = BIND(this[mth], this);
  }
  return result;
};

Iterator.publics = ['next', 'hasNext', 'rewind'];
Iterator.prototype.index    = -1;
Iterator.prototype.children = null;

Iterator.prototype.next = function () {
  if (this.children === null) {
    return null;
  }
  var CHLD    = this.children,
      len     = CHLD.length,
      result  = null;
  ++this.index;
  if (len !== 0 && this.index < len) {
    result = CHLD[this.index];
  }
  return result;
};

Iterator.prototype.hasNext = function () {
  if (this.children === null) {
    return false;
  }
  var result  = false,
      len     = this.children.length;
  if (len !== 0 && this.index < (len - 1)) {
    result = true;
  }
  return result;
};

Iterator.prototype.rewind = function () {
  this.index = -1;
  var it, 
      i     = 0, 
      CHLD  = this.children,
      len   = CHLD.length;
  if (len) {
    for (;i < len; ++i) {
      it = CHLD[i].iterator;
      if (it) {
        it.rewind();
      }
    }
  }
};

Iterator.bind = function (func, context) {
  return function () {
    return func.apply(context, arguments);
  };
};

// ---------------------------------------------------

var Destination = function () {};

Destination.prototype.accept = function (visitor) {
  visitor.visit(this);
};

Destination.prototype.iterator = null;

Destination.prototype.getIterator = function () {
  return this.iterator;
};

Destination.prototype.toString = function () {
  throw new Error('each implementation needs to implement this method.');
};

// ---------------------------------------------------

var TextNodeDest = function (root) {
  this.root = root;
  this.iterator = new Iterator(this.root.toString());
};

TextNodeDest.prototype = new Destination();
TextNodeDest.prototype.toString = function () {
  return this.root;
};

// ---------------------------------------------------

var DOMNodeDest = function (root) {
  this.root     = root;
  this.index    = -1;
  this.children = [];

  var node, tmp,
      ROOT    = this.root,
      i       = 0, 
      CHLD    = ROOT.childNodes,
      len     = CHLD.length,
      mychld  = this.children;
  for (; i < len; ++i) {
    node = CHLD[i];
    if (node.tagName === undefined && node.nodeName.indexOf('#') === 0) {
      tmp = new TextNodeDest(node);
    }
    else {
      tmp = new DOMNodeDest(node);
    }
    mychld[mychld.length] = tmp;
  }

  this.iterator = new Iterator(this.children);
};

DOMNodeDest.prototype = new Destination();

DOMNodeDest.prototype.toString = function () {
  return this.root;
};

// ---------------------------------------------------

var Visitor = function () {};
Visitor.prototype.visit = function (obj) {
  console.log('visit: ' + obj.toString());

  var it = obj.getIterator();
  while (it.hasNext()) {
    it.next().accept(this);
  }
};
