//
// BEGIN FLOCK GPL
// 
// Copyright Flock Inc. 2005-2006
// http://flock.com
// 
// This file may be used under the terms of 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 DEBUG = false;                // set to false to suppress debug messages
const FLOCK_PHOTO_CID               = Components.ID('{ea011d80-1ffc-4030-a870-780e1c7c4d76}');
const FLOCK_PHOTO_FNAME             = "flock_people_photo.rdf";

const nsISupports                   = Components.interfaces.nsISupports;
const nsIClassInfo                  = Components.interfaces.nsIClassInfo;
const nsIFactory                    = Components.interfaces.nsIFactory;
const nsIProperties                 = Components.interfaces.nsIProperties;
const nsILocalFile                  = Components.interfaces.nsILocalFile;
const nsIFile                       = Components.interfaces.nsIFile;
const nsIIOService                  = Components.interfaces.nsIIOService;
const nsIFileProtocolHandler        = Components.interfaces.nsIFileProtocolHandler;
const nsIRDFRemoteDataSource        = Components.interfaces.nsIRDFRemoteDataSource;
const nsIRDFDataSource              = Components.interfaces.nsIRDFDataSource;
const flockIPhotoPeopleService      = Components.interfaces.flockIPhotoPeopleService;
const flockIPhotoAPIManager         = Components.interfaces.flockIPhotoAPIManager;
const flockIPhotoAPI                = Components.interfaces.flockIPhotoAPI;
const flockIPhotoPerson = Components.interfaces.flockIPhotoPerson;
const FLOCK_PHOTOPERSON_CONTRACTID = '@flock.com/photo-person;1';

const FLOCK_PHOTO_API_MANAGER_CONTRACTID        = '@flock.com/photo-api-manager;1?';
const FLOCK_PHOTO_CONTRACTID        = '@mozilla.org/rdf/datasource;1?name=flock-people-photo';
const DIRECTORY_SERVICE_CONTRACTID  = '@mozilla.org/file/directory_service;1';
const LOCAL_FILE_CONTRACTID         = '@mozilla.org/file/local;1';
const PREFERENCES_CONTRACTID        = '@mozilla.org/preferences-service;1';
const IO_SERVICE_CONTRACTID         = '@mozilla.org/network/io-service;1';

const FLOCK_NS                      = 'http://flock.com/rdf#';
const FLOCK_RDF_PEOPLE_ROOT         = 'urn:flock:people:photo';
const FLOCK_RDF_PEOPLE_LISTS_ROOT   = 'urn:flock:people:photo:lists';
const FLOCK_RDF_PEOPLE_SETTINGS_ROOT   = 'urn:flock:people:photo:settings';
//careful with FLOCK_RDF_PEOPLE_ADD_ACCOUNT - this constant is used in UI!!!!!
const FLOCK_RDF_PEOPLE_ADD_ACCOUNT   = 'urn:flock:people:photo:internal:addaccount';
const NC = "http://home.netscape.com/NC-rdf#";

loadLibraryFromSpec('chrome://browser/content/flock/contrib/rdfds.js');

function loadLibraryFromSpec(aSpec) {
    var loader = Components.classes['@mozilla.org/moz/jssubscript-loader;1']
        .getService(Components.interfaces.mozIJSSubScriptLoader);

    loader.loadSubScript(aSpec);
}

function flockPhotoPeopleService() { /*{{{*/
    debug ('CONSTRUCTOR: flockPhotoPeopleService\n');
    var profd = Components.classes[DIRECTORY_SERVICE_CONTRACTID].
        getService(nsIProperties).get('ProfD', nsIFile);
    var file = Components.classes[LOCAL_FILE_CONTRACTID]
        .createInstance(nsILocalFile);
    try {
        file.initWithPath(profd.path);
    }
    catch(e) {
        var tmpd = Components.classes[DIRECTORY_SERVICE_CONTRACTID].
            getService(nsIProperties).get('TmpD', nsIFile);
        file.initWithPath(tmpd.path);
    }

    file.append(FLOCK_PHOTO_FNAME);
    if(!file.exists()) file.createUnique(0,0700);

    this.ds = new RDFDataSource('file://' + file.path);
    this.dataSource = this.ds.getRawDataSource()
        .QueryInterface (nsIRDFRemoteDataSource);
    this.listsRoot = this.ds.getNode(FLOCK_RDF_PEOPLE_LISTS_ROOT);
    this.listsRoot.makeSeq();

    this.apiManager = Components.classes[FLOCK_PHOTO_API_MANAGER_CONTRACTID]
        .getService(Components.interfaces.flockIPhotoAPIManager);
    this.initLists();
    var prefService = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefBranch);

    try {
        var pref = prefService.getIntPref("flock.photo.checkForNewPhotosIval");
        this.updateInterval = pref * 60;
    }
    catch(e) {
    }

    this.startTimers();

}/*}}}*/
//////////////////////////////////////////////////////////////////////////////
// Helpers
//////////////////////////////////////////////////////////////////////////////



