/**
 * Creates the UI for any type of object. Looks at the type and then
 * invokes the appropriate ui function.
 *
 * @param container {jquery DOM Element}   <div> where to add the UI
 * @param obj {Detail}   The object to display
 * @param navFunc {Function}
 *     Called when the user clicks on another object and it should be shown.
 * @param errorCallback {Function(e)}
 * @param storage {Storage} (Optional)
 *     To show other objects with same name
 */
function uiDetail(p) {
  assert(p.obj instanceof Detail, "obj: need Detail object");
  assert(typeof(p.navFunc) == "function", "navFunc: need function");
  assert(typeof(p.errorCallback) == "function", "errorCallback: need function");

  uiObjTitle({
    container: p.container,
    text : p.obj.name,
  });

  if (p.obj instanceof Person) {
    p.person = p.obj;
    uiPerson(p);
  }
  else if (p.obj instanceof Event) {
    p.event = p.obj;
    uiEvent(p);
  }
  else if (p.obj instanceof BibleText) {
    p.source = p.obj;
    uiBible(p);
    return; // don't show descr at bottom
  }
  else if (p.obj instanceof Source) {
    p.source = p.obj;
    uiSource(p);
  }
  else if (p.obj instanceof Place) {
    p.place = p.obj;
    uiPlace(p);
  }

  if (p.obj.descr) {
    var $descr = uiDescr({
      container: $("<div/>").appendTo(p.container),
      text : p.obj.descr,
    });
    enhanceDOMTextWithLinks({
      container : $descr,
      objs : p.obj.allRelatedObjs,
      navFunc : p.navFunc,
      errorCallback : p.errorCallback,
    });
  }

  if (p.obj.sources.length > 0) {
    uiSectionTitle({
      container: $("<div/>").appendTo(p.container),
      text : tr("page.obj.sources"),
    });
    uiSourceList({
      container : $("<div/>").appendTo(p.container),
      sources : p.obj.sources,
      navFunc : p.navFunc,
      errorCallback : p.errorCallback,
    });
  }

  // Other objects with same name
  if (p.storage instanceof Storage &&
      (p.obj instanceof Person || p.obj instanceof Place)) {
    var name = p.obj.name.split(" ")[0];
    p.storage.searchName(name, function(otherObjs) {
      otherObjs = otherObjs.filter(function(obj) {
        return (obj instanceof Person || obj instanceof Place) && obj != p.obj;
      });
      if (otherObjs.length == 0) {
        return;
      }
      uiSectionTitle({
        container: p.container,
        text : tr("page.obj.others-with-same-name", name),
      });
      uiList({
        container : p.container,
        columns : [
          { label : tr("page.obj.name.th"), displayFunc : function(obj) {
              return obj.name;
            } },
          { label : tr("page.obj.othernames.th"), displayFunc : function(obj) {
              return obj.otherNames ? obj.otherNames.join(", ") : "";
            } },
          { label : tr("page.obj.role.th"), displayFunc : function(obj) {
              return obj.role ? obj.role : "";
            } },
          { label : tr("page.person.father.th"), displayFunc : function(obj) {
              return obj instanceof Person && obj.father ? obj.father.name : "";
            } },
        ],
        data : otherObjs,
        clickFunc : function(obj) { p.navFunc(obj,
            otherObjs.concat([p.obj]),
            tr("page.obj.others-with-same-name.hl", name)); },
      });
    }, p.errorCallback);
  }
}


/**
 * Creates the UI for a person object.
 *
 * Creates:
 * uiDescr
 * uiList with person relations (left) + uiGenealogy (right)
 *
 * Part of and called by uiDetail()
 *
 * @param container {jquery DOM Element}   <div> where to add the UI
 * @param person {Person}   The object to display
 * @param navFunc {Function}
 *     Called when the user clicks on another object and it should be shown.
 */
