/**
 * Copyright (c) 2008 Google Inc.
 *
 * You are free to copy and use this sample.
 * License can be found here: http://code.google.com/apis/ajaxsearch/faq/#license
*/

/**
 * WhatIWant()
 *
 * This is the core application object for this sample app.
 * It maintains the various containers, forms, and controls used
 * to build the simulated wish list application.
*/
function WhatIWant() {

  // get address of the core app sections
  this.itemsContainer = document.getElementById("itemsContainer");
  this.searchContainer = document.getElementById("searchContainer");
  this.newItemForm = document.getElementById("newItemForm");
  this.newItemInput = document.getElementById("newItemInput");
  this.newItemAddButton = document.getElementById("newItemAddButton");

  // bind in a search control with Amazon.com and Web Search
  this.searchControl = new google.search.SearchControl();


  // site restricted web search with custom label
  var siteSearch = new google.search.WebSearch();
  siteSearch.setUserDefinedLabel("Amazon.com");
  siteSearch.setSiteRestriction("amazon.com");
  this.searchControl.addSearcher(siteSearch);

  var webSearch = new google.search.WebSearch();
  this.searchControl.addSearcher(webSearch);
  this.searchControl.setOnKeepCallback( this, WhatIWant.prototype.onKeep, "I want this..." );
  this.searchControl.draw(this.searchContainer);

  // bind to the new item form
  this.newItemAddButton.onclick = method_closure(this, WhatIWant.prototype.onAddButton, []);
  this.newItemForm.onsubmit = method_closure(this, WhatIWant.prototype.onAddButton, []);

  this.visibleItemCount = 0;
  this.currentItem = null;

  this.switchToNewItemForm();
}

/**
 * .onAddButton()
 *
 * Called when a new item is added by pressing the "this is what I want link"
 * or by "submitting" the form. It always returns false to indicate that
 * the form should not be submitted since all the side effects are
 * fully contained.
 *
 * If the input value is set, then a new item is added, this form is
 * closed, and the search control is opened.
*/
WhatIWant.prototype.onAddButton = function() {
  if (this.newItemInput.value) {
    this.addItem(this.newItemInput.value);
    this.switchToSearchControl(this.newItemInput.value);
  }
  return false;
}

/**
 * .switchToSearchControl()
 *
 * Called when a new item is added, or opened for edit.
 * Its purpose is to toggle off the add new item form, and enable the
 * search control, optionally executing a search in the process.
*/
WhatIWant.prototype.switchToSearchControl = function(opt_query) {
  if ( opt_query ) {
    this.searchControl.execute(opt_query);
  }
  cssSetClass(this.newItemForm, "wiw-new-item-form-closed");
  cssSetClass(this.searchContainer, "wiw-new-item-form-closed");
}


/**
 * .switchToNewItemForm()
 *
 * Called when an item is saved/deleted to switch to new item
 * creation mode.
*/
WhatIWant.prototype.switchToNewItemForm = function() {
  this.newItemInput.value = "";
  cssSetClass(this.newItemForm, "wiw-new-item-form-opened");
  cssSetClass(this.searchContainer, "wiw-new-item-form-opened");
  this.newItemInput.focus();
}

/**
 * .addItem()
 *
 * A new item is added to the list and opened for edit.
 * Note as a side effect of opening for edit, a search
 * is initiated using the title of the item.
*/
WhatIWant.prototype.addItem = function(opt_title) {

  // create the new item, add it to the list
  // set it as current
  // add it to the dom in open mode
  var item = new Item(opt_title);
  this.currentItem = item;
  prependNode(this.itemsContainer, item.root);
  item.open();

  this.visibleItemCount++;
  return item;
}

/**
 * .onKeep()
 *
 * Global on keep handler which simply propogates the event to the current item.
*/
WhatIWant.prototype.onKeep = function(result) {
  this.currentItem.addResult(result);
}

