// BEGIN FLOCK GPL
//
// Copyright Flock Inc. 2005-2009
// http://flock.com
//
// This file may be used under the terms of the
// GNU General Public License Version 2 or later (the "GPL"),
// http://www.gnu.org/licenses/gpl.html
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// END FLOCK GPL

const CC = Components.classes;
const CI = Components.interfaces;
const CR = Components.results;
const CU = Components.utils;

CU.import("resource:///modules/FlockPrefsUtils.jsm");
CU.import("resource:///modules/FlockXPCOMUtils.jsm");
FlockXPCOMUtils.debug = false;

const MODULE_NAME = "Flock People Service";

// People Service
const FLOCK_PEOPLE_SERVICE_CLASS_NAME = "Flock People Service";
const FLOCK_PEOPLE_SERVICE_CLASS_ID =
        Components.ID("{c04112e6-de37-438e-9ccc-4efae2b75ecb}");
const FLOCK_PEOPLE_SERVICE_CONTRACT_ID = "@flock.com/people-service;1";

const OBSERVER_SERVICE_CONTRACTID = "@mozilla.org/observer-service;1";
const PEOPLESERVICE_TOPIC = "PeopleService";

// People Observer
const FLOCK_PEOPLE_OBSERVER_CLASS_NAME = "Flock People Observer";
const FLOCK_PEOPLE_OBSERVER_CLASS_ID =
        Components.ID("{587c5c29-89c2-4bdf-8996-8915d7f42c3b}");
const FLOCK_PEOPLE_OBSERVER_CONTRACT_ID = "@flock.com/people-observer;1";

const UPDATEDATE = "http://flock.com/rdf#updateDate";

const EXCLUDE_PREF = "flock.myworld.friendActivity.excludecriteria";

/**************************************************************************
 * Component: Flock People Service
 **************************************************************************/

// Constructor.
function flockPeopleService() {
  this._logger = CC["@flock.com/logger;1"]
                 .createInstance(CI.flockILogger);
  this._logger.init("flockPeopleService");
  this._coop = CC["@flock.com/singleton;1"]
               .getService(CI.flockISingleton)
               .getSingleton("chrome://flock/content/common/load-faves-coop.js")
               .wrappedJSObject;
  this._peopleObserverService = CC[OBSERVER_SERVICE_CONTRACTID]
                                .getService(CI.nsIObserverService);
  this._rdfSvc = CC["@mozilla.org/rdf/rdf-service;1"]
                 .getService(CI.nsIRDFService);

  this._ds = this._rdfSvc.GetDataSource("rdf:flock-favorites");
  var inst = this;
  this._rdfObserver = {
    rdfChanged: function flockPeopleService_rdfChanged(ds, type, source,
                                                       predicate, target,
                                                       oldtarget)
    {
      if (!source) {
        inst._logger.error("rdfChanged: source is nothing so exiting");
        return;
      }
      var id = source.ValueUTF8;
      var pred = predicate.ValueUTF8;
      inst._logger.debug("rdfChanged for " + id + " - " + pred);
      if (type == CI.flockIRDFObserver.TYPE_UNASSERT) {
        inst._logger.debug("removeFriend");
        inst._notifyObservers("removeFriend", id);
      } else {
        var coopObj = inst._coop.get_from_resource(source, id);
        if (!coopObj) {
          inst._logger.error("rdfChanged: coopObj is null so exiting");
          return;
        }
        if (type == CI.flockIRDFObserver.TYPE_CHANGE) {
          if (coopObj.isInstanceOf(inst._coop.Identity)) {
            inst._logger.debug("updateFriend");
            if (pred == "http://flock.com/rdf#lastUpdate") {
              inst._updateActivityLog(coopObj);
            }
            inst._notifyObservers("updateFriend", id);
          }
        } else if (type == CI.flockIRDFObserver.TYPE_ASSERT) {
          if (coopObj.isInstanceOf(inst._coop.Identity)) {
            inst._logger.debug("addFriend");
            inst._updateActivityLog(coopObj);
            inst._notifyObservers("addFriend", id);
          }
        }
      }
    }
  }

  var prefs = CC["@mozilla.org/preferences-service;1"]
              .getService(CI.nsIPrefBranch);
  this._maxCount = prefs.getIntPref("flock.myworld.topfriends.count");

  var obs = CC["@mozilla.org/observer-service;1"]
            .getService(CI.nsIObserverService);

  obs.addObserver(this, "user-interaction-inactive", false);
}