function uiPerson(p) {
  assert(p.person instanceof Person, "person: need");
  p.container.addClass("detail");
  p.container.addClass("person");

  if (p.person.role) {
    uiSectionTitle({
      container: $("<div class='role'/>").appendTo(p.container),
      text : p.person.role,
    });
  }
  if (p.person.otherNames && p.person.otherNames.length > 0) {
    var names = p.person.otherNames.join(", ");
    uiSectionTitle({
      container: $("<div class='other-names'/>").appendTo(p.container),
      text : tr("page.person.othernames", names),
    });
  }
  p.person.media.forEach(function(media) {
    uiMedia({
      container: $("<div/>").appendTo(p.container),
      media : media,
    });
  });
  if (p.person.personRelations.length > 0) {
    uiSectionTitle({
      container: $("<div/>").appendTo(p.container),
      text : tr("page.person.relatives"),
    });
    uiGenealogy({
      container : p.container,
      person : p.person,
      navFunc : p.navFunc,
      errorCallback : p.errorCallback,
    });
    var personsInclSiblings = p.person.personRelations.concat(p.person.siblingRelations);
    uiList({
      container : p.container,
      columns : [
        { label : tr("page.person.relatives.type.th"), displayFunc : function(rel) { return rel.typeLabel; } },
        { label : tr("page.person.relatives.name.th"), displayFunc : function(rel) { return rel.obj.name; } },
      ],
      data : personsInclSiblings,
      clickFunc : function(rel) {
        p.navFunc(rel.obj, p.person.relatedPersons, tr("page.person.relatives.hl", p.person.name));
      },
    });
  }
  var places = p.person.places;
  if (p.person.events.length > 0) {
    uiSectionTitle({
      container: $("<div/>").appendTo(p.container),
      text : tr("page.person.events"),
    });
    uiTimeline({
      container : $("<div/>").appendTo(p.container),
      events : p.person.events,
      navFunc : p.navFunc,
      errorCallback : p.errorCallback,
    });
    uiEventList({
      container : $("<div/>").appendTo(p.container),
      events : p.person.events,
      navFunc : p.navFunc,
    });
    uiMap({
      container : p.container,
      events : p.person.events,
      places : places,
      obj : p.person,
      navFunc : p.navFunc,
      errorCallback : p.errorCallback,
    });
  }
  if (places.length > 0) {
    uiSectionTitle({
      container: $("<div/>").appendTo(p.container),
      text : tr("page.person.places"),
    });
    uiList({
      container : $("<div/>").appendTo(p.container),
      columns : [
        { label : tr("page.person.place.th"), displayFunc : function(place) { return place.name; } },
      ],
      data : places,
      clickFunc : function(obj) { p.navFunc(obj, places, tr("page.person.places.hl", p.person.name)); },
    });
  }
}



/**
 * Creates the UI for an event
 *
 * Creates:
 * uiDescr
 * map TODO
 * uiList with persons
 * uiList with places
 * uiList with sources
 *
 * Part of and called by uiDetail()
 *
 * @param container {jquery DOM Element}   <div> where to add the UI
 * @param event {Event}   The object to display
 * @param navFunc {Function}
 *     Called when the user clicks on another object and it should be shown.
 */
function uiEvent(p) {
  assert(p.event instanceof Event, "event: need");
  p.container.addClass("detail");
  p.container.addClass("event");

  p.event.media.forEach(function(media) {
    uiMedia({
      container: $("<div/>").appendTo(p.container),
      media : media,
    });
  });

  /* afterEvent/beforeEvent is mostly internal, thus not shown to end user.
     Explicitly stating "Foo born happened before Foo died" is a bit ridiculous. */

  if (p.event.persons.length > 0) {
    uiSectionTitle({
      container: $("<div/>").appendTo(p.container),
      text : tr("page.event.person"),
    });
    uiList({
      container : $("<div/>").appendTo(p.container),
      columns : [
        { label : tr("page.event.person.th"), displayFunc : function(person) { return person.name; } },
      ],
      data : p.event.persons,
      clickFunc : function(obj) { p.navFunc(obj, p.event.persons, tr("page.event.person.hl", p.event.name)); },
    });
  }

  var places = p.event.places;
  if (places.length > 0) {
    uiSectionTitle({
      container: $("<div/>").appendTo(p.container),
      text : tr("page.event.places"),
    });
    uiList({
      container : $("<div/>").appendTo(p.container),
      columns : [
        { label : tr("page.event.place.th"), displayFunc : function(place) { return place.name; } },
      ],
      data : places,
      clickFunc : function(obj) { p.navFunc(obj, places, tr("page.event.places.hl", p.event.name)); },
    });
    uiMap({
      container : $("<div/>").appendTo(p.container),
      places : places, // TODO necessary?
      events : [ p.event ],
      obj : p.event,
      navFunc : p.navFunc,
      errorCallback : p.errorCallback,
    });
  }
}