//////////////////////////////////////////////////////////////////////////////
// Implementation
//////////////////////////////////////////////////////////////////////////////

flockPhotoPeopleService.prototype.mListeners = new Array();

flockPhotoPeopleService.prototype.addListener = function(aListener) {
    this.mListeners.push(aListener);
}

flockPhotoPeopleService.prototype.removeListener = function(aListener) {
    for(var i=0;i<this.mListeners.length;++i) {
        if(aListener==this.mListeners[i]) {
            this.mListeners.splice(i,1);
            break;
        }
    }
}

flockPhotoPeopleService.prototype.notifyListeners = function() {
    for(var i=0;i<this.mListeners.length;++i) {
            this.mListeners[i].onUpdate();
    }
}

flockPhotoPeopleService.prototype.notifyNewPhotos = function() {
    for(var i=0;i<this.mListeners.length;++i) {
            this.mListeners[i].onNewPhotos();
    }
    var settings = this.ds.getNode(FLOCK_RDF_PEOPLE_SETTINGS_ROOT);
    settings.addTargetOnce(FLOCK_NS + "hasNew", "true");
    this.mHasNew = true;
}

flockPhotoPeopleService.prototype.unnotifyNewPhotos = function() {
    for(var i=0;i<this.mListeners.length;++i) {
            this.mListeners[i].onNewPhotosTouched();
    }
    var settings = this.ds.getNode(FLOCK_RDF_PEOPLE_SETTINGS_ROOT);
    settings.addTargetOnce(FLOCK_NS + "hasNew", "false");
    this.mNeedSave = true;
    this.mHasNew = false;
    this.mNeedUnnotify = false;
}

flockPhotoPeopleService.prototype.mHasNew = null;

flockPhotoPeopleService.prototype.__defineGetter__('hasNew', function () { 
    if(null==this.mHasNew) {
        var settings = this.ds.getNode(FLOCK_RDF_PEOPLE_SETTINGS_ROOT);
        var hasNewNode = settings.getTarget(FLOCK_NS + "hasNew");
        if(!hasNewNode) this.mHasNew = false;
        else if(hasNewNode.getValue()=="true") this.mHasNew = true;
        else this.mHasNew = false;
    }
    return this.mHasNew;
})

flockPhotoPeopleService.prototype.getAddAccountNode = function() {
    var accountNode = this.ds.getNode(FLOCK_RDF_PEOPLE_ADD_ACCOUNT);
    var str = "Add Account..";
    if(!accountNode.getTarget(NC + "Name")) {
        accountNode.addTargetOnce(NC + "Name", str);
        accountNode.addTargetOnce(FLOCK_NS + "link", "true");
    }
    return accountNode;
}

flockPhotoPeopleService.prototype.mNeedSave = false;
flockPhotoPeopleService.prototype.startTimers = function() {
    var accountTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
    var inst = this;
    var timerFunc = {
        notify: function(aTimer) {
            if(inst.mNeedSave) {
                inst.mNeedSave = false;
                inst.Flush();
                inst.notifyListeners();
            }
            if(inst.mNeedUnnotify) {
                inst.unnotifyNewPhotos();
            }
            return;
            var listNode = inst.getListNode(flockIPhotoPeopleService.LIST_ACCOUNTS);
            var enum = listNode.getChildren();
            var hasAccount = false;
            while(enum.hasMoreElements()) {
                var personNode = enum.getNext();
                try {
                    var service = inst.apiManager.getAPIFromShortname(personNode.getTarget(FLOCK_NS + "apiShortName").getValue());
                    var state = service.authState;
                    var stateStr = "";
                    switch(state) {
                        case flockIPhotoAPI.LOGGED_OUT:
                            stateStr = "LOGGED_OUT";
                        break;
                        case flockIPhotoAPI.LOGGING_IN:
                            stateStr = "LOGGING_IN";
                        break;
                        case flockIPhotoAPI.LOGGED_IN:
                            stateStr = "LOGGED_IN";
                        break;
                        case flockIPhotoAPI.LOGGED_ERROR:
                            stateStr = "LOGIN_ERROR";
                        break;
                    }
                    personNode.addTargetOnce(FLOCK_NS + "authState", stateStr);
                    hasAccount = true;
                }
                catch(e) {
                }
            }
            /*
            // not needed in the new UI
            if(!hasAccount) {
                var accountNode = inst.getAddAccountNode();
                var i = listNode.getChildIndex(accountNode);
                if(i<=0) {
                    listNode.addChild(accountNode);
                    inst.Flush();
                }
            }
            else {
                var accountNode = inst.getAddAccountNode();
                var i = listNode.getChildIndex(accountNode);
                if(i>=0) {
                    listNode.removeChildForReal(accountNode);
                    inst.Flush();
                }
            }
            */
        },
    }
    accountTimer.initWithCallback(timerFunc, 5 * 1000, 1);//re-check token
    timerFunc.notify();
    this.mUpdateTimer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);

    var updateTimerFunc = {
        running: false,
        notify: function(aTimer) {
            inst.findNewPhotosForList(flockIPhotoPeopleService.LIST_WATCHED);
/*
            if(this.running) return;
            var listNode = inst.getListNode(flockIPhotoPeopleService.LIST_WATCHED);
            var enum = listNode.getChildren();
            var toUpdate = new Array();
            while(enum.hasMoreElements()) {
                var personNode = enum.getNext();
                toUpdate.push(inst.getPersonFromNode(personNode));
            }
*/
        },
    }
    this.mUpdateTimer.initWithCallback(updateTimerFunc, this.updateInterval * 1000, 1);//re-check token