/**************************************************************************
* Flock People Service: XPCOM Component Creation
**************************************************************************/

flockPeopleService.prototype = new FlockXPCOMUtils.genericComponent(
  FLOCK_PEOPLE_SERVICE_CLASS_NAME,
  FLOCK_PEOPLE_SERVICE_CLASS_ID,
  FLOCK_PEOPLE_SERVICE_CONTRACT_ID,
  flockPeopleService,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.flockIPeopleService,
    CI.nsIObserver
  ]
);

flockPeopleService.prototype._xpcom_categories = [
  { category: "flock-startup", service: true }
];

/**************************************************************************
 * Flock People Service: private members
 **************************************************************************/

flockPeopleService.prototype._isValid =
function flockPeopleService__isValid(aCoopObj) {
  // The isValid function returns true if aCoopObj does not match
  // any of the exclude criteria specified in a pref.
  // The required format for the exclude criteria pref is shown in the
  // example below and is a JSON string that represents sets of criteria.
  // The example shows a single criteria set which will match on
  // any identity where the last update was to a status and the status is
  // empty.
  // [[{"field":"lastUpdateType","value":"status"},
  //   {"field":"statusMessage","value":"flockempty"}]];
  // Note: flockempty is used to represent empty string

  var excludeCriteria = FlockPrefsUtils.getCharPref(EXCLUDE_PREF);
  if (!excludeCriteria) {
    // No exclude criteria set so nothing to do
    return true;
  }

  var nsJSON = CC["@mozilla.org/dom/json;1"].createInstance(CI.nsIJSON);
  var excludeCriteriaSets = nsJSON.decode(excludeCriteria);
  // Loop through each criteria set
  for each (var criteriaSet in excludeCriteriaSets) {
    var exclude = true;
    // Loop through each criterion
    for each (var criterion in criteriaSet) {
      // Special check for null
      var isMatchForNull = (aCoopObj[criterion.field] == "" &&
                            criterion.value == "flockempty");
      if ((aCoopObj[criterion.field] != criterion.value) && !isMatchForNull) {
        exclude = false;
        break;
      }
    }

    if (exclude) {
      // If exclude is still true at this point we know that this identity
      // matched at least one set of exclude criteria
      return false;
    }
  }
  return true;
};


flockPeopleService.prototype._updateActivityLog =
function flockPeopleService__updateActivityLog(aCoopIdentity) {
  var updateDate = new Date(aCoopIdentity.lastUpdate * 1000);

  // See if this event should be ignore because of the exclude criteria
  if (!this._isValid(aCoopIdentity)) {
    return;
  }

  // Only add events with a valid date
  if (updateDate.getTime() > 0) {
    var newActivity = new this._coop.FriendActivity({updateDate: updateDate});
    var coopAccount = aCoopIdentity.getAccount();
    var coopActivityLog = coopAccount.friendActivityLog;
    if (!coopActivityLog) {
      coopActivityLog = new this._coop.FriendActivityLog();
      coopAccount.friendActivityLog = coopActivityLog;
    }
    var replacedUrn = coopActivityLog.children.insertSortedOn(UPDATEDATE,
                                                              newActivity,
                                                              this._maxCount);
    if (replacedUrn) {
      // The new activity was inserted
      newActivity.updateType = aCoopIdentity.lastUpdateType;
      newActivity.updateValue = aCoopIdentity.statusMessage;
      newActivity.updateURL = aCoopIdentity.statusMessageUrl;
      newActivity.identity = aCoopIdentity;
      this._notifyObservers("newActivity", newActivity.id());
    }
  }
}