/**
 * Creates the UI for a place
 *
 * Creates:
 * uiDescr
 * TODO:
 * map
 * uiList with persons
 * uiList with events
 * uiList with sources (explaining or referencing this one)
 *
 * Part of and called by uiDetail()
 *
 * @param container {jquery DOM Element}   <div> where to add the UI
 * @param place {Place}   The object to display
 * @param navFunc {Function}
 *     Called when the user clicks on another object and it should be shown.
 * @param errorCallback {Function}
 */
function uiPlace(p) {
  assert(p.place instanceof Place, "place: need");
  p.container.addClass("detail");
  p.container.addClass("place");

  p.place.media.forEach(function(media) {
    uiMedia({
      container: $("<div/>").appendTo(p.container),
      media : media,
    });
  });

  uiMap({
    container : $("<div/>").appendTo(p.container),
    places : [ p.place ],
    events : p.place.events,
    obj : p.place,
    navFunc : p.navFunc,
    errorCallback : p.errorCallback,
  });
  if (p.place.places.length > 0) {
    uiSectionTitle({
      container: $("<div/>").appendTo(p.container),
      text : tr("page.place.places"),
    });
    uiList({
      container : $("<div/>").appendTo(p.container),
      columns : [
        { label : tr("page.place.place.th"), displayFunc : function(place) { return place.name; } },
      ],
      data : p.place.places,
      clickFunc : function(obj) { p.navFunc(obj, p.place.places, tr("page.place.places.hl"), p.place.name); },
    });
  }
  if (p.place.persons.length > 0) {
    uiSectionTitle({
      container: $("<div/>").appendTo(p.container),
      text : tr("page.place.persons"),
    });
    uiList({
      container : $("<div/>").appendTo(p.container),
      columns : [
        { label : tr("page.place.person.th"), displayFunc : function(person) { return person.name; } },
      ],
      data : p.place.persons,
      clickFunc : function(obj) { p.navFunc(obj, p.place.persons, tr("page.place.persons.hl", p.place.name)); },
    });
  }
  if (p.place.events.length > 0) {
    uiSectionTitle({
      container: $("<div/>").appendTo(p.container),
      text : tr("page.place.events"),
    });
    uiTimeline({
      container : $("<div/>").appendTo(p.container),
      events : p.place.events,
      navFunc : p.navFunc,
      errorCallback : p.errorCallback,
    });
    uiEventList({
      container : $("<div/>").appendTo(p.container),
      events : p.place.events,
      navFunc : p.navFunc,
    });
  }
}


/**
 * Creates the UI for a source, displaying as full page with full quote.
 *
 * Creates:
 * uiDescr
 * TODO:
 * uiList with persons
 * uiList with events
 * uiList with places
 * uiList with sources (explaining or referencing this one)
 *
 * Part of and called by uiDetail()
 *
 * @param container {jquery DOM Element}   <div> where to add the UI
 * @param source {Source}   The object to display
 * @param navFunc {Function}
 *     Called when the user clicks on another object and it should be shown.
 */
function uiSource(p) {
  assert(p.source instanceof Source, "source: need");
  p.container.addClass("detail");
  p.container.addClass("source");

  var $descr = uiDescr({
    container: $("<div/>").appendTo(p.container),
    text : p.source.quote,
  });
  enhanceDOMTextWithLinks({
    container : $descr,
    objs : p.source.allRelatedObjs,
    navFunc : p.navFunc,
    errorCallback : p.errorCallback,
  });
}

// uiBible() is in biblereader.txt



/////////////////////////////////////////////////////////////////////
// Helpers

/**
 * Creates the UI to show a list of events for some other object
 *
 * Creates:
 * uiList with event name, small part of the descr, and way to open the source
 *
 * @param container {jquery DOM Element}   <div> where to add the UI
 * @param events {Array of Event}  the event list to display
 * @param navFunc {Function}
 *     Called when the user clicks on another object and it should be shown.
 */