//    updateTimerFunc.notify();

    var apiListener = {
        onAPIStateChange: function(aAPI) {
            var state = aAPI.authState;
            var stateStr = "";
            switch(state) {
                case flockIPhotoAPI.LOGGED_OUT:
                {
                    var listNode = inst.getListNode(flockIPhotoPeopleService.LIST_ACCOUNTS);
                    var enum = listNode.getChildren();
                    var hasAccount = false;
                    while(enum.hasMoreElements()) {
                        var personNode = enum.getNext();
                        if(!personNode.getTarget(FLOCK_NS + "apiShortName")) continue;
                        var service = inst.apiManager.getAPIFromShortname(personNode.getTarget(FLOCK_NS + "apiShortName").getValue());
                        if(service.shortName==aAPI.shortName) {
                            listNode.removeChildForReal(personNode);
                            break;
                        }
                    }
                    inst.clearContactsForService(aAPI);
                }
                break;
                case flockIPhotoAPI.LOGGING_IN:
                break;
                case flockIPhotoAPI.LOGGED_IN:
                if(0){//disable contacts
                    var person = aAPI.getAuthPerson();
                    if(person) {
                        inst.addToList(flockIPhotoPeopleService.LIST_ACCOUNTS, person);
                    }
                    var contactsListener = {
                        onGetContactsResult : function(aEnumerator) {
                            while(aEnumerator.hasMoreElements()) {
                                var person = aEnumerator.getNext();
                                person.QueryInterface(flockIPhotoPerson);
                                inst.addToList(flockIPhotoPeopleService.LIST_CONTACTS, person);
                            }
                            inst.notifyListeners();
                        },
                        onError: function(aError) {
                        },
                    }
                    inst.clearContactsForService(aAPI);
                    inst.notifyListeners();
                    try {
                        aAPI.getContacts(contactsListener);
                    }
                    catch(e) {
                    }
                }
                break;
                case flockIPhotoAPI.LOGGED_ERROR:
                break;
            }
            timerFunc.notify();
        },
        
        onError: function(aError) {
        }
    }
    this.apiManager.addListener(apiListener);
}

flockPhotoPeopleService.prototype.isSyncingContacts = false;

flockPhotoPeopleService.prototype.syncContacts = function() {
    if(this.isSyncingContacts) return;
    this.isSyncingContacts = true;

    var services = this.apiManager.services;
    var inst = this;
    var inProcess = new Array();

    while(services.hasMoreElements()) {
        var service = services.getNext();
        var contactsListener = {
            finished: false,
            doPop : function() {
                if(this.finished) return;
                inProcess.pop();
                if(inProcess.length==0) inst.isSyncingContacts = false;
            },
            onGetContactsResult : function(aEnumerator) {
                if(this.finished) return;
                this.finished = true;
                var first = true;
                inst.clearContactsForService(this.service);
                while(aEnumerator.hasMoreElements()) {
                    var person = aEnumerator.getNext();
                        // this may not be precisely what we would like to do
                    person.QueryInterface(flockIPhotoPerson);
                    inst.addToList(flockIPhotoPeopleService.LIST_CONTACTS, person);
                }
                this.doPop();
                inst.notifyListeners();
            },
            onError: function(aError) {
                if(this.finished) return;
                this.finished = true;
                this.doPop();
            },
        }
        try {
            inProcess.push(service);
            //no throttling here
            if(!service.shortName) continue;
            contactsListener.service = service;
            service.getContacts(contactsListener);
        }
        catch(e) {
        }
    }
}

flockPhotoPeopleService.prototype.isFindingNewPhotosForList = false;

function Checker4NewPhotos() {
}

