/**
 * Based on relative time statements of all events together,
 * calculates the exact or approximate times that each
 * event must have taken place.
 */
function calculateEventTimes(storage, successCallback, errorCallback) {
  storage.getAll(null, function(allObjs) {
    var starttime = Date.now();
    var allEvents = allObjs.filter(function(obj) {
      return obj instanceof Event;
    });

    // Get all events where we have absolute times from editor
    // We'll add events with estimated times as we go on.
    allEventsWithTime = allEvents.filter(function(event) {
      return event.time;
    });

    var generatedEvents = [];
    var generatedRelations = [];

    // Calculate time from timeAfterEvent
    // Inverse after -> before
    allEvents.forEach(function(after) {
      var before = after.afterEventWithRelativeTime;
      if (before) {
        var rel = before.addRelation(after, "beforeEventWithRelativeTime"); // |before| can have several after it
        var rel2 = after.addRelation(before, "afterEvent"); // just to be sure
        var rel3 = before.addRelation(after, "beforeEvent");
        generatedRelations.push(rel);
        generatedRelations.push(rel2);
        generatedRelations.push(rel3);
      }
    });
    // Go over all events with absolute time, and
    // set the events after it with times relative to it.
    function setRelativeTimeFor(before) {
      before.relationsOfType("beforeEventWithRelativeTime", Event)
          .forEach(function(after) {
        if (after.time) return; // don't overwrite manual time, and stop-gap for recursion
        after.time = dateAdd(before.time, after.timeAfterEvent);
        after.timePlusMinus = dateCopy(before.timePlusMinus);
        after.timeGenerated = true;
        allEventsWithTime.push(after);
        setRelativeTimeFor(after); // recurse, to calculate all following events
      });
    };
    function calculateRelativeTimes() {
      allEventsWithTime.forEach(setRelativeTimeFor);
    }
    calculateRelativeTimes();

    // We calculated all exact times that we can
    // Now do the estimations

    // birth/death events
    // They help with having a realistic estimate of times
    // Generate birth/death events for all persons
    allObjs.forEach(function(person) {
      if ( !(person instanceof Person)) return;
      var birth = person.relationsOfType("birth", Event)[0];
      var death = person.relationsOfType("death", Event)[0];
      if ( !birth) {
        birth = new Event();
        allEvents.push(birth);
        generatedEvents.push(birth);
        birth.name = person.name + " born";
        var rel = person.addRelation(birth, "birth");
        generatedRelations.push(rel);
      }
      if ( !death) {
        death = new Event();
        allEvents.push(death);
        generatedEvents.push(death);
        death.name = person.name + " died";
        var rel = person.addRelation(death, "death");
        generatedRelations.push(rel);
      }
    });
    // Mark before/after for child birth relative to parents
    allObjs.forEach(function(person) {
      if ( !(person instanceof Person)) return;
      var birth = person.relationsOfType("birth", Event)[0];
      var death = person.relationsOfType("death", Event)[0];
      if (birth && death) {
        var rels = death.wasAfterEvent(birth);
        generatedRelations = generatedRelations.concat(rels);
      }
      person.children.forEach(function(child) {
        var childBirth = child.relationsOfType("birth", Event)[0];
        if (childBirth && birth) {
          var rels = childBirth.wasAfterEvent(birth);
          generatedRelations = generatedRelations.concat(rels);
        }
        if (childBirth && death) {
          // plus ~1 year, but that's literally a "one-off". Fix manually.
          var rels = death.wasAfterEvent(childBirth);
          generatedRelations = generatedRelations.concat(rels);
        }
      });
    });

    // Spread out the events evenly between the fixed events,
    // (unless we have a relative time).
    // Create event chains of events with "afterEvent"
    // between 2 events with known |time|s.
    // I.e. first and last event will have |time|,
    // the rest in the middle will not have |time|, but |afterEvent|
    // set to the previous in the chain
    allEventsWithTime.forEach(function(startEvent) {
      /**
       * @param before {Events}
       * @param chain {Array of Events} Events in the middle
       * Each with |afterEvent|, and without |time|
       * not incl. startEvent and endEvent
       */
      function step(before, chain) {
        before.relationsOfType("beforeEvent", Event)
            .forEach(function(after) {
          if ( !after.time) {
            // Still no concrete time, so add to chain and continue
            assert( !arrayContains(chain, before), "Loop in afterEvent chain: " +
                chain.map(function(ev) { return ev.name; }).join(", ") +
                " and now again " + before.name);
            /* Because |before| can have several events |after|,
             * and these in turn several, this is branching out.
             * That's why it's a recursive function and
             * we need to copy |chain| using concat().
             * concat() also adds simple elements, not just arrays. */
            var newChain = before == startEvent ? chain : chain.concat(before);
            step(after, newChain);
          } else {
            // Done with this chain. Calculate.
            var endEvent = after;
            var timespan = endEvent.time.getTime() - startEvent.time.getTime();
            var stepspan = timespan / (chain.length + 1);
            var counter = 0;
            chain.forEach(function(middle) { // {Event}
              if (middle.time) return; // TODO HACK shouldn't happen
              assert( !middle.time, middle.name + " has .time. Chain: " +
                  chain.map(function(ev) { return ev.name; }).join(", ") +
                  " startEvent: " + startEvent.name + " endEvent: " + endEvent.name);
              if (middle.afterEventWithRelativeTime) {
                /* It's fairly common that we know B was 1 day after A,
                 * but we don't know when exactly A was. This is solved here. */
                var bef = middle.afterEventWithRelativeTime;
                middle.time = dateAdd(bef.time, middle.timeAfterEvent);
                middle.timePlusMinus = dateCopy(bef.timePlusMinus);
                middle.timeGenerated = true;
                allEventsWithTime.push(middle);
              } else {
                // Guess time, by spreading out evenly
                var relativeTime = new Date(stepspan * ++counter);
                middle.time = dateAdd(startEvent.time, relativeTime);
                middle.timePlusMinus = new Date(timespan);
                middle.timeGenerated = true;
                allEventsWithTime.push(middle);
              }
            });
          }
        });
      }
      step(startEvent, []);
    });

    /* |generatedEvents| should not be stored, just used for calculation.
     * They have not been added to |Storage|, just |allEvents| here,
     * so no need to clean up.
     * But we need to remove the relations, even generated relations
     * between non-generated birth/death events. */
    generatedRelations.forEach(function(rel) {
      rel.remove();
    });

    /*generatedEvents.forEach(function(event) {
      storage.add(event);
    });*/

    ddebug("calculated. Time: " + (Date.now() - starttime) + "ms");
    successCallback(allEventsWithTime);
  }, errorCallback);
}

