/* public interface */

Dabble = new Object;
Dabble.prefix = 'dabble';

Dabble.View = function (entries) {this.entries = entries};
Dabble.Entry = function () {};

Dabble.Schema = function () {};
Dabble.Category = function () {};

Dabble.Field = function () {};

Dabble.Type = function () {};

Dabble.Text = function () {};
Dabble.Text.prototype = new Dabble.Type;
Dabble.Text.prototype.className = 'Text';

Dabble.WebLink = function () {};
Dabble.WebLink.prototype = new Dabble.Type;
Dabble.WebLink.prototype.className = 'WebLink';

Dabble.EmailAddress = function () {};
Dabble.EmailAddress.prototype = new Dabble.Type;
Dabble.EmailAddress.prototype.className = 'EmailAddress';

Dabble.Phone = function () {};
Dabble.Phone.prototype = new Dabble.Type;
Dabble.Phone.prototype.className = 'Phone';

Dabble.Number = function () {};
Dabble.Number.prototype = new Dabble.Type;
Dabble.Number.prototype.className = 'Number';

Dabble.Money = function () {};
Dabble.Money.prototype = new Dabble.Type;
Dabble.Money.prototype.className = 'Money';

Dabble.DateTime = function () {};
Dabble.DateTime.prototype = new Dabble.Type;
Dabble.DateTime.prototype.className = 'DateTime';

Dabble.Duration = function () {};
Dabble.Duration.prototype = new Dabble.Type;
Dabble.Duration.prototype.className = 'Duration';

Dabble.Location = function () {};
Dabble.Location.prototype = new Dabble.Type;
Dabble.Location.prototype.className = 'Location';

Dabble.Choice = function () {};
Dabble.Choice.prototype = new Dabble.Type;
Dabble.Choice.prototype.className = 'Choice';

Dabble.Checkbox = function () {};
Dabble.Checkbox.prototype = new Dabble.Type;
Dabble.Checkbox.prototype.className = 'Checkbox';

Dabble.User = function () {};
Dabble.User.prototype = new Dabble.Type;
Dabble.User.prototype.className = 'User';

Dabble.Attachment = function () {};
Dabble.Attachment.prototype = new Dabble.Type;
Dabble.Attachment.prototype.className = 'Attachment';

Dabble.Link = function () {};
Dabble.Link.prototype = new Dabble.Type;
Dabble.Link.prototype.className = 'Link';

Dabble.List = function () {};
Dabble.List.prototype = new Dabble.Type;
Dabble.List.prototype.className = 'List';

Dabble.addSchema = function(schema)
{
 this.schema = this.adopt(schema);
};

Dabble.addView = function(view)
{
  if (this.views == undefined) this.views = {};
  this.views[view.id] = this.View.adopt(view);;
};

Dabble.field = function(token)
{   
  for (var i=0,j=this.schema.fields.length; i<j; i++)
    if (this.schema.fields[i].isIdentifiedBy(token))
      return this.schema.fields[i];
};

Dabble.view = function(token)
{
  for (var p in this.views)
    if (this.views[p].id == token || this.views[p].name == token)
      return this.views[p];
};

Dabble.writeSubmit = function(label, url, tagID)
{
  var node = new Dabble.Node('input');
  node.at.id = tagID;
  node.at.type = 'submit';
  node.at.value = label ? label : 'Submit';
  node.pre('class', 'submit');
  node.write();

  var form = Dabble.currentScriptNode();
  while (form.tagName != 'FORM') form = form.parentNode;
  form.className = this.pre('form');
  form.action = this.schema.postUrl;
  form.method = 'POST';
  
  if (url) 
  {
    var hidden = new Dabble.Node('input');
    hidden.at.type = 'hidden';
    hidden.at.name = 'redirect';
    hidden.at.value = url;
    form.insertBefore(hidden.dom(), form.firstChild);
  }
};


Dabble.View.prototype.writeTable = function()
{
  var table = new Dabble.Node('table');
  table.pre('class', 'table', 'view-' + this.id);
  var tbody = new Dabble.Node('tbody');
  table.appendChild(tbody);
  for (var i=0,j=this.entries.length; i<j; i++)
    tbody.appendChild(this.entries[i].createRow(i));
  table.write();
};


Dabble.Entry.prototype.writeRow = function(index)
{
  this.createRow(index).write();
};

Dabble.Entry.prototype.writeCell = function(index)
{
  this.createCell(index).write();
};

Dabble.Entry.prototype.writeValue = function(index)
{
  document.write(this.printValue(index));
};