Checker4NewPhotos.prototype = {
    timer: Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer),
    peeps: null,

    onFindByUsernameResult: function(aIgnore) {
    },
    onResult: function(aIgnore) {
    },
    onGetContactsResult: function(aIgnore) {
    },
    onSearchResult: function(aEnumerator) {
        var photo = null;
        while(aEnumerator.hasMoreElements()) {
            var photo = aEnumerator.getNext();
            photo.QueryInterface(Components.interfaces.flockIPhoto);
            break;
        }
        var newStuff = false;
        if(photo) {
            var seq = 0;
            var seqNode = this.personNode.getTarget(FLOCK_NS + "seq");
            if(seqNode) {
                seq = parseInt(seqNode.getValue());
            }
            var newSeq = parseInt(photo.id);
            if(newSeq>seq) {
                this.personNode.addTargetOnce(FLOCK_NS + "seq", newSeq + "");
                var oldHasNew = this.personNode.getTarget(FLOCK_NS + "hasNew").getValue();
                if(seq!=0) {
                    newStuff = true;
                    this.personNode.addTargetOnce(FLOCK_NS + "hasNew", "true");
                    if(oldHasNew=="false") {
                        this.personNode.addTargetOnce(FLOCK_NS + "lastNewSeq", newSeq + "");
                    }
                }
            }
            if(newStuff) {
                this.inst.mNeedSave = true;
                this.inst.notifyNewPhotos();
            }
        }
        this.next();
    },
    onError: function(aError) {
        this.inst.isFindingNewPhotosForList = false;
    },
    notify: function() {
        if(!this.peeps.length) {
            this.inst.mNeedSave = true;
            this.inst.isFindingNewPhotosForList = false;
            return;
        }
        this.personNode = this.peeps.shift();
        this.person = this.inst.getPersonFromNode(this.personNode);

        var inst = this;
        // for some reason, this code works better than direct calling - it
        // just -seems- superfluous
        var myListener = {
            onSearchResult: function(aEnumerator) {
                inst.onSearchResult(aEnumerator);
            },
            onError: function(aError) {
                inst.onError(aError);
            }
        }

        try {
            this.person.service.search(myListener, this.person.id, "", "", 1, 1);
        } catch(e) {
            this.next();
        }
    },
    next: function() {
        this.timer.initWithCallback(this, 1000, 0);
    },
}

function ServicesChecker() {}

ServicesChecker.prototype = {
    mHasNewPhotos: false,
    finish: function() {
        debug("ServicesChecker.finish\n");
        this.mSvc.isFindingNewPhotosForList = false;
        if(this.mHasNewPhotos) {
            this.mSvc.mNeedSave = true;
            this.mSvc.notifyNewPhotos();
        }
        else if(this.mNeedSave) {
            this.mSvc.mNeedSave = true;
        }
    },
    doService: function() {
        debug("ServicesChecker.doService\n");
        if(this.mServiceListNames.length==0) {
            this.finish();
            return;
        }
        var inst = this;
        var shortName = this.mServiceListNames.pop();
        var list = this.mServiceLists[shortName];
        var service = list[0].service;
        var listener =  {
            onGetMostRecentPhotoForList: function(aChanged) {
                //dump("onGetMostRecentPhotoForList: " + aChanged + "\n");
                if(aChanged) {
                    inst.mNeedSave = true;
                    for(var i=0;i<list.length;++i) {
                        var person = list[i]; 
                        var node = inst.mSvc.getPersonNode(person);
                        node.addTargetOnce(FLOCK_NS + "seq", person.seq + "");
                        node.addTargetOnce(FLOCK_NS + "lastNewSeq", person.lastNewSeq + "");
                        node.addTargetOnce(FLOCK_NS + "hasNew", person.hasNew + "");
                    }
                }
                if(aChanged==Components.interfaces.flockIPhotoAPIListener.LIST_HAS_NEW) {
                    inst.mHasNewPhotos = true;
                }
                inst.doService();
            },
            onError: function(aError) {
                inst.doService();
            },
        }
        var enum = {
            i: 0,
            hasMoreElements: function() {
                return (this.i!=list.length);
            },
            getNext: function() {
                return list[this.i++];
            },
        }
        service.getMostRecentPhotoForList(listener,enum);
    },
    init: function(aSvc, aList) {
        debug("ServicesChecker.init\n");
        this.mSvc = aSvc;
        this.mServiceLists = [];
        this.mServiceListNames = new Array();
        for(var i=0;i<aList.length;++i) {
            var person = aList[i];
            var shortName = person.service.shortName;
            if(!this.mServiceLists[shortName]) {
                this.mServiceLists[shortName] = new Array();
                this.mServiceListNames.push(shortName);
            }
            this.mServiceLists[shortName].push(person);
        }
        this.doService();
    },
}

flockPhotoPeopleService.prototype.findNewPhotosForList = function(aListID) {
    if(this.isFindingNewPhotosForList) return;
    this.isFindingNewPhotosForList = true;
    var listNode = this.getListNode(aListID);
    var enum = listNode.getChildren();
    var peeps = new Array();
    while(enum.hasMoreElements()) {
        var personNode = enum.getNext();
        var person = this.getPersonFromNode(personNode);
        peeps.push(person);
    }
    debug("flockPhotoPeopleService.prototype.findNewPhotosForList\n");
    var servicesChecker = new ServicesChecker();
    servicesChecker.init(this, peeps);
}