/**
 * Item()
 *
 * This is the core Item class. An Item contains all of the various
 * content and controls needed to manage its existence, as well as the
 * collection of results contained within.
 *
 * The .root property of an item is its html DOM representation
*/
function Item(opt_title) {
  this.closed = true;
  this.detailsClosed = true;
  this.deleted_ = false;

  // the root
  this.root = createDiv(null,"wiw-item");

  // the main form of an item
  this.form_ = createForm("wiw-item-form");

  // title is both a text input control and static div
  // depending on the open/closed mode of the item (css controlled)
  this.input_ = createTextInput("wiw-title");
  this.staticTitle_ = createDiv("nothing", "wiw-static-title");

  // along with the title is a set of controls that govern
  // edit, save, and delete. Again, these are css controlled
  // based on the mode of the item
  this.edit_ = createDiv("edit", "wiw-title-control wiw-edit");
  this.save_ = createDiv("save", "wiw-title-control wiw-save");
  this.delete_ = createDiv("delete", "wiw-title-control wiw-delete");

  // clipped search results end up in this region where we have a header, and
  // a sibling results container. The results heaver contains a twiddle for
  // exposing partial or full detail on the various search results
  this.resultsHeader_ = createDiv("show details", "wiw-details wiw-details-closed");
  this.resultsContainer_ = createDiv(null, "wiw-results wiw-result-details-partial");

  // the title is housed within a two cell table to allow us to easily control
  // placement of the controls
  var table = createTable("wiw-title-table");
  var row = createTableRow(table);
  var titleCell = createTableCell(row, "wiw-title-cell");
  var controlCell = createTableCell(row, "wiw-control-cell");
  titleCell.appendChild(this.input_);
  titleCell.appendChild(this.staticTitle_);
  controlCell.appendChild(this.edit_);
  controlCell.appendChild(this.save_);
  controlCell.appendChild(this.delete_);

  // bind title and results (header/container) into form
  this.form_.appendChild(table);
  this.form_.appendChild(this.resultsHeader_);
  this.form_.appendChild(this.resultsContainer_);

  // bind in the various controls
  this.form_.onsubmit = method_closure(this, Item.prototype.onSave, [true]);
  this.save_.onclick = method_closure(this, Item.prototype.onSave, [false]);
  this.edit_.onclick = method_closure(this, Item.prototype.onEdit, []);
  this.delete_.onclick = method_closure(this, Item.prototype.onDelete, []);
  this.resultsHeader_.onclick = method_closure(this, Item.prototype.onDetailsChange, []);

  if (opt_title) {
    this.input_.value = opt_title;
  }

  // the root is set. caller can append this to the active document
  this.root.appendChild(this.form_);
}

/**
 * .close()
 *
 * Close an item means to transition from edit mode to read only mode.
*/
Item.prototype.close = function() {
  if (this.deleted_) {
    return;
  }
  this.closed = true;
  this.detailsClosed = true;
  this.staticTitle_.innerHTML = this.input_.value != "" ? this.input_.value : "undefined";
  cssSetClass(this.root, "wiw-item wiw-item-closed");
  cssSetClass(this.resultsHeader_, "wiw-details wiw-details-closed");
  cssSetClass(this.resultsContainer_, "wiw-results wiw-result-details-partial");
  this.resultsHeader_.innerHTML = "show details";
}

/**
 * .edit()
 *
 * Opposite of close, make an item read/write
 * If the item vas a valid title, execute a search as well
*/
Item.prototype.open = function() {
  this.closed = false;
  this.detailsClosed = false;
  cssSetClass(this.root, "wiw-item wiw-item-opened");
  cssSetClass(this.resultsHeader_, "wiw-details wiw-details-opened");
  cssSetClass(this.resultsContainer_, "wiw-results wiw-result-details-full");
  this.resultsHeader_.innerHTML = "hide details";
  if ( this.input_.value != "" ) {
    app.searchControl.execute(this.input_.value);
  }
  this.input_.focus();
}

/**
 * .addResult()
 *
 * Add a search result to the item. The search result is
 * wrapped in a .wiw-result which gives us a container to
 * host a "delete" control.
 *
 * Link in the control and bind in a click handler.
*/
Item.prototype.addResult = function(result) {
  var resultDiv = createDiv(null,"wiw-result");
  var node = result.html.cloneNode(true);
  var deleteMe = createDiv("delete", "wiw-delete");

  resultDiv.appendChild(node);
  resultDiv.appendChild(deleteMe);
  this.resultsContainer_.appendChild(resultDiv);

  deleteMe.onclick = method_closure(this, Item.prototype.onDeleteResult, [resultDiv]);

  if (this.input_.value == "") {
    this.input_.value = result.titleNoFormatting;
  }
}