Dabble.Entry.prototype.writeFieldName = function(index)
{
  document.write(this.printFieldName(index))
};

Dabble.Entry.prototype.printValue = function(index)
{
  return this.fields[index].print(this.values[index])
};

Dabble.Entry.prototype.printFieldName = function(index)
{
  return this.fields[index].name
};


Dabble.Field.prototype.isIdentifiedBy = function(token)
{
  if (typeof token == 'number')
    return this.id == token;
  
  if (-1 == token.indexOf("."))
    return this.name == token
  else {
    tokens = token.split('.');
    return this.category.name == tokens[0] && this.name == tokens[1] 
  }
  
};

Dabble.Field.prototype.print = function(value)
{
  return value ? this.type.print(value) : '';
};

Dabble.Field.prototype.writeInput = function()
{
  this.type.writeInput(this.inputAttributes(), arguments);
};

Dabble.Field.prototype.writeLabel = function()
{
  this.createLabel().write();
};



/* private implementation */

if (!window['Node']) {
    window.Node = new Object();
    Node.ELEMENT_NODE = 1;
    Node.ATTRIBUTE_NODE = 2;
    Node.TEXT_NODE = 3;
    Node.CDATA_SECTION_NODE = 4;
    Node.ENTITY_REFERENCE_NODE = 5;
    Node.ENTITY_NODE = 6;
    Node.PROCESSING_INSTRUCTION_NODE = 7;
    Node.COMMENT_NODE = 8;
    Node.DOCUMENT_NODE = 9;
    Node.DOCUMENT_TYPE_NODE = 10;
    Node.DOCUMENT_FRAGMENT_NODE = 11;
    Node.NOTATION_NODE = 12;
};

Dabble.Node = function(name)
{
  this.name = name;
  this.at = new Object;
  this.children = new Array(0);
};

Dabble.Node.prototype.setAttribute = function(name, value)
{
  if (value != '') this.at[name] = value;
};

Dabble.Node.prototype.appendChild = function(node)
{
  if (node != null) this.children.push(node);
};

Dabble.Node.prototype.pre = function(name)
{
  var result = '';
  for (var i=1,j=arguments.length; i<j; i++)
    result += ' ' + Dabble.prefix + '-' + arguments[i];

  this.at[name] = result.slice(1);
};

Dabble.Node.prototype.print = function()
{
  var html = '';
  html += '<' + this.name
  for (p in this.at)
  {
    html += ' ' + p;
    html += '="' + this.at[p] + '"';
  }
  html += '>';
  
  for (var i=0,j=this.children.length; i<j; i++) {
    if ('object' == typeof this.children[i])
      html += this.children[i].print();
    else
      html += this.children[i].toString();
  };
  
  html += '</' + this.name + '>';
  return html;
};

Dabble.Node.prototype.write = function()
{
  document.write(this.print())
};

Dabble.Node.prototype.dom = function()
{
  var dom = document.createElement(this.name);
  
  for (p in this.at)
    dom.setAttribute(p, this.at[p]);
    
  for (var i=0,j=this.children.length; i<j; i++) {
    if ('object' == typeof this.children[i])
      dom.appendChild(this.children[i].dom());
    else
      dom.appendChild(document.createTextNode(this.children[i].toString()));
  };
  
  return dom;
};

Dabble.pre = function()
{
  var result = '';
  for (var i=0,j=arguments.length; i<j; i++)
    result += ' ' + this.prefix + '-' + arguments[i];
  
  return result.slice(1);
};

Dabble.adopt = function adopt(obj) 
{
  var result;
  if (obj != null && typeof obj == 'object') 
  {
    var constructor = obj._class;
    delete obj._class;
    if (constructor && this[constructor]) 
      result = new this[constructor];
    else
      result = obj;
    
    for (var p in obj) result[p] = this.adopt(obj[p]);
  }
  else result = obj;
  return result;
};

Dabble.currentScriptNode = function()
{
  var elements = document.getElementsByTagName('script');
  return elements[elements.length-1];  
};

Dabble.writeSelect = function(attr, labels, values, defaultValue, isMulti)
{
  var attributes, select, option;
  
  select = new Dabble.Node('select');
  select.at = attr;
  if (isMulti) select.at.multiple = 'multiple';
  if (defaultValue == null && ! isMulti)
  {
    option = new Dabble.Node('option');
    option.at.value = '';
    select.appendChild(option);
  }
  for (var i=0,j=labels.length; i<j; i++)
  {
    value = values == undefined ? i : values[i];
    option = new Dabble.Node('option');
    option.at.value = value;
    if (value == defaultValue) option.at.selected = 'selected';
    option.appendChild(labels[i]);
    select.appendChild(option);
  }
   
  select.write();
};