flockPhotoPeopleService.prototype.clearContactsForService = function(aService) {
    var listNode = this.getListNode(flockIPhotoPeopleService.LIST_CONTACTS);
    var enum = listNode.getChildren();
    var toRemove = new Array();
    while(enum.hasMoreElements()) {
        var personNode = enum.getNext();
        if(!personNode.getTarget(FLOCK_NS + "apiShortName")) continue;
        var service = this.apiManager.getAPIFromShortname(personNode.getTarget(FLOCK_NS + "apiShortName").getValue());
        if(service.shortName==aService.shortName) {
            toRemove.push(personNode);
        }
    }
    while(toRemove.length>0) {
        var node = toRemove.pop();
        listNode.removeChildForReal(node);
    }
}

flockPhotoPeopleService.prototype.buildPersonURI = function(aService, aID) {
    var shortName = this.apiManager.getShortnameFromAPI(aService);
    return FLOCK_RDF_PEOPLE_ROOT + ":" + shortName + ":" + aID;
}

flockPhotoPeopleService.prototype.getPersonURI = function(aPerson) {
    return this.buildPersonURI(aPerson.service, aPerson.id);
}

flockPhotoPeopleService.prototype.initLists = function() {
    this.registerList(flockIPhotoPeopleService.LIST_WATCHED, "watched", "Watch List")
    this.registerList(flockIPhotoPeopleService.LIST_RECENT, "recent", "Recent People")
    this.registerList(flockIPhotoPeopleService.LIST_CONTACTS, "contacts", "My Contacts")
    this.registerList(flockIPhotoPeopleService.LIST_ACCOUNTS, "accounts", "My Accounts")

}

flockPhotoPeopleService.prototype.getPhotoPersonByShortName = function(aServiceName, aID) {
    try {
        var service = this.apiManager.getAPIFromShortname(aServiceName); 
        return this.getPhotoPersonByID(service, aID);
    }
    catch(e) {
        return null;
    }
}

flockPhotoPeopleService.prototype.getPhotoPersonByID = function(aService, aID) {
    try {
        var uri = this.buildPersonURI(aService, aID);
        return this.getPhotoPersonByURI(uri);
    }
    catch(e) {
        return null;
    }
}

flockPhotoPeopleService.prototype.getPhotoPersonByURI = function(aURI) {
    try {
        var personNode = this.ds.getNode(aURI);
        var rval = Components.classes[FLOCK_PHOTOPERSON_CONTRACTID].createInstance(flockIPhotoPerson);
        rval.id = personNode.getTarget(FLOCK_NS + "id").getValue();
        rval.username = personNode.getTarget(FLOCK_NS + "username").getValue();
        rval.fullname = personNode.getTarget(FLOCK_NS + "fullname").getValue();
        try {
            rval.lastNewSeq = parseInt(personNode.getTarget(FLOCK_NS + "lastNewSeq").getValue());
        }
        catch(e) {
            rval.lastNewSeq = 0;
        }
        try {
            var val = personNode.getTarget(FLOCK_NS + "hasNew").getValue();
            if(val=="true") {
                rval.hasNew = true;
            }
            else {
                rval.hasNew = false;
            }
        }
        catch(e) {
            rval.hasNew = false;
        }
        try {
            rval.seq = parseInt(personNode.getTarget(FLOCK_NS + "seq").getValue());
        }
        catch(e) {
            rval.seq = 0;
        }
        rval.service = this.apiManager.getAPIFromShortname( personNode.getTarget(FLOCK_NS + "apiShortName").getValue());
        return rval;
    }
    catch(e) {
        return null;
    }
}

flockPhotoPeopleService.prototype.getPhotoPerson = function(aPerson) {
    return this.getPhotoPersonByID(aPerson.service, aPerson.id);
}

flockPhotoPeopleService.prototype.mListNames = {};
flockPhotoPeopleService.prototype.mLocalizedListNames = {};

flockPhotoPeopleService.prototype.createPersonIfNecessary = function(aPerson) {
    var personNode = this.getPersonNode(aPerson);
    if(!personNode.getTarget(FLOCK_NS + "id")) {
        personNode.addTargetOnce(FLOCK_NS + "id", aPerson.id);
        personNode.addTargetOnce(FLOCK_NS + "iconUrl", aPerson.service.iconUrl);
        personNode.addTargetOnce(FLOCK_NS + "hasNew", "false");
        personNode.addTargetOnce(FLOCK_NS + "apiShortName", aPerson.service.shortName);
        personNode.addTargetOnce(FLOCK_NS + "fullname", aPerson.fullname);
        personNode.addTargetOnce(FLOCK_NS + "username", aPerson.username);
        personNode.addTargetOnce(FLOCK_NS + "seq", "0");
        personNode.addTargetOnce(FLOCK_NS + "lastNewSeq", "0");
        personNode.addTargetOnce(NC + "Name", aPerson.username);
    }
    return personNode;
}