/**************************************************************************
 * Flock People Service: nsIObserver Implementation
 **************************************************************************/

flockPeopleService.prototype.observe =
function flockPeopleService_observe(aSubject, aTopic, aState) {
  var obs = CC["@mozilla.org/observer-service;1"]
            .getService(CI.nsIObserverService);

  switch (aTopic) {
    case "user-interaction-inactive":
      obs.removeObserver(this, "user-interaction-inactive");
      obs.addObserver(this, "xpcom-shutdown", false);
      // Maintain an activity log for each identity;
      // this is also why we need this service to be launched at startup
      this._addRDFObservers();
      break;
    case "xpcom-shutdown":
      obs.removeObserver(this, "xpcom-shutdown");
      this._removeRDFObservers();
      break;
  }
}

/**************************************************************************
 * Flock People Service: flockIPeopleService Implementation
 **************************************************************************/

flockPeopleService.prototype.addObserver =
function flockPeopleService_addObserver(aObserver) {
  this._peopleObserverService.addObserver(aObserver, PEOPLESERVICE_TOPIC, false);
  this._logger.info("addObserver: " + this.numObservers());
}

flockPeopleService.prototype.removeObserver =
function flockPeopleService_removeObserver(aObserver) {
  this._logger.info("removeObserver: " + this.numObservers());
  this._peopleObserverService.removeObserver(aObserver, PEOPLESERVICE_TOPIC);
}

flockPeopleService.prototype._notifyObservers =
function flockPeopleService__notifyObservers(aMethod, aValue) {
  var enumerator = this._peopleObserverService.enumerateObservers(PEOPLESERVICE_TOPIC);
  while (enumerator.hasMoreElements()) {
    var obs = enumerator.getNext();
    obs.QueryInterface(CI.flockIPeopleObserver);
    switch (aMethod)
    {
      case "removeFriend":
        if (obs.onFriendRemove) {
          obs.onFriendRemove(aValue);
        }
        break;
      case "updateFriend":
        if (obs.onFriendUpdate) {
          obs.onFriendUpdate(aValue);
        }
        break;
      case "addFriend":
        if (obs.onInsertFriend) {
          obs.onInsertFriend(aValue);
        }
        break;
      case "newActivity":
        if (obs.onNewActivity) {
          obs.onNewActivity(aValue);
        }
        break;
      default:
        this._logger.error(aMethod + " is not a valid event");
        break;
    }
  }  
}

flockPeopleService.prototype.numObservers =
function flockPeopleService_numObservers() {
  var enumerator = this._peopleObserverService
                       .enumerateObservers(PEOPLESERVICE_TOPIC);
  var count = 0;
  while (enumerator.hasMoreElements()) {
    enumerator.getNext();
    count++;
  }
  return count;
}