Dabble.View.options = function(v)
{
  var entries;
  if (v instanceof this) entries = v.entries;
  else if (v instanceof Array) entries = v;
  else if (typeof v == 'string') entries = Dabble.views[v].entries;
  else entries = new Array;
  
  labels = new Array(entries.length);
  values = new Array(entries.length);
  for (var i=0,j=entries.length; i<j; i++)
  {
    values[i] = entries[i]._id;
    labels[i] = entries[i]._name
  }
    
  return [labels, values];
};

Dabble.View.adopt = function(view)
{
  var result = new this;
  for (p in view)
    if (p != '_class' && p != 'entries')
      result[p] = Dabble.adopt(view[p]);
    
  result.entries = new Array(view.entries.length);
  for (var i=0,j=view.entries.length; i<j; i++)
    result.entries[i] = Dabble.Entry.adopt(result, view.entries[i])
  
  return result;
};

Dabble.Entry.adopt = function(view, entry)
{
  var result = new this;
  result.fields = new Array(view.fields.length);
  for (var i=0,j=view.fields.length; i<j; i++)
    result.fields[i] = Dabble.field(view.fields[i]);
  for (p in entry) result[p] = entry[p];
  return result;
};

Dabble.Entry.prototype.createRow = function(index)
{
  var field, cell;
  var row = new Dabble.Node('tr');
  var parity = index % 2 ? 'even' : 'odd';
  row.pre('class', 'row-' + parity, 'entry-' + this._id);
  cell = new Dabble.Node('td');
  cell.pre('class', 'field-name', 'type-text');
  cell.appendChild(this._name);
  row.appendChild(cell);
  for (var i=0,j=this.fields.length; i<j; i++) 
  {
    cell = this.createCell(i);
    row.appendChild(cell);
  }
  return row; 
};

Dabble.Entry.prototype.createCell = function(index)
{
  var field = this.fields[index];
  var cell = new Dabble.Node('td');
  cell.setAttribute('class', field.tagClass());
  cell.appendChild(this.printValue(index));
  return cell;
};

Dabble.Field.prototype.inputAttributes = function()
{
  var at = new Object;
  at.name = this.id;
  at.id = this.tagID();
  at['class'] = this.tagClass();
  return at;
};

Dabble.Field.prototype.tagID = function()
{
  return Dabble.pre('input-' + this.id);
};

Dabble.Field.prototype.tagClass = function()
{
	var tagClass = this.type.tagClass ? this.type.tagClass() : 'unknown';
  return Dabble.pre('field-' + this.id) + ' ' + this.type.tagClass();
};

Dabble.Field.prototype.createLabel = function()
{
  var node = new Dabble.Node('label');
  node.pre('id', 'label-' + this.id);
  node.pre('for', 'input-' + this.id);
  node.appendChild(this.name);
  return node;
};

Dabble.Type.prototype.printDefaultValue = function()
{
  return this.defaultValue ? this.defaultValue.toString() : '';
};

Dabble.Type.prototype.tagID = function()
{
  return Dabble.pre('type-' + this.id);
};

Dabble.Type.prototype.tagClass = function()
{
  return Dabble.pre('type-' + this.id, (this.className ? ('type-' + this.className.toLowerCase()) : 'type-unknown'));
};

Dabble.Type.prototype.print = function(value)
{
  return value.toString();
};

Dabble.Type.prototype.writeInput = function(attributes)
{
  attributes.type = 'text';
  attributes.value = this.printDefaultValue();
  var node = new Dabble.Node('input');
  node.at = attributes;
  node.write();
};

Dabble.Text.prototype.print = function(value)
{
  switch (this.format) {
    case 'email': return this.printEmail(value);
    case 'image': return this.printImage(value);
    case 'url': return this.printUrl(value);
    case 'text': return this.printText(value);
    default: return this.printText(value);
  }
};

Dabble.Text.prototype.printEmail = function(value)
{
  var address = value.toString();
  return '<a href="mailto:' + address + '">' + address + '</a>'
};

Dabble.Text.prototype.printImage = function(value)
{
  return '<img src="' + value.toString() + '" />'
};