flockPhotoPeopleService.prototype.addToList = function(aListID, aPerson) {
    var listNode = this.getListNode(aListID);
    var personNode = this.createPersonIfNecessary(aPerson);
    if(!this.isInList(aListID, aPerson)) {
        listNode.addChild(personNode);
    }
    this.mNeedSave = true;
}

flockPhotoPeopleService.prototype.getMostRecentPhotoPerson = function() {
    var listNode = this.getListNode(flockIPhotoPeopleService.LIST_RECENT, true);
    var enum = listNode.getChildren();
    var child = null;
    while(enum.hasMoreElements()) {
        child = enum.getNext();
        break;
    }
    if(!child) return null;
    var id = child.getTarget(FLOCK_NS + "id").getValue();
    var shortName = child.getTarget(FLOCK_NS + "apiShortName").getValue();
    return this.getPhotoPersonByShortName(shortName, id);
}

flockPhotoPeopleService.prototype.recentListLength = 25;
flockPhotoPeopleService.prototype.addToRecent = function(aPerson) {
    var listNode = this.getListNode(flockIPhotoPeopleService.LIST_RECENT, true);
    var personNode = this.createPersonIfNecessary(aPerson);

    listNode.removeChildForReal(personNode);
    listNode.addChildAt(personNode, 1);

    var enum = listNode.getChildren();
    var toRemove = new Array();
    var count = 0;
    while(enum.hasMoreElements()) {
        var child = enum.getNext();
        if(count++<this.recentListLength) continue;
        toRemove.push(child);
    }
    while(toRemove.length) {
        var child = toRemove.pop();
        listNode.removeChildForReal(child);
    }

    //this.Flush();
    this.mNeedSave = true;
    return;
}

flockPhotoPeopleService.prototype.clearRecent = function() {
    this.clearList(flockIPhotoPeopleService.LIST_RECENT);
}

flockPhotoPeopleService.prototype.getListRoot = function(aListID) {
    return FLOCK_RDF_PEOPLE_ROOT + ":lists:" + this.mListNames[aListID];
}

flockPhotoPeopleService.prototype.getListNode = function(aListID, aDontAddToTopLevel) {
    var root = this.getListRoot(aListID);
    var listNode = this.ds.getNode(root);
    if(!listNode.isContainer()) {
        //this is new.
        //var date = new Date();
        //listNode.addTargetOnce(FLOCK_NS + "date", date.getTime() + "");//XXX FIX THIS HACK TO USE DATES BETTER
        listNode.makeSeq();
        listNode.addTargetOnce(NC + "Name", this.mLocalizedListNames[aListID]);
        listNode.addTargetOnce(FLOCK_NS + "shortName", this.mListNames[aListID]);
        listNode.addTargetOnce(FLOCK_NS + "listid", aListID + "");
        if(!aDontAddToTopLevel) this.listsRoot.addChild(listNode);
        //this.listsRoot.addChild(listNode);
    }
    return listNode;
}

flockPhotoPeopleService.prototype.getPersonNode = function(aPerson) {
    var uri = this.getPersonURI(aPerson);
    var personNode = this.ds.getNode(uri);
    return personNode;
}

flockPhotoPeopleService.prototype.getPersonFromNode = function(aNode) {
    var id = aNode.getTarget(FLOCK_NS + "id").getValue();
    var shortName = aNode.getTarget(FLOCK_NS + "apiShortName").getValue();
    return this.getPhotoPersonByShortName(shortName, id);
}

flockPhotoPeopleService.prototype.removeFromList = function(aListID, aPerson) {
    var listNode = this.getListNode(aListID);
    var personNode = this.getPersonNode(aPerson);
    listNode.removeChildForReal(personNode);
    //this.Flush();
    this.mNeedSave = true;
}

flockPhotoPeopleService.prototype.isInList = function(aListID, aPerson) {
    var listNode = this.getListNode(aListID);
    var personNode = this.getPersonNode(aPerson);
    var i = listNode.getChildIndex(personNode);
    return (i>=0);
}

flockPhotoPeopleService.prototype.clearList = function(aListID) {
    var listNode = this.getListNode(aListID);
    var enum = listNode.getChildren();
    var toRemove = new Array();

    while(enum.hasMoreElements()) {
        var child = enum.getNext();
        toRemove.push(child);
    }

    while(toRemove.length) {
        child = toRemove.pop();
        listNode.removeChildForReal(child);
    }

    //this.Flush();
    this.mNeedSave = true;
}

flockPhotoPeopleService.prototype.removePerson = function(aPerson) {
    for(var p in this.mListNames) {
        this.removeFromList(p, aPerson);
    }
}

flockPhotoPeopleService.prototype.hasNewPhotos = function(aPerson) {
    var personNode = this.getPersonNode(aPerson);
    if(personNode.getTarget(FLOCK_NS + "hasNew") && personNode.getTarget(FLOCK_NS + "hasNew").getValue()=="true") {
        return true;
    }
    return false;
}