function uiEventList(p) {
  assert(p.container, "div: need element");
  assert(p.events.length >= 0, "events: need array");
  assert(p.events.length == 0 || p.events[0] instanceof Event, "events: wrong type");
  assert(typeof(p.navFunc) == "function", "navFunc: need function");
  p.container.addClass("event-list");

  uiList({
    container : p.container,
    columns : [
      { label : tr("page.event.th"), displayFunc : function(event) { return event.name; } },
      //{ label : tr("page.event.descr.th"), displayFunc : function(event) { return shortenText(event.descr, 120); } },
    ],
    data : p.events,
    clickFunc : function(obj) { p.navFunc(obj, p.events, tr("page.events.hl")); },
  });
}

/**
 * Creates the UI to show a list of source texts for some other object
 *
 * Creates:
 * uiList with source name, quote (part) and way to open the source
 *
 * @param container {jquery DOM Element}   <div> where to add the UI
 * @param sources {Array of Source}  the sources list to display
 * @param cutQuote {Integer}
 *     Cut direct quote display to this many characters.
 *     0 = no limit
 *     (optional, default: full quote, unless there are many sources: then 60)
 * @param navFunc {Function}
 *     Called when the user clicks on another object and it should be shown.
 * @param errorCallback {Function(e)}
 */
function uiSourceList(p) {
  assert(p.container, "div: need element");
  assert(p.sources.length >= 0, "sources: need array");
  assert(p.sources.length == 0 || p.sources[0] instanceof Source, "sources: wrong type");
  if (p.cutQuote == undefined) {
    if (p.sources.length > 20) {
      p.cutQuote = 60;
    } else {
      p.cutQuote = 0;
    }
  }
  assert(typeof(p.cutQuote) == "number", "cutQuote: need Integer or not set");
  assert(p.sources.length == 0 || p.sources[0] instanceof Source, "sources: wrong type");
  assert(typeof(p.navFunc) == "function", "need navFunc");
  assert(typeof(p.errorCallback) == "function", "need errorCallback");
  p.container.addClass("source-list");

  function showList() {
    uiList({
      container : p.container,
      columns : [
        { label : tr("page.source.th"), classes: "clickable",
           displayFunc : function(source) { return source.name; } },
        { label : tr("pages.source.quote.th"), displayFunc : function(source, $td) {
            $td.attr("title", source.quote);
            return p.cutQuote ? shortenText(source.quote, p.cutQuote) : source.quote;
          } },
      ],
      data : p.sources,
      clickFunc : function(obj) { p.navFunc(obj, p.sources, tr("page.sources.hl")); },
    });
  }

  var waiting = p.sources.length;
  p.sources.forEach(function(source) {
    source.load(function() {
      if (--waiting == 0) {
        showList();
      }
    }, p.errorCallback);
  }, p.errorCallback);
}

/**
 * Show these objects as a small list, for fast browsing.
 * Called by showHeaderList().
 *
 * @param list {Array of Detail}
 * @param title {String}   Title to show to end user for header list
 * @param navFunc {Function}
 *     Called when the user clicks on another object and it should be shown.
 */
function uiHeaderList(p) {
  assert(p.container, "div: need element");
  assert(p.list.length >= 0, "list: need Array");
  assert(p.list[0] instanceof Detail, "list: need Detail");
  assert(typeof(p.navFunc) == "function", "navFunc: need function");

  function clickFunc(obj) {
    // When I click on an entry in the header list, keep the header list open.
    // Actually, open the same one again.
    p.navFunc(obj, p.list, p.title);
  }

  var listboxE = $("<div class='headerlistbox'/>").appendTo(p.container);
  if (p.list[0] instanceof Person) {
    uiList({
      container : listboxE,
      columns : [
        { label : p.title, displayFunc : function(person) { return person.name; } },
      ],
      data : p.list,
      clickFunc : clickFunc,
    });
  } else if (p.list[0] instanceof Event) {
    uiList({
      container : listboxE,
      columns : [
        { label : p.title, displayFunc : function(event) { return event.name; } },
      ],
      data : p.list,
      clickFunc : clickFunc,
    });
  } else if (p.list[0] instanceof Source) {
    uiList({
      container : listboxE,
      columns : [
        { label : p.title, displayFunc : function(source) { return source.name; } },
      ],
      data : p.list,
      clickFunc : clickFunc,
    });
  }
}
