/**
 * Saves the DB to a JSON file.
 * This can be loaded later using |LoadFromNativeJSON|
 *
 * Call save()
 *
 * @param storage {Storage}   What to save
 * @param withDescr {Boolean} (Optional, default true)
 *     If false, does not save descriptions.
 * @param minified {Boolean} (Optional, default false)
 *     If false, makes it human-readable with whitespace.
 *     If true, removes whitespace, makes it "minified".
 */
function SaveToNativeJSON(storage, withDescr, minified) {
  assert(storage instanceof Storage);
  this._storage = storage;
  this._withDescr = withDescr != false;
  this._minified = !!minified;
}
SaveToNativeJSON.prototype = {
  _storage : null, // {Storage}   What to save

  /**
   * Do the actual work
   *
   * @param successCallback {Function(result {JSON as String}) }
   * @param errorCallback
   */
  save : function(successCallback, errorCallback) {
    var resultJSON = {};
    resultJSON.details = [];
    var self = this;
    this._storage.iterate(function(obj) {
      resultJSON.details.push(self.detailToJSON(obj));
    }, function() {
      var jsonStr = JSON.stringify(resultJSON, null, self._minified ? null : " ") + "\n";
      successCallback(jsonStr);
    }, errorCallback);
  },
  detailToJSON : function(detail) {
    assert(detail instanceof Detail);
    var json = detail.toJSON();
    if ( !this._withDescr) {
      delete json.descr;
    }
    return json;
  },
  /*
  relationToJSON : function(rel) {
    assert(rel instanceof Relation);
    return {
      id : rel.id,
      subj : rel.subj.id,
      obj : rel.obj.id,
      type : rel.type,
    };
  },
  */



  /**
   * UNUSED
   * Copies all properties of the object to a new object.
   * By default, does *not* copy recursively.
   *
   * @param obj {Object}   What you want to copy
   * @param maxDepth {Integer}   How often to recurse
   *     Optional, default: 1
   * @returns {Object}   The copy.
   *     Limitation: This will be a simple object, not the same type as |obj|.
   * static function
   */
  _copy : function (org, maxDepth)
  {
    if (maxDepth == undefined)
      maxDepth = 1;

    // primitives
    if (typeof(org) == "undefined")
      return undefined;
    if (org == null)
      return null;
    if (typeof(org) == "string")
      return org;
    if (typeof(org) == "number")
      return org;
    if (typeof(org) == "boolean")
      return org == true;
    if (typeof(org) == "function")
      return org;
    if (typeof(org) != "object")
      throw "can't copy objects of type " + typeof(org) + " yet";

    if (maxDepth > 0) {
      //TODO still instanceof org != instanceof copy
      //var result = new org.constructor();
      var result = new Object();
      if (typeof(org.length) == "number") {
        result = new Array();
      }
      for (var prop in org) {
        // skip getters (?)
        if (org.__lookupGetter__(prop)) {
          continue;
        }
        var value = this._copy(org[prop], maxDepth - 1)
        if (value === null || typeof(value) == "undefined") {
          continue;
        }
        result[prop] = value;
      }
      return result;
    }

    return null;
  },
}


Detail.prototype.toJSON = function() {
  var json = {};
  json.typename = this.typename;
  json.id = this.id;
  json.name = this.name;
  //json.firstName = this.firstName;
  //json.lastName = this.lastName;
  if (this.idExt) {
    json.idExt = this.idExt;
  }
  if (this.dbpediaID) {
    json.dbpediaID = this.dbpediaID;
  }
  if (this.role) {
    json.role = this.role;
  }
  if (this.otherNames && this.otherNames.length > 0) {
    json.otherNames = this.otherNames; // Array of String
  }
  json.descr = this.descr;

  if (this.relations.length > 0) {
    json.relations = [];
    this.relations.forEach(function(rel) {
      json.relations.push({
        // skiping ID: not needed
        // skipping subj: implicitly |this|
        obj : rel.obj.id,
        type : rel.type,
      });
    });
  }

  return json;
}

Person.prototype.toJSON = function() {
  var json = Detail.prototype.toJSON.apply(this, arguments);
  json.male = this.male;
  return json;
}

Event.prototype.toJSON = function() {
  var json = Detail.prototype.toJSON.apply(this, arguments);
  if (this.time) {
    json.time = this.time.getTime(); // Unixtime
  }
  if (this.timePlusMinus) {
    json.timePlusMinus = this.timePlusMinus.getTime(); // Unixtime
  }
  if (this.timeAfterEvent) {
    json.timeAfterEvent = this.timeAfterEvent; // Unixtime
  }
  if (this.timeGenerated) {
    json.timeGenerated = this.timeGenerated; // Boolean
  }
  return json;
}

Place.prototype.toJSON = function() {
  var json = Detail.prototype.toJSON.apply(this, arguments);
  if (this.point) {
    json.point = this.point.toJSON(); // GeoCoordinate
  }
  if (this.area && this.area.length > 0) {
    json.area = [];
    this.area.forEach(function(coord) {
      json.area.push(coord.toJSON());
    });
  }
  return json;
}

GeoCoordinate.prototype.toJSON = function(){
  var json = {};
  json.lat = this.lat;
  json.long = this.long;
  return json;
}

/*
Source.prototype.toJSON = function() {
  return Detail.prototype.toJSON.apply(this, arguments);
}

BibleText.prototype.toJSON = function() {
  var json = Source.prototype.toJSON.apply(this, arguments);
  json.bookNum = this.book.num;
  json.chapter = this.chapter;
  json.verse = this.verse;
  if (this.range) {
    json.range = this.range;
    json.verseTo = this.verseTo;
  }
  return json;
}
*/

BibleText.prototype.toJSON = function() {
  var json = {}; // Do *not* call super, save *only* the ref
  json.typename = this.typename;
  json.id = this.id;
  json.name = this.codeRef;
  return json;
}

Media.prototype.toJSON = function() {
  var json = Detail.prototype.toJSON.apply(this, arguments);
  json.url = this.url;
  return json;
}