flockPhotoPeopleService.prototype.mNeedUnnotify = false;

flockPhotoPeopleService.prototype.clearAllHasNewPhotos = function() {
    var listNode = this.getListNode(flockIPhotoPeopleService.LIST_WATCHED);
    var enum = listNode.getChildren();
    while(enum.hasMoreElements()) {
        var personNode = enum.getNext();
        personNode.addTargetOnce(FLOCK_NS + "hasNew", "false");
    }
    this.mNeedUnnotify = true;
    this.mNeedSave = true;
}

flockPhotoPeopleService.prototype.clearHasNewPhotos = function(aPerson) {
    if(aPerson) {
        var personNode = this.getPersonNode(aPerson);
        if(personNode.getTarget(FLOCK_NS + "hasNew") && personNode.getTarget(FLOCK_NS + "hasNew").getValue()=="true") {
            personNode.addTargetOnce(FLOCK_NS + "hasNew", "false");
        }
    }
    this.mNeedUnnotify = true;
    this.mNeedSave = true;
}

flockPhotoPeopleService.prototype.getlastUpdated = function(aPerson) {
    throw "not impl\n";
}

flockPhotoPeopleService.prototype.update = function() {
    this.findNewPhotosForList(flockIPhotoPeopleService.LIST_WATCHED);
}

flockPhotoPeopleService.prototype.registerList = function(aListID, aShortName, aName) {
    this.mListNames[aListID] = aShortName;
    this.mLocalizedListNames[aListID] = aName;
}


flockPhotoPeopleService.prototype.lastUpdated = 0;
flockPhotoPeopleService.prototype.updateInterval = 60 * 60;//once per hour
flockPhotoPeopleService.prototype.__defineGetter__('people', function () { 
    throw "not impl\n";
})

flockPhotoPeopleService.prototype.getListEnumerator = function(aListID) {
    var listNode = this.getListNode(aListID);
    var inst = this;
    var enum = listNode.getChildren();
    var rval = {
        getNext: function() {
            var node = enum.getNext();
            var person = inst.getPersonFromNode(node);
            return person;
        },
        hasMoreElements: function() {
            return enum.hasMoreElements();
        },
    }
    return rval;
}

//////////////////////////////////////////////////////////////////////////////

flockPhotoPeopleService.prototype.URI = 'rdf:flock-people-photo';
flockPhotoPeopleService.prototype.GetSource = function (aProperty, aTarget, aTruthValue) { /*{{{*/
    return this.dataSource.GetSource (aProperty, aTarget, aTruthValue); 
}/*}}}*/
flockPhotoPeopleService.prototype.GetSources = function (aProperty, aTarget, aTruthValue) { /*{{{*/
    return this.dataSource.GetSources (aProperty, aTarget, aTruthValue); 
}/*}}}*/
flockPhotoPeopleService.prototype.GetTarget = function (aSource, aProperty, aTruthValue) { /*{{{*/
    return this.dataSource.GetTarget (aSource, aProperty, aTruthValue); 
}/*}}}*/
flockPhotoPeopleService.prototype.GetTargets = function (aSource, aProperty, aTruthValue) { /*{{{*/
    return this.dataSource.GetTargets (aSource, aProperty, aTruthValue); 
}/*}}}*/
flockPhotoPeopleService.prototype.Assert = function (aSource, aProperty, aTarget, aTruthValue) { /*{{{*/
    this.dataSource.Assert (aSource, aProperty, aTarget, aTruthValue); 
}/*}}}*/
flockPhotoPeopleService.prototype.Unassert = function (aSource, aProperty, aTarget) { /*{{{*/
    this.dataSource.Unassert (aSource, aProperty, aTarget); 
}/*}}}*/
flockPhotoPeopleService.prototype.Change = function (aSource, aProperty, aOldTarget, aNewTarget) { /*{{{*/
    this.dataSource.Change (aSource, aProperty, aOldTarget, aNewTarget); 
}/*}}}*/
flockPhotoPeopleService.prototype.Move = function (aOldSource, aNewSource, aProperty, aTarget) { /*{{{*/
    this.dataSource.Move (aOldSource, aNewSource, aProperty, aTarget); 
}/*}}}*/
flockPhotoPeopleService.prototype.HasAssertion = function (aSource, aProperty, aTarget, aTruthValue) { /*{{{*/
    return this.dataSource.HasAssertion (aSource, aProperty, aTarget, aTruthValue); 
}/*}}}*/
flockPhotoPeopleService.prototype.AddObserver = function (aObserver) { /*{{{*/
    this.dataSource.AddObserver (aObserver); 
}/*}}}*/
flockPhotoPeopleService.prototype.RemoveObserver = function (aObserver) { /*{{{*/
    this.dataSource.RemoveObserver (aObserver); 
}/*}}}*/
flockPhotoPeopleService.prototype.ArcLabelsIn = function (aNode) { /*{{{*/
    return this.dataSource.ArcLabelsIn (aNode); 
}/*}}}*/
flockPhotoPeopleService.prototype.ArcLabelsOut = function (aNode) { /*{{{*/
    return this.dataSource.ArcLabelsOut (aNode); 
}/*}}}*/
flockPhotoPeopleService.prototype.GetAllResources = function () { /*{{{*/
    return this.dataSource.GetAllResources (); 
}/*}}}*/
flockPhotoPeopleService.prototype.IsCommandEnabled = function (aSources, aCommand, aArguments) { /*{{{*/
    return this.dataSource.IsCommandEnabled (aSources, aCommand, aArguments); 
}/*}}}*/
flockPhotoPeopleService.prototype.DoCommand = function (aSources, aCommand, aArguments) { /*{{{*/
    this.dataSource.DoCommand (aSources, aCommand, aArguments); 
}/*}}}*/
flockPhotoPeopleService.prototype.GetAllCmds = function (aSource) { /*{{{*/
    return this.dataSource.GetAllCmds (aSource); 
}/*}}}*/
flockPhotoPeopleService.prototype.hasArcIn = function (aNode, aArc) { /*{{{*/
    return this.dataSource.hasArcIn (aNode, aArc); 
}/*}}}*/
flockPhotoPeopleService.prototype.hasArcOut = function (aNode, aArc) { /*{{{*/
    return this.dataSource.hasArcOut (aNode, aArc); 
}/*}}}*/
flockPhotoPeopleService.prototype.beginUpdateBatch = function () { /*{{{*/
    this.dataSource.beginUpdateBatch (); 
}/*}}}*/
flockPhotoPeopleService.prototype.endUpdateBatch = function () { /*{{{*/
    this.dataSource.endUpdateBatch (); 
}/*}}}*/
flockPhotoPeopleService.prototype.Flush = function () { /*{{{*/
    this.dataSource.Flush (); 
}/*}}}*/
flockPhotoPeopleService.prototype.FlushTo = function (aURI) { /*{{{*/
    this.dataSource.FlushTo (aURI); 
}/*}}}*/
flockPhotoPeopleService.prototype.Init = function (aURI) { /*{{{*/
    this.dataSource.Init (aURI); 
}/*}}}*/
flockPhotoPeopleService.prototype.Refresh = function (aBlocking) { /*{{{*/
    this.dataSource.Refresh (aBlocking); 
}/*}}}*/