Dabble.Text.prototype.printUrl = function(value)
{
	var noPrefix = value.match(/\/\/([a-zA-Z\.]+)\//);
	var label = (noPrefix && noPrefix.length > 1) ? noPrefix[1] : value;
  return '<a href="' + value.toString() + '">' + label + '</a>'
};

Dabble.Text.prototype.printText = function(value)
{
  return value.toString().replace(/(\r\|\r|\n)/g, '<br />')
};

Dabble.Text.prototype.writeInput = function(attributes)
{
  lines = Dabble.field(attributes.name).type.lines;
  if(lines != null && lines > 1)
  {
    var input = new Dabble.Node('textarea');
    input.at = attributes;
    input.at.rows = lines;
    input.at.cols = 40;
    input.appendChild(this.defaultValue);
    input.write();
  }
    
  else
    Dabble.Type.prototype.writeInput.call(this, attributes);
};

Dabble.WebLink.prototype.print = function(value)
{
	switch (this.format) {
    case 'image': return this.printImage(value);
    default: return this.printUrl(value);
	}
};

Dabble.WebLink.prototype.printImage = function(value)
{
  return '<img src="' + value.toString() + '" />'
};

Dabble.WebLink.prototype.printUrl = function(value)
{
	var noPrefix = value.match(/\/\/([a-zA-Z\.]+)\//);
	var label = (noPrefix && noPrefix.length > 1) ? noPrefix[1] : value;
  return '<a href="' + value.toString() + '">' + label + '</a>'
};

Dabble.EmailAddress.prototype.print = function(value)
{
  var address = value.toString();
  return '<a href="mailto:' + address + '">' + address + '</a>';
};

Dabble.Number.prototype.print = function(value)
{
  return this.decimalPlaces == undefined ? value : value.toFixed(this.decimalPlaces)
};

Dabble.Money.prototype.print = function(value)
{
	var amount = this.decimalPlaces == undefined ? value.amount : value.amount.toFixed(this.decimalPlaces);
	var currency = value.currency ? value.currency : "";
	return currency + " " + amount;
};

Dabble.DateTime.prototype.print = function(value)
{
  return value.name;
};

Dabble.Duration.prototype.print = function(value)
{
  var seconds = value; 
  var days = Math.floor(seconds / 86400);
  seconds = seconds % 86400; 
  var hours = Math.floor(seconds / 3600);
  seconds = seconds % 3600; 
  var minutes = Math.floor(seconds / 60);
  seconds = seconds % 60; 
  string = '';
  if (days) {string += days + 'd'};
  if (hours) {string += hours + 'h'};
  if (minutes) {string += minutes + 'm'};
  if (seconds || string.length == 0) {string += seconds + 's'};
  return string;
};

Dabble.Location.prototype.print = function(value)
{
  return value.address ? value.address : value.name;
};

Dabble.Location.prototype.writeInput = function(attributes)
{
	var input = new Dabble.Node('textarea');
	input.at = attributes;
	input.at.rows = 4;
	input.at.cols = 30;
	input.appendChild(this.defaultValue);
	input.write();
};

Dabble.Choice.prototype.writeInput = function (attributes)
{
  var defaultIndex = null;
  for (var i=0,j=this.options.length; i<j; i++)
    if (this.options[i] == this.defaultValue) 
    {
        defaultIndex = i;
        break;
    }
  Dabble.writeSelect(attributes, this.options, null, defaultIndex, false)
};


Dabble.User.prototype.writeInput = function (attributes)
{
  Dabble.writeSelect(attributes, this.options, this.options, this.defaultValue, false)
};

Dabble.User.prototype.print = function(value)
{
  var address = value.toString();
  return '<a href="mailto:' + address + '">' + address + '</a>'
};

Dabble.Attachment.prototype.print = function(value)
{
  var content;
  if (undefined == value.thumbnail) content = value.name
  else content = '<img src="' + value.thumbnail + '">';
  
  return '<a href="' + value.url + '">' + content + '</a>'
};

Dabble.Link.prototype.print = function(value)
{
  return value._name;
};

Dabble.Link.prototype.writeInput = function(attributes, args)
{
  var options = Dabble.View.options(args[0]);
  Dabble.writeSelect(attributes, options[0], options[1], null, false);
};

Dabble.List.prototype.print = function(list)
{
  var string;
  string = list.length > 0 ? list[0]._name : '';
  for (var i=1,j=list.length; i<j; i++)
    string += ', ' + list[i]._name;
  return string;
};

Dabble.List.prototype.writeInput = function(attributes, args)
{
  var options = Dabble.View.options(args[0]);
  Dabble.writeSelect(attributes, options[0], options[1], null, true);
};

