/**
 * MyPlaces()
 *
 * 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 MyPlaces() {

  // get address of the core app sections
  this.mapContainer = document.getElementById("mapContainer");
  this.itemsContainer = document.getElementById("itemsContainer");
  this.localResultsContainer = document.getElementById("localResultsContainer");
  this.searchContainer = document.getElementById("searchContainer");
  this.searchform = document.getElementById("searchform");

  this.searchForm = new GSearchForm(false, this.searchform);
  this.searchForm.setOnSubmitCallback(this, MyPlaces.prototype.onNewSearch);

  // bind in a map
  // todo(markl) maps 2.0 related changes!
  this.gmap = new GMap(this.mapContainer);
  this.gmap.addControl(new GSmallMapControl());
  this.gmap.addControl(new GMapTypeControl());
  this.gmap.centerAndZoom(new GPoint(-122.1419, 37.4419), 6);
  GEvent.bind(this.gmap, "click", this, this.onMapClick);
  this.temporaryOverlays = new Array();

  // bind in a search control with fully expanded web search
  this.searchControl = new GSearchControl();
  var drawOptions = new GdrawOptions();
  drawOptions.setInput(this.searchForm.input);
  var webSearch = new GwebSearch();
  var searcherOptions = new GsearcherOptions();
  searcherOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
  this.searchControl.addSearcher(webSearch, searcherOptions);
  this.searchControl.setOnKeepCallback( this, MyPlaces.prototype.onKeep, "add this to my place" );
  this.searchControl.draw(this.searchContainer, drawOptions);

  // create a local search object, set for max results, bount to the map
  this.localSearch = new GlocalSearch();
  this.localSearch.setCenterPoint(this.gmap);
  this.localSearch.setSearchCompleteCallback(this, MyPlaces.prototype.onSearchComplete);


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

  // kick start with a standing search
  this.switchToSearchMode();
  this.searchForm.execute("Vancouver");
}

/**
 * .onNewSearch()
 *
 * Called when a user "submits" a search by hitting enter in the search form
 *
 * - Clear out old local search results
 * - Clear out old map markers
 * - Execute a local search
 *
*/
MyPlaces.prototype.onNewSearch = function(form) {
  if (form.input.value) {
    for (var i=0; i < this.temporaryOverlays.length; i++ ) {
      this.gmap.removeOverlay(this.temporaryOverlays[i]);
    }
    this.temporaryOverlays = new Array();
    removeChildren(this.localResultsContainer);
    this.localSearch.execute(form.input.value);
    this.searchControl.execute(form.input.value);
    this.gmap.closeInfoWindow();
  }
  return false;
}

/**
 * .onSearchComplete()
 *
 * Called when a local search completes
 *
*/
MyPlaces.prototype.onSearchComplete = function() {

  // determine if we have results, and how many we have

  this.gmap.closeInfoWindow();
  var rcount = 0;
  if (this.localSearch.results && this.localSearch.results.length > 0 ) {
    rcount = this.localSearch.results.length;
  } else {
    return;
  }

  // pan to first result
  var first = this.localSearch.results[0];
  this.gmap.recenterOrPanToLatLng(new GPoint(parseFloat(first.lng), parseFloat(first.lat)));
  for (var i=0; i < rcount; i++) {
    var result = new LocalResult(this.localSearch.results[i]);
    var node = result.ProduceResultNode();
    this.localResultsContainer.appendChild(node);
    this.temporaryOverlays.push(result.temp_marker);
    this.gmap.addOverlay(result.temp_marker);
  }
  var attribution = this.localSearch.getAttribution();
  if (attribution) {
    this.localResultsContainer.appendChild(attribution);
  }
}

/**
 * .switchToSearchMode()
 *
 * Called when no items are open for editing.
 * Keeper is disabled int he search control.
*/
MyPlaces.prototype.switchToSearchMode = function() {
  cssSetClass(this.searchContainer, "p-item-closed");
}


/**
 * .switchToEditMode()
 *
 * Called when an item is opened for edit, keeper is enabled
 * in the search control.
*/
MyPlaces.prototype.switchToEditMode = function() {
  cssSetClass(this.searchContainer, "p-item-opened");
}

/**
 * .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.
*/
MyPlaces.prototype.addItem = function(result) {

  // 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(result);
  this.currentItem = item;
  prependNode(this.itemsContainer, item.root);
  item.open();

  this.gmap.closeInfoWindow();
  this.gmap.removeOverlay(result.temp_marker);
  this.gmap.addOverlay(item.marker);

  this.visibleItemCount++;
  return item;
}

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

MyPlaces.prototype.onMapClick = function(marker, point) {
  if (marker) {
    var item = marker._item_;
    if (item) {
      var node = item.result.gresult.html.cloneNode(true);
      var div = createDiv(null, "gs-infoWindow");
      div.appendChild(node);
      marker.openInfoWindow(div);
    } else {
      var localResult = marker._localResult_;
      if (localResult) {
        var div = createDiv(null, "gs-infoWindow");
        div.appendChild(localResult.ProduceResultNode());
        marker.openInfoWindow(div);
      }
    }
  }
}