flockPeopleService.prototype._addRDFObservers =
function flockPeopleService__addRDFObservers() {
  var observable = this._ds.QueryInterface(CI.flockIRDFObservable);
  observable.addArcObserver(CI.flockIRDFObserver.TYPE_CHANGE,
                            null,
                            this._rdfSvc.GetResource("http://home.netscape.com/NC-rdf#Name"),
                            null,
                            this._rdfObserver);
  observable.addArcObserver(CI.flockIRDFObserver.TYPE_CHANGE,
                            null,
                            this._rdfSvc.GetResource("http://flock.com/rdf#lastUpdate"),
                            null,
                            this._rdfObserver);
  observable.addArcObserver(CI.flockIRDFObserver.TYPE_CHANGE,
                            null,
                            this._rdfSvc.GetResource("http://flock.com/rdf#avatar"),
                            null,
                            this._rdfObserver);
  observable.addArcObserver(CI.flockIRDFObserver.TYPE_CHANGE,
                            null,
                            this._rdfSvc.GetResource("http://flock.com/rdf#unseenMedia"),
                            null,
                            this._rdfObserver);
  observable.addArcObserver(CI.flockIRDFObserver.TYPE_ASSERT,
                            null,
                            this._rdfSvc.GetResource("http://flock.com/rdf#lastUpdate"),
                            null,
                            this._rdfObserver);
  observable.addArcObserver(CI.flockIRDFObserver.TYPE_UNASSERT,
                            null,
                            this._rdfSvc.GetResource("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
                            this._rdfSvc.GetResource("http://flock.com/rdf#Identity"),
                            this._rdfObserver);
}

flockPeopleService.prototype.__defineGetter__("peopleIconLit",
function flockPeopleService_peopleIconLit() {
  return this._peopleIconLit;
})

flockPeopleService.prototype._removeRDFObservers =
function flockPeopleService__removeRDFObservers() {
  var observable = this._ds.QueryInterface(CI.flockIRDFObservable);
  observable.removeArcObserver(CI.flockIRDFObserver.TYPE_CHANGE,
                               null,
                               this._rdfSvc.GetResource("http://home.netscape.com/NC-rdf#Name"),
                               null,
                               this._rdfObserver);
  observable.removeArcObserver(CI.flockIRDFObserver.TYPE_CHANGE,
                               null,
                               this._rdfSvc.GetResource("http://flock.com/rdf#lastUpdate"),
                               null,
                               this._rdfObserver);
  observable.removeArcObserver(CI.flockIRDFObserver.TYPE_CHANGE,
                               null,
                               this._rdfSvc.GetResource("http://flock.com/rdf#avatar"),
                               null,
                               this._rdfObserver);
  observable.removeArcObserver(CI.flockIRDFObserver.TYPE_CHANGE,
                               null,
                               this._rdfSvc.GetResource("http://flock.com/rdf#unseenMedia"),
                               null,
                               this._rdfObserver);
  observable.removeArcObserver(CI.flockIRDFObserver.TYPE_ASSERT,
                               null,
                               this._rdfSvc.GetResource("http://flock.com/rdf#lastUpdate"),
                               null,
                               this._rdfObserver);
  observable.removeArcObserver(CI.flockIRDFObserver.TYPE_UNASSERT,
                               null,
                               this._rdfSvc.GetResource("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),
                               this._rdfSvc.GetResource("http://flock.com/rdf#Identity"),
                               this._rdfObserver);
}

flockPeopleService.prototype.togglePeopleIcon =
function flockPeopleService_togglePeopleIcon(aToggleOn) {
  this._logger.debug(".togglePeopleIcon(" + aToggleOn + ")");

  // Services will toggle on the people icon when there are new notifications.
  this._peopleIconLit = aToggleOn;
  var obs = CC["@mozilla.org/observer-service;1"]
            .getService(CI.nsIObserverService);
  obs.notifyObservers(null, "new-people-notification", aToggleOn);
}

/**************************************************************************
 * BEGIN Flock People Observer
 **************************************************************************/

// Constructor.
function flockPeopleObserver() {
}

/**************************************************************************
 * Flock People Observer: XPCOM Component Creation
 **************************************************************************/

flockPeopleObserver.prototype = new FlockXPCOMUtils.genericComponent(
  FLOCK_PEOPLE_OBSERVER_CLASS_NAME,
  FLOCK_PEOPLE_OBSERVER_CLASS_ID,
  FLOCK_PEOPLE_OBSERVER_CONTRACT_ID,
  flockPeopleObserver,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.flockIPeopleObserver,
  ]
);

/**************************************************************************
 * END Flock People Observer
 **************************************************************************/

/**************************************************************************
 * XPCOM Support - Module Construction
 **************************************************************************/

// Create array of components.
var gComponentsArray = [flockPeopleService, flockPeopleObserver];

// Generate a module for XPCOM to find.
var NSGetModule = FlockXPCOMUtils.generateNSGetModule(MODULE_NAME,
                                                      gComponentsArray);

/**************************************************************************
 * END XPCOM Support
 **************************************************************************/