/**
 * .onSave()
 *
 * Similar to close in that it transitions an item out of read/write
 * mode and into read only mode. In addition to just closing the item,
 * the search control is closed and new item creation mode is entered.
 *
 * This method is used as the submit handler for an item's form and therefore
 * returns false to indicate that the form should not be submitted. All side
 * effects are contained within.
*/
Item.prototype.onSave = function(conditional) {
  if (conditional && this.input_.value == "") {
    return false;
  }
  this.close();

  app.switchToNewItemForm();
  return false;
}

/**
 * .onEdit()
 *
 * Switch on the search control, close the current item,
 * mark this item as the current item (so that search results can
 * spill into this item), and then open the item for edit.
*/
Item.prototype.onEdit = function() {
  app.switchToSearchControl(null);
  app.currentItem.close();
  app.currentItem = this;
  this.open();
}

/**
 * .onDelete()
 *
 * Mark the current item as "deleted". Note that delete is a soft delete.
 * This would allow us to easily build an undo/undelete, and doesn't impact
 * save since we record in the dom using wiw-item-deleted class value that
 * this item is dead.
*/
Item.prototype.onDelete = function() {
  this.deleted_ = true;
  app.visibleItemCount--;
  cssSetClass(this.root, "wiw-item wiw-item-deleted");
  if ( app.currentItem.closed || app.currentItem.deleted_ || app.currentItem.visibleItemCount == 0) {
    app.switchToNewItemForm();
  }
}

/**
 * .onDetailsChange()
 *
 * Twiddle the amount of info shown in the search results container.
*/
Item.prototype.onDetailsChange = function() {
  if (this.detailsClosed) {
    this.detailsClosed = false;
    cssSetClass(this.resultsHeader_, "wiw-details wiw-details-opened");
    cssSetClass(this.resultsContainer_, "wiw-results wiw-result-details-full");
    this.resultsHeader_.innerHTML = "hide details";
  } else {
    this.detailsClosed = true;
    cssSetClass(this.resultsHeader_, "wiw-details wiw-details-closed");
    cssSetClass(this.resultsContainer_, "wiw-results wiw-result-details-partial");
    this.resultsHeader_.innerHTML = "show details";
  }
}

/**
 * .onDeleteResult()
 *
 * Soft delete the specified search result. Note, that in this function
 * result is NOT that same as what we get from the on keep handler. Here,
 * result is a dom node.
*/
Item.prototype.onDeleteResult = function(result) {
  cssSetClass(result, "wiw-result wiw-deleted-result");
}


/**
 * Various Static DOM Wrappers.
*/
function method_closure(object, method, opt_argArray) {
  return function() {
    return method.apply(object, opt_argArray);
  }
}

function removeChildren(parent) {
  while (parent.firstChild) {
    parent.removeChild(parent.firstChild);
  }
}

function cssSetClass(el, className) {
  el.className = className;
}

function createDiv(opt_text, opt_className) {
  var el = document.createElement("div");
  if (opt_text) {
    el.innerHTML = opt_text;
  }
  if (opt_className) { el.className = opt_className; }
  return el;
}

function prependNode(el, node) {
  if ( el.childNodes.length ) {
    el.insertBefore(node, el.childNodes[0]);
  } else {
    el.appendChild(node);
  }
}

function createForm(opt_className) {
  var el = document.createElement("form");
  if (opt_className) { el.className = opt_className; }
  return el;
}

function createTextInput(opt_className) {
  var el = document.createElement("input");
  el.setAttribute("autoComplete", "off"); // fixes firefox auto-complete bug
  el.type = "text";
  if (opt_className) { el.className = opt_className; }
  return el;
}

function createTable(opt_className) {
  var el = document.createElement("table");
  if (opt_className) { el.className = opt_className; }
  return el;
}

function createTableRow(table) {
  var tr = table.insertRow(-1);
  return tr;
}

function createTableCell(tr, opt_className) {
  var td = tr.insertCell(-1);
  if (opt_className) { td.className = opt_className; }
  return td;
}