flockPhotoPeopleService.prototype.flags = nsIClassInfo.SINGLETON;
flockPhotoPeopleService.prototype.classDescription = "Flock Photo People Service";
flockPhotoPeopleService.prototype.getInterfaces = function (count) {
    var interfaceList = [flockIPhotoPeopleService, nsIClassInfo];
    count.value = interfaceList.length;
    return interfaceList;
}

flockPhotoPeopleService.prototype.getHelperForLanguage = function (count) {return null;}/*}}}*/

flockPhotoPeopleService.prototype.QueryInterface =
function (iid) {
    if (!iid.equals(flockIPhotoPeopleService) && 
        !iid.equals(nsIRDFDataSource) &&
        !iid.equals(nsIClassInfo) &&
        !iid.equals(nsISupports))
        throw Components.results.NS_ERROR_NO_INTERFACE;
    if (iid.equals(nsIRDFDataSource) && !this.dataSourceSetup) {
    }
    return this;
}/*}}}*/

// Module implementation/*{{{*/
var PhotoPeopleModule = new Object();

PhotoPeopleModule.registerSelf =
function (compMgr, fileSpec, location, type)
{
    compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);

    compMgr.registerFactoryLocation(FLOCK_PHOTO_CID, 
                                    "Flock Photo People JS Component",
                                    FLOCK_PHOTO_CONTRACTID, 
                                    fileSpec, 
                                    location,
                                    type);
}

PhotoPeopleModule.getClassObject =
function (compMgr, cid, iid) {
    if (!cid.equals(FLOCK_PHOTO_CID))
        throw Components.results.NS_ERROR_NO_INTERFACE;
    
    if (!iid.equals(Components.interfaces.nsIFactory))
        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
    
    return PhotoPeopleServiceFactory;
}

PhotoPeopleModule.canUnload =
function(compMgr)
{
    debug("Unloading photo component.\n");
    return true;
}/*}}}*/
    
/* factory object *//*{{{*/
var PhotoPeopleServiceFactory = new Object();

PhotoPeopleServiceFactory.createInstance =
function (outer, iid) {
    debug("CI: " + iid + "\n");
    if (outer != null)
        throw Components.results.NS_ERROR_NO_AGGREGATION;

    return (new flockPhotoPeopleService()).QueryInterface(iid);
}/*}}}*/

/* entrypoint *//*{{{*/
function NSGetModule(compMgr, fileSpec) {
    return PhotoPeopleModule;
}/*}}}*/