/**
 * 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
*/
var SHOW_DETAILS_TEXT = "show related search result details";
var HIDE_DETAILS_TEXT = "hide related search result details";
function Item(result) {
  this.closed = true;
  this.detailsClosed = true;
  this.deleted_ = false;
  this.result = result;

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

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

  this.localResult = result.gresult.html.cloneNode(true);
  this.marker = result.marker;
  this.marker._item_ = this;

  // 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", "p-title-control p-edit");
  this.save_ = createDiv("save", "p-title-control p-save");
  this.delete_ = createDiv("delete", "p-title-control p-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_TEXT, "p-details p-details-closed");
  this.resultsContainer_ = createDiv(null, "p-results p-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("p-title-table");
  var row = createTableRow(table);
  var resultCell = createTableCell(row, "p-result-cell");
  var controlCell = createTableCell(row, "p-control-cell");
  resultCell.appendChild(this.localResult);
  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, []);

  // 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() {
  app.gmap.closeInfoWindow();
  if (this.deleted_) {
    return;
  }
  this.closed = true;
  this.detailsClosed = true;
  cssSetClass(this.root, "p-item p-item-closed");
  cssSetClass(this.resultsHeader_, "p-details p-details-closed");
  cssSetClass(this.resultsContainer_, "p-results p-result-details-partial");
  this.resultsHeader_.innerHTML = SHOW_DETAILS_TEXT;
}

/**
 * .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() {
  app.gmap.closeInfoWindow();
  this.closed = false;
  this.detailsClosed = false;
  cssSetClass(this.root, "p-item p-item-opened");
  cssSetClass(this.resultsHeader_, "p-details p-details-opened");
  cssSetClass(this.resultsContainer_, "p-results p-result-details-full");
  this.resultsHeader_.innerHTML = HIDE_DETAILS_TEXT;
  app.searchControl.execute(this.result.gresult.titleNoFormatting);
}

/**
 * .addResult()
 *
 * Add a search result to the item. The search result is
 * wrapped in a .p-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) {
  app.gmap.closeInfoWindow();
  var resultDiv = createDiv(null,"p-result");
  var node = result.html.cloneNode(true);
  var deleteMe = createDiv("delete", "p-delete");

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

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

/**
 * .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) {
  app.gmap.closeInfoWindow();
  this.close();
  app.switchToSearchMode();
  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.gmap.closeInfoWindow();
  app.switchToEditMode();
  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 p-item-deleted class value that
 * this item is dead.
*/
Item.prototype.onDelete = function() {
  app.gmap.closeInfoWindow();
  this.deleted_ = true;
  app.gmap.removeOverlay(this.marker);
  app.visibleItemCount--;
  cssSetClass(this.root, "p-item p-item-deleted");
  if ( app.currentItem.closed || app.currentItem.deleted_ || app.currentItem.visibleItemCount == 0) {
    app.switchToSearchMode();
  }
}

/**
 * .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_, "p-details p-details-opened");
    cssSetClass(this.resultsContainer_, "p-results p-result-details-full");
    this.resultsHeader_.innerHTML = HIDE_DETAILS_TEXT;
  } else {
    this.detailsClosed = true;
    cssSetClass(this.resultsHeader_, "p-details p-details-closed");
    cssSetClass(this.resultsContainer_, "p-results p-result-details-partial");
    this.resultsHeader_.innerHTML = SHOW_DETAILS_TEXT;
  }
}

/**
 * .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, "p-result p-deleted-result");
}


/**
 * LocalResult
 *
 * Simple wrapper over local search result, mainly used
 * so that the onPin function has context
*/
function LocalResult(result) {
  this.gresult = result;
  this.point = new GPoint(parseFloat(result.lng), parseFloat(result.lat));
  this.marker = new GMarker(this.point, new GIcon(BaseIcon_));
  this.temp_marker = new GMarker(this.point, new GIcon(TempBaseIcon_));
  this.temp_marker._localResult_ = this;
}

/**
 * .ProduceResultNode()
 *
 * Returns an html representation for use in the local results container
 * Wired to pin as an item, etc.
*/
LocalResult.prototype.ProduceResultNode = function() {
  var node = this.gresult.html.cloneNode(true);
  var div = createDiv(null,"p-local-result");
  var pinMe = createDiv("remember this", "p-remember");
  pinMe.onclick = method_closure(this, LocalResult.prototype.onPinMe, [div]);
  div.appendChild(node);
  div.appendChild(pinMe);
  return div;
}

/**
 * .onPinMe()
 *
 * Called when the pinme link is clicked
*/
LocalResult.prototype.onPinMe = function(localResult) {

  if ( app.currentItem && app.currentItem.closed == false ) {
    app.currentItem.close();
  }

  // nuke from results
  cssSetClass(localResult, "p-local-result p-deleted-local-result");

  // add the item and switch in to edit mode
  app.addItem(this);
  app.switchToEditMode();
}

/**
 * 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;
}


var BaseIcon_ = new GIcon(null);
BaseIcon_.image = "http://www.google.com/mapfiles/marker.png";
BaseIcon_.shadow = "http://www.google.com/mapfiles/shadow50.png";
BaseIcon_.iconSize = new GSize(20, 34);
BaseIcon_.shadowSize = new GSize(37, 34);
BaseIcon_.iconAnchor = new GPoint(9, 34);
BaseIcon_.infoWindowAnchor = new GPoint(9, 2);
BaseIcon_.infoShadowAnchor = new GPoint(18, 25);

var TempBaseIcon_ = new GIcon(BaseIcon_);
TempBaseIcon_.image = "http://www.localsearchcanada.com/temp_marker.png";