//
// 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;         

const FLICKR_PHOTO_CID               = Components.ID('{db720a5c-6315-49bf-a39f-b4d4aa5ed142}');

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 flockIPhotoAPI                = Components.interfaces.flockIPhotoAPI;
const flockIPhoto = Components.interfaces.flockIPhoto;
const flockIPhotoPerson = Components.interfaces.flockIPhotoPerson;
const FLOCK_PHOTO_CONTRACTID = '@flock.com/photo;1';
const FLOCK_PHOTOPERSON_CONTRACTID = '@flock.com/photo-person;1';
const FLOCK_PHOTO_ALBUM_CONTRACTID = '@flock.com/photo-album;1';
const flockIPhotoAlbum = Components.interfaces.flockIPhotoAlbum;
const FLOCK_PHOTO_ACCOUNT_CONTRACTID = '@flock.com/photo-account;1';
const flockIPhotoAccount = Components.interfaces.flockIPhotoAccount;
const flockIError = Components.interfaces.flockIError;
const FLOCK_ERROR_CONTRACTID = '@flock.com/error;1'


const FLICKR_PHOTO_CONTRACTID       = '@flock.com/?photo-api-flickr;1';
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 FLICKR_TOKEN_PREF_ID          = 'flock.photo.flickr.token';

function flickrPhotoService() { /*{{{*/
    debug ('CONSTRUCTOR: flickrPhotoService\n');
    this.init();
    this.state = flockIPhotoAPI.LOGGED_OUT;
}/*}}}*/

function flock_flickrBuildPhotoUrl(server,id,secret,size) {/*{{{*/
    var convertedSize = "";
    switch (size) {
        case "square": convertedSize = "_s"; break;
        case "thumbnail": convertedSize = "_t"; break;
        case "small": convertedSize = "_m"; break;
        case "medium": convertedSize = "_d"; break;
        case "large": convertedSize = "_b"; break;
        case "original": convertedSize = "_o"; break;
        default: convertedSize = "";
    }
    return "http://static.flickr.com/" + server + "/" + id + "_" + secret + convertedSize + ".jpg";
}
function flock_flickrBuildPageUrl(aUserID,aPhotoId)  {/*{{{*/
    return "http://flickr.com/photos/"+aUserID+"/"+aPhotoId;
}


flickrPhotoService.prototype.serviceName = "Flickr";
flickrPhotoService.prototype.shortName = "flickr";

flickrPhotoService.prototype.handleInfoResult = function(aXML) {
    var photo = aXML.getElementsByTagName("photo")[0];
    var owner = aXML.getElementsByTagName("owner")[0];
    var visibility = aXML.getElementsByTagName("visibility")[0];
    var title = aXML.getElementsByTagName("title")[0].firstChild.nodeValue;
    var id = photo.getAttribute("id");
    var server = photo.getAttribute("server");
    var secret = photo.getAttribute("secret");
    var square_url = flock_flickrBuildPhotoUrl(server,id,secret,'square');
    var small_url = flock_flickrBuildPhotoUrl(server,id,secret,'small');
    var med_url = flock_flickrBuildPhotoUrl(server,id,secret,'medium');
    var page_url = flock_flickrBuildPageUrl(owner.getAttribute('nsid'),id);
    var newPhoto = Components.classes[FLOCK_PHOTO_CONTRACTID].createInstance(flockIPhoto);
    newPhoto.webPageUrl = page_url;
    newPhoto.thumbnail = square_url;
    newPhoto.midSizePhoto = small_url;
    newPhoto.largeSizePhoto = med_url;
    newPhoto.username = owner.getAttribute('username');
    newPhoto.title = title;
    newPhoto.id = id;
    var ispublic = visibility.getAttribute("ispublic");
    if(ispublic=="1") ispublic = "true";
    else ispublic = "false";
    newPhoto.is_public = ispublic;

    return newPhoto;
}

flickrPhotoService.prototype.handlePhotosResult = function(aXML, aUserid) {
        var rval = new Array();
        var photoList = aXML.getElementsByTagName("photo");
        for (i = 0; i < photoList.length; i++) {
            var photo = photoList[i];
            
            var id = photo.getAttribute("id");
            var server = photo.getAttribute("server");
            var secret = photo.getAttribute("secret");
            var title = photo.getAttribute("title");
            var owner = photo.getAttribute("owner");
            if (!owner) owner = aUserid;
            var owner_name = photo.getAttribute("ownername");
            var square_url = flock_flickrBuildPhotoUrl(server,id,secret,'square');
            var small_url = flock_flickrBuildPhotoUrl(server,id,secret,'small');
            var med_url = flock_flickrBuildPhotoUrl(server,id,secret,'medium');
            var page_url = flock_flickrBuildPageUrl(owner,id);
            var newPhoto = Components.classes[FLOCK_PHOTO_CONTRACTID].createInstance(flockIPhoto);
            newPhoto.webPageUrl = page_url;
            newPhoto.thumbnail = square_url;
            newPhoto.midSizePhoto = small_url;
            newPhoto.largeSizePhoto = med_url;
            newPhoto.username = owner_name;
            newPhoto.userid = owner;
            newPhoto.title = title;
            newPhoto.id = id;
            
            // if the photo result set doesn't return back the ispublic
            // attribute, we assume that the server is returning only
            // public photos back in the response
            if (photo.hasAttribute("ispublic")) {
                var ispublic = photo.getAttribute("ispublic");
                if(ispublic=="1") ispublic = "true";
                else ispublic = "false";
            } else
                ispublic = "true";

            newPhoto.is_public = ispublic;

            rval.push(newPhoto);
        }    
        return rval;
}

flickrPhotoService.prototype.handleContactsResult = function(aXML) {
        var rval = new Array();
        var contactList = aXML.getElementsByTagName("contact");
        for (i = 0; i < contactList.length; i++) {
            var contact = contactList[i];
            var nsid = contact.getAttribute("nsid");
            var username = contact.getAttribute("username");
            var fullname = contact.getAttribute("fullname");
            var family = contact.getAttribute("family");
            var friend = contact.getAttribute("friend");
            
            // implement flockIPhotoPerson
            //var newContact = {};
            var newContact = Components.classes[FLOCK_PHOTOPERSON_CONTRACTID].createInstance(flockIPhotoPerson);
            newContact.id = nsid;
            newContact.username = username;
            newContact.fullname = username;
            newContact.service = this;

            rval.push(newContact);
        }    
        return rval;
}

flickrPhotoService.prototype.handleAlbumsResult = function(aXML) {
        var rval = new Array();
        var photosetList = aXML.getElementsByTagName("photoset");
        var titleList = aXML.getElementsByTagName("title");
        for (i = 0; i < photosetList.length; i++) {
            var id = photosetList[i].getAttribute("id");
            var title = titleList[i].firstChild.nodeValue;

            var newAlbum = Components.classes[FLOCK_PHOTO_ALBUM_CONTRACTID].createInstance(flockIPhotoAlbum);
            newAlbum.id = id;
            newAlbum.title = title;

            rval.push(newAlbum);
        }    
        return rval;
}

flickrPhotoService.prototype.getContacts = function(aListener) {
        var inst = this;
        var myListener = {
            onResult: function (aXML) {
                var rval = inst.handleContactsResult(aXML);
                var enum = {
                    hasMoreElements: function() {
                        return (rval.length>0);
                    },
                    getNext: function() {
                        return rval.shift();
                    },
                }
                aListener.onGetContactsResult(enum);
            },
            onError: function (aError) {
                aListener.onError(aError);
            },
        }
        var params = new Object();
        var dict = params2Dictionary(params);
        this.authenticatedCall(myListener, "flickr.contacts.getList", dict);
}

flickrPhotoService.prototype.getPhoto = function(aListener, aID) {
        var inst = this;
        var myListener = {
            onResult: function (aXML) {
                var photo = inst.handleInfoResult(aXML);
                aListener.onGetPhotoResult(photo);
            },
            onError: function (aError) {
                aListener.onError(aError);
            },
        }
        var params = new Object();
        params.photo_id = aID;
        var dict = params2Dictionary(params);
        this.call(myListener, "flickr.photos.getInfo", dict);
}

flickrPhotoService.prototype.fakeAlbums = {};
flickrPhotoService.prototype.realishAlbums = {};

flickrPhotoService.prototype.createAlbum = function(aListener, aTitle) {
    var newAlbum = Components.classes[FLOCK_PHOTO_ALBUM_CONTRACTID].createInstance(flockIPhotoAlbum);
    newAlbum.title = aTitle;
    var date = new Date();//hopefully this won't collide with an actual id!
    newAlbum.id = date.getTime();
    this.fakeAlbums[newAlbum.id] = newAlbum;
    aListener.onCreateAlbum(newAlbum);
}

flickrPhotoService.prototype.reallyCreateAlbum = function(aListener, aTitle, aID) {
        var inst = this;
        var myListener = {
            onResult: function (aXML) {
                var newAlbum = Components.classes[FLOCK_PHOTO_ALBUM_CONTRACTID].createInstance(flockIPhotoAlbum);
                var photoset = aXML.getElementsByTagName("photoset")[0];
                newAlbum.title = aTitle;
                newAlbum.id = photoset.getAttribute("id");;
                aListener.onCreateAlbumResult(newAlbum);
            },
            onError: function (aXML) {
                aListener.onError(aXML);
            },
        }
        var params = new Object();
        params.title = aTitle;
        params.primary_photo_id = aID;
        var dict = params2Dictionary(params);
        this.authenticatedCall(myListener, "flickr.photosets.create", dict);
}

flickrPhotoService.prototype.getAlbums = function(aListener, aUsername) {
        var inst = this;
        var myListener = {
            onResult: function (aXML) {
                try {
                var rval = inst.handleAlbumsResult(aXML);
                var enum = {
                    hasMoreElements: function() {
                        return (rval.length>0);
                    },
                    getNext: function() {
                        return rval.shift();
                    },
                }
                aListener.onGetAlbumsResult(enum);
                } catch(e) {
                    aListener.onError(null);
                }
            },
            onError: function (aXML) {
                aListener.onError(aXML);
            },
        }
        var params = new Object();
        if (aUsername)
            params.user_id  = aUsername;
        var dict = params2Dictionary(params);
        this.call(myListener, "flickr.photosets.getList", dict);
}

flickrPhotoService.prototype.getAccountStatus = function(aListener) {
    var inst = this;
    var myListener = {
        onResult:function (aXML) {
            try {
                debug('we got a result for account status...\n')
                var photoAccount = Components.classes[FLOCK_PHOTO_ACCOUNT_CONTRACTID].createInstance(flockIPhotoAccount);
                var username = aXML.getElementsByTagName('username')[0].firstChild.nodeValue;
                var bandwidth = aXML.getElementsByTagName('bandwidth')[0];
                var maxFileSize = aXML.getElementsByTagName('filesize')[0];
                var isPro = aXML.getElementsByTagName('user')[0].getAttribute('ispro');
                photoAccount.username = username;
                photoAccount.maxSpace = bandwidth.getAttribute('max');
                photoAccount.usedSpace = bandwidth.getAttribute('used');
                photoAccount.maxFileSize = maxFileSize;
                photoAccount.usageUnits = "bytes";
                photoAccount.isPremium = (isPro == "1" ? true: false);
                aListener.onGetAccountStatus(photoAccount);
                
            } catch (ex) {
                debug('getaccountstatus error >>>>>>>>>>>>>>>>>>>>' + ex + '\n');   
            }  
        },
        onError: function (aError) {
            aListener.onError(aError);
        },  
    }   
    var params = new Object();
    var dict = params2Dictionary(params);
    this.authenticatedCall(myListener, "flickr.people.getUploadStatus", dict);
}

function MultiGetter() {
}

MultiGetter.prototype = {
    mTimer: Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer),
    init: function(aSvc, aListener, aEnumerator) {
        this.mHasNew = false;
        this.mNeedSave = false;
        this.mSvc = aSvc;
        this.mListener = aListener;
        this.mArray = new Array();
        this.mMap = {};
        while(aEnumerator.hasMoreElements()) {
            var p = aEnumerator.getNext();
            p.QueryInterface(Components.interfaces.flockIPhotoPerson);

            this.mMap[p.id] = p;
            this.mArray.push(p.id);
            //dump(p + " " + p.id + " " + this.mArray.length + " initting \n");

        }
        if(this.mSvc.isLoggedIn) {
            this.doBiggee();
        }
        else {
            this.next();
        }
    },
    updatePerson: function(aPerson, aPhoto) {
        //dump(aPerson.username + "updating person\n");
        var seq = aPerson.seq;
        var newSeq = parseInt(aPhoto.id);
        if(newSeq>seq) {
            var oldHasNew = aPerson.hasNew;
            aPerson.seq = aPhoto.id;
            this.mNeedSave = true;
            if(seq!=0) {
                this.mHasNew = true;
                aPerson.hasNew = true
                if(oldHasNew==false) {
                    aPerson.lastNewSeq = seq + 1;
                }
            }
        }
        this.mMap[aPerson.id] = null;// wipe this entry out
    },
    doBiggee: function() {
        var inst = this;
        var listener = {
            onSearchResult: function(aEnumerator) {
                //dump("on search result\n");
                try {
                    while(aEnumerator.hasMoreElements()) {
                        var photo = aEnumerator.getNext();
                        var person = inst.mMap[photo.userid];// username is actuall an id
                        //dump("result\n")
                        if(!person) continue;
                        inst.updatePerson(person, photo);
                    }
                } catch(e) {
                    //dump("this" + e + " " + e.lineNumber + "\n");
                }
                inst.next();
            },
            onError: function(aError) {
                //dump("on error result\n");
                //hmm. mebbe should bail
                inst.finish();
            },
        }
        this.mSvc.getContactsPhotos(listener);

    },
    finish: function() {
        //dump("FINITI\n");
        if(this.mHasNew) {
            this.mListener.onGetMostRecentPhotoForList(Components.interfaces.flockIPhotoAPIListener.LIST_HAS_NEW);
        }
        else if(this.mNeedSave) {
            this.mListener.onGetMostRecentPhotoForList(Components.interfaces.flockIPhotoAPIListener.LIST_NEED_SAVE);
        }
        else {
            this.mListener.onGetMostRecentPhotoForList(Components.interfaces.flockIPhotoAPIListener.LIST_NO_CHANGE);
        }
        return;
    },
    notify: function() {
        this.doNext();
    },
    next: function() {
        this.mTimer.initWithCallback(this, 1000, 0);
    },
    doNext: function() {
        //dump("NEXT PLEASE\n");
        var inst = this;
        var person = null;
        for(;;) {
            if(!this.mArray.length) break;
            var id = this.mArray.pop();
        //dump(id + " " + "< NEXT PLEASE\n");
            person = this.mMap[id];
            if(person) {
                break;
            }
        }
        if(!person) {
            this.finish();
            return;
        }

        var listener = {
            onSearchResult: function(aEnumerator) {
            //dump("b search result\n");
                while(aEnumerator.hasMoreElements()) {
                    var photo = aEnumerator.getNext();
                    var person = inst.mMap[photo.userid];// username is actuall an id
                    inst.updatePerson(person, photo);
                    break;
                }
                inst.next();
            },
            onError: function(aError) {
            //dump("b search eror\n");
                inst.finish();
            },
        }
        this.mSvc.search(listener, person.id, "", "", 1, 1);
    },
}

flickrPhotoService.prototype.getContactsPhotos = function(aListener) {

    var inst = this;
    var myListener = {
        onResult: function (aXML) {
            var rval = inst.handlePhotosResult(aXML);
            var enum = {
                hasMoreElements: function() {
                    return (rval.length>0);
                },
                getNext: function() {
                    return rval.shift();
                },
            }
            aListener.onSearchResult(enum);
        },
        onError: function (aXML) {
            aListener.onError(null);
        },
    }
    var params = new Object();
    params.single_photo = "1";
    params.include_self = "1";
    params.extras = "owner_name,license";
    var dict = params2Dictionary(params);
    this.authenticatedCall(myListener, "flickr.photos.getContactsPhotos", dict);
}


flickrPhotoService.prototype.getMostRecentPhotoForList = function(aListener, aEnumerator) {
    var mg = new MultiGetter();
    mg.init(this, aListener, aEnumerator);
}

flickrPhotoService.prototype.findByUsername = function(aListener, aUsername) {
    var inst = this;
    var myListener = {
        onResult: function (aXML) {
            var newUserObj = Components.classes[FLOCK_PHOTOPERSON_CONTRACTID].createInstance(flockIPhotoPerson);
            var user = aXML.getElementsByTagName("user")[0];
            newUserObj.service = inst;
            newUserObj.id = user.getAttribute("id");
            newUserObj.username = aUsername;
            newUserObj.fullname = aUsername
            aListener.onFindByUsernameResult(newUserObj);
        },
        onError: function (aXML) {
            aListener.onError(aXML);
        },
    }
    var params = new Object();
    params.username = aUsername;
    var dict = params2Dictionary(params);
    this.call(myListener, "flickr.people.findByUsername", dict);
}


flickrPhotoService.prototype.lookupUser  = function(aListener, aURL) {
        var inst = this;
        var myListener = {
            onResult: function (aXML) {
                var newUserObj = Components.classes[FLOCK_PHOTOPERSON_CONTRACTID].createInstance(flockIPhotoPerson);
                var user = aXML.getElementsByTagName("user")[0];
                newUserObj.service = inst;
                newUserObj.id = user.getAttribute("id");
                newUserObj.username = user.getElementsByTagName("username")[0].childNodes[0].nodeValue;
                newUserObj.fullname = newUserObj.username;
                aListener.onLookupUser(newUserObj);
            },
            onError: function (aXML) {
                aListener.onError(aXML);
            },
        }
        var params = new Object();
        params.url = aURL;
        var dict = params2Dictionary(params);
        this.call(myListener, "flickr.urls.lookupUser", dict);
}

flickrPhotoService.prototype.getValidPerson = function(aListener, aURL) {
	this.lookupUser( {
		onLookupUser: aListener.onGetValidPerson, 
		onError: aListener.onError
	}, aURL );
}

flickrPhotoService.prototype.people_getInfo = function(aListener, aUserId) {
        var inst = this;
        var myListener = {
            onResult: function (aXML) {
                var newUserObj = Components.classes[FLOCK_PHOTOPERSON_CONTRACTID].createInstance(flockIPhotoPerson);
                var person = aXML.getElementsByTagName("person")[0];
                newUserObj.service = inst;
                newUserObj.id = person.getAttribute("id");
                newUserObj.username = person.getElementsByTagName('username')[0].firstChild.nodeValue + '';
				newUserObj.fullname = newUserObj.username;
//                newUserObj.fullname = person.getElementsByTagName('realname')[0].firstChild.nodeValue + '';					
                aListener.onGetInfo(newUserObj);
            },
            onError: function (aXML) {
                aListener.onError(aXML);
            },
        }
        var params = new Object();
        params.user_id = aUserId;
        var dict = params2Dictionary(params);
        this.call(myListener, "flickr.people.getInfo", dict);
}

flickrPhotoService.prototype.search = function(aListener, aUserid, aTags, aFreeText, aCount, aPage, aAlbumName) {
        var inst = this;
        var myListener = {
            onResult: function (aXML) {
                var rval = inst.handlePhotosResult(aXML, aUserid);
                //aListener.onResult(rval);
                var enum = {
                    hasMoreElements: function() {
                        return (rval.length>0);
                    },
                    getNext: function() {
                        return rval.shift();
                    },
                }
                aListener.onSearchResult(enum);
            },
            onError: function (aError) {
                aListener.onError(aError);
            },
        }
        var params = new Object();
        if (aUserid && aUserid.length) params.user_id = aUserid;
	params.per_page = aCount;
        params.page = aPage;
        
	if(aFreeText && aFreeText.length) params.text = aFreeText;
        if(aTags && aTags.length) params.tags = aTags;

        params.tag_mode = "all";
	params.extras = "owner_name,license";
        if (aAlbumName && aAlbumName.length)
            params.photoset_id  = aAlbumName;
                    
        var dict = params2Dictionary(params);
        if (params.photoset_id) {
            if (aPage > 1) return; // this is because the flickr call doesn't support pagination [bug 4770]
            dict = params2Dictionary(params);
            this.call(myListener, "flickr.photosets.getPhotos", dict);
        } else if (params.user_id || params.text || params.tags) {
            if(this.isLoggedIn) this.authenticatedCall(myListener, "flickr.photos.search", dict);
            else this.call(myListener, "flickr.photos.search", dict);
        } else {
            if(this.isLoggedIn) this.authenticatedCall(myListener, "flickr.photos.getRecent", dict);
            else this.call(myListener, "flickr.photos.getRecent", dict);
            //if(this.isLoggedIn) this.authenticatedCall(myListener, "flickr.photos.search", dict);
            //else this.call(myListener, "flickr.photos.search", dict);
        }
}

flickrPhotoService.prototype.mSupportsTitle = true;
flickrPhotoService.prototype.mSupportsTags = true;
flickrPhotoService.prototype.mSupportsContacts = true;
flickrPhotoService.prototype.mSupportsPrivacy = true;
flickrPhotoService.prototype.iconUrl = "chrome://browser/skin/flock/photo/flickrIcon.png";

flickrPhotoService.prototype.supportsFeature = function(aFeature) {
    var supports = {};
    supports.tags = true;
    supports.title = true;
    supports.contacts = true;
    supports.privacy = true;
    supports.albumCreation = true;
    return (supports[aFeature] == true);
}

flickrPhotoService.prototype.init = function () { /*{{{*/
    this.api = new FlickrAPI();
    this.api.svc = this;
    var inst = this;
    timerFunc = {
        inProgress: false,
        count: 0,
        observe: function(subject, topic, state) {
            if(topic == 'nsPref:changed' && state == FLICKR_TOKEN_PREF_ID) {
                var oldToken = flock_getCharPref(FLICKR_TOKEN_PREF_ID);
                if(oldToken && oldToken.length && !inst.api.isLoggedIn()) {
                    this.inProgress = true;
                    inst.api.checkToken(this, oldToken);
                }
            }
        },
        notify: function(aTimer) {
            if(this.inProgress) return;
            if(inst.api.isLoggedIn()) {
                inst.api.processPendingUploadTickets();
                inst.api.processPendingAlbumAdditions();
            }
        },
        onResult: function(aXML) {
            this.inProgress = false;
        },
        onAuth: function(aXML) {
            this.inProgress = false;
            inst.state = flockIPhotoAPI.LOGGED_IN;
        },
        onError: function(aError) {
            inst.logout();
            this.inProgress = false;
        },
    }
    timerFunc.observe(null, 'nsPref:changed', FLICKR_TOKEN_PREF_ID);
    timerFunc.notify();
    var prefs = Components.classes['@mozilla.org/preferences-service;1'].
        getService(Components.interfaces.nsIPrefBranch2);
    prefs.addObserver(FLICKR_TOKEN_PREF_ID, timerFunc, false);
    timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
    timer.initWithCallback(timerFunc,5*1 * 1000,1);//re-check token
    //};
}/*}}}*/

flickrPhotoService.prototype.__defineGetter__('supportsTitle', function () { return this.mSupportsTitle; })
flickrPhotoService.prototype.__defineGetter__('supportsTags', function () { return this.mSupportsTags; })
flickrPhotoService.prototype.__defineGetter__('supportsContacts', function () { return this.mSupportsContacts; })
flickrPhotoService.prototype.__defineGetter__('supportsPrivacy', function () { return this.mSupportsPrivacy; })
flickrPhotoService.prototype.__defineGetter__('firstRunUser', srv_getFirstRunUser)
flickrPhotoService.prototype.__defineGetter__('firstRunPerson', srv_getFirstRunPerson)

function srv_getFirstRunUser() {
    var overrideUser = flock_getCharPref('flock.photo.flickr.firstrun.user');
    return (overrideUser != null)?overrideUser : "Using the Photobar in Flock";
}

function srv_getFirstRunPerson() {
    var person = {};
    person.username = "Using the Photobar in Flock";
    person.fullname = "Using the Photobar in Flock";
    person.id = "79288061@N00";
    person.service = this;
    return person;
}

flickrPhotoService.prototype.getAuthState = function () { /*{{{*/
    return this.state;
}/*}}}*/

flickrPhotoService.prototype.__defineGetter__('authState', function () { return this.state; })

flickrPhotoService.prototype.logout = function () { /*{{{*/
    this.api.logout();
    var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"].
        getService(Components.interfaces.nsICookieManager);
    cookieManager.remove(".flickr.com","cookie_accid","/",false);
    cookieManager.remove(".flickr.com","cookie_epass","/",false);
    cookieManager.remove(".flickr.com","cookie_session","/",false);
    cookieManager.remove(".flickr.com","cookie_accid","/",false);
    cookieManager.remove(".flickr.com","use_master_until","/",false);
    this.state = flockIPhotoAPI.LOGGED_OUT;
}/*}}}*/

flickrPhotoService.prototype.login = function (aListener) { /*{{{*/
    var inst = this;
    var myListener = {
        onAuth: function() {
            inst.state = flockIPhotoAPI.LOGGED_IN;
            aListener.onAuth();
        },
        onError: function(aError) {
            inst.state = flockIPhotoAPI.LOGIN_ERROR;
            aListener.onError(aError);
        },
    }
    this.api.logout();
    inst.state = flockIPhotoAPI.LOGGING_IN;
    this.api.login(myListener);
}/*}}}*/

//flickrPhotoService.prototype.isLoggedIn = function (aListener) { /*{{{*/ return this.api.isLoggedIn(); //    if(this.api.isLoggedIn()) { //        this.state = flockIPhotoAPI.LOGGED_IN; //    } }/*}}}*/

flickrPhotoService.prototype.__defineGetter__('isLoggedIn', function () { return this.api.isLoggedIn(); })

flickrPhotoService.prototype.getAuthUser = function () { /*{{{*/
    return this.api.getAuthUser();
}/*}}}*/

flickrPhotoService.prototype.getAuthPerson = function () { /*{{{*/
    try {
        var user = this.api.getAuthUser();
        var person = {};
        person.id = user.id;
        person.fullname = user.fullname;
        person.username = user.username;
        person.service = this;
        return person;
    }
    catch(e) {
        return null;
    }

}/*}}}*/

flickrPhotoService.prototype.__defineGetter__('isUploading', function () { return this.running; })
//flickrPhotoService.prototype.isUploading = function () { /*{{{*/ return this.running; }/*}}}*/

flickrPhotoService.prototype.dictionary2Params = function (aDictionary) { /*{{{*/
    var params = {};
    var obj = {};
    var count = {};
    var keys = aDictionary.getKeys(count, obj);
    for(var i=0;i<keys.length;++i) {
        var supports = aDictionary.getValue(keys[i]);
        var supportsString = supports.QueryInterface(Components.interfaces.nsISupportsString);
        var val = supportsString.toString();
        params[keys[i]] = val;
    }
    return params;
}/*}}}*/

flickrPhotoService.prototype.call = function (aListener, aMethod, aDictionary) { /*{{{*/
    var params = this.dictionary2Params(aDictionary);
    this.api.call(aListener, aMethod, params);
}/*}}}*/

flickrPhotoService.prototype.authenticatedCall = function (aListener, aMethod, aDictionary) { /*{{{*/
    if(!this.isLoggedIn) throw "not logged in";
    var params = this.dictionary2Params(aDictionary);
    this.api.authenticatedCall(aListener, aMethod, params);
}/*}}}*/

flickrPhotoService.prototype.upload2 = function (aListener, aUpload, aFile) {
    var params = {};
    params.title = aUpload.title;
    params.description = aUpload.description;
    params.is_family = aUpload.is_family;
    params.is_friend = aUpload.is_friend;
    params.is_public = aUpload.is_public;
    params.async = "1";
    params.tags = aUpload.tags;
    this.api.upload(aListener, aFile, params, aUpload);
}

flickrPhotoService.prototype.upload = function (aListener, aFile, aDictionary, aUpload) {
    var params = this.dictionary2Params(aDictionary);
    this.api.upload(aListener, aFile, params, aUpload);
}

flickrPhotoService.prototype.cancelUpload = function () { /*{{{*/
    try { this.api.uploader.req.abort(); }
    catch(e){};
}/*}}}*/

loadLibraryFromSpec('chrome://browser/content/flock/common/flocksafe.js');
loadLibraryFromSpec("chrome://browser/content/flock/common/md5.js");
loadLibraryFromSpec("chrome://browser/content/flock/photo/photoAPI.js");

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

    loader.loadSubScript(aSpec);
}

flickrPhotoService.prototype.flags = nsIClassInfo.SINGLETON;
flickrPhotoService.prototype.classDescription = "Flickr Photo Service";
flickrPhotoService.prototype.getInterfaces = function (count) {
    var interfaceList = [flockIPhotoAPI, nsIClassInfo];
    count.value = interfaceList.length;
    return interfaceList;
}
flickrPhotoService.prototype.getHelperForLanguage = function (count) {return null;}/*}}}*/

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

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

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

    debug("registering photo factory stuff\n");
    compMgr.registerFactoryLocation(FLICKR_PHOTO_CID, 
                                    "Flickr Photo JS Component",
                                    FLICKR_PHOTO_CONTRACTID, 
                                    fileSpec, 
                                    location,
                                    type);
    //necessary category registration
    var catmgr = Components.classes["@mozilla.org/categorymanager;1"]
        .getService (Components.interfaces.nsICategoryManager);
    catmgr.addCategoryEntry('flockIPhotoAPI', 'flickr', FLICKR_PHOTO_CONTRACTID, true, true);
}

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

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

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

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

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

FlickrAPI.prototype = new Object();

function FlickrAPI() { 
    var inst = this;
    this.need2CreateAlbum = false;
    this.api_key = "92c2a562f0e9c2ed8dfe78f42a7734c7"; 
    this.api_secret = "17b26c20558cf979"; 
    this.endpoint = "http://www.flickr.com/services/rest/"; 
    this.upload_endpoint = "http://www.flickr.com/services/upload/"; 
    this.auth_endpoint = "http://www.flickr.com/services/auth/"; 
    this.req = null;
    this.frob = null;
    this.lastToken = new Date();
    this.authUser = null;
    this.filesToDelete = new Array();
    this.setEndpoint = function(aEndpoint) {
        this.endpoint = aEndpoint;
    };

    this.reset = function() {
        this.hasCreateAlbum = null;
    };

    this.getAuthUser = function() {
        return this.authUser;
    };
    this.getStoredToken = function(aListener) {
    };
    this.checkToken = function(aListener, aToken) {
        var tokenListener = {
            onResult: function (aXML) {
                var flickrUser = new FlickrUser();
                flickrUser.token = aXML.getElementsByTagName("token")[0].firstChild.nodeValue;
                var user = aXML.getElementsByTagName("user")[0];
                flickrUser.nsid = user.getAttribute("nsid");
                flickrUser.id = user.getAttribute("nsid");
                flickrUser.username = user.getAttribute("username");
                flickrUser.fullname = user.getAttribute("fullname");
                inst.authUser = flickrUser;

                debug("got token\n");
                inst.lastToken = new Date();
                aListener.onAuth();
            },
            onError: function (aError) {
                debug("error getting token\n");
                inst.frob = null;
                aListener.onError(null);
            },
        }
        var token = aToken;
        var params = {
            auth_token: token,
        };
        this.call(tokenListener, "flickr.auth.checkToken", params);
    };
    this.getToken = function(aListener) {
        var tokenListener = {
            onResult: function (aXML) {
                var flickrUser = new FlickrUser();
                flickrUser.token = aXML.getElementsByTagName("token")[0].firstChild.nodeValue;
                var user = aXML.getElementsByTagName("user")[0];
                flickrUser.nsid = user.getAttribute("nsid");
                flickrUser.id = user.getAttribute("nsid");
                flickrUser.username = user.getAttribute("username");
                flickrUser.fullname = user.getAttribute("fullname");
                inst.authUser = flickrUser;

                flock_setCharPref(FLICKR_TOKEN_PREF_ID, flickrUser.token);
                debug("got token\n");
                inst.lastToken = new Date();
                aListener.onAuth();
            },
            onError: function (aError) {
                debug("error getting token\n");
                inst.frob = null;
                if (!aError.errorCode) aError.errorCode = 1002;
                aListener.onError(aError);
            },
        }
        var frob = this.frob;
        var params = {
            frob : frob,
        };
        this.call(tokenListener, "flickr.auth.getToken", params);
    };
    this.isLoggedIn = function() {
        if(this.authUser) return true;
        return false;
    };
    this.logout = function() {
        this.authUser = null;
        this.frob = null;
        this.token = null;
        flock_setCharPref(FLICKR_TOKEN_PREF_ID, "");
    };
    this.login = function(aListener) {
        this.authUser = null;
        this.frob = null;
        var api = this;
        var frobListener = {
            onResult: function (aXML) {
                try {
                    var frobNode = aXML.getElementsByTagName("frob")[0];
                    var frob = frobNode.firstChild.nodeValue;
                    var authUrl = api.getAuthUrl(frob, "write");
                    api.frob = frob;
                    var windowManager = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService();
                    var windowManagerInterface = windowManager.QueryInterface( Components.interfaces.nsIWindowMediator);
                    var topWindow = windowManagerInterface.getMostRecentWindow(null);
                    var url = "chrome://browser/content/flock/photo/photoLoginWindow.xul?" + "url=" + escape(authUrl) + "&finalString=logout.gne";
                    topWindow.open(url,"_blank", "chrome,close,titlebar,resizable=yes,toolbar,dialog=no,scrollbars=yes,modal,centerscreen");
                    //topWindow.open(authUrl,"_blank", "chrome,close,titlebar,resizable=yes,toolbar,dialog=no,scrollbars=yes,modal,centerscreen");
                    api.getToken(aListener);
                }
                catch(e) {
                }
            },
            onError: function (aError) {
                aListener.onError(aError);
            },
        }
        this.call(frobListener, "flickr.auth.getFrob", null);
    };
    this.add2Album = function(aUploadListener, aUpload, aID) {
        var inst = this;
        var listener = {
            onResult: function(aXML) {
                inst.finalizePhoto(aUploadListener, aUpload, aID);
            },
            onError: function(aError) {
                inst.finalizePhoto(aUploadListener, aUpload, aID);
            },
        }
        var params = {};
        params.photoset_id = aUpload.album;
        params.photo_id = aID;
        this.authenticatedCall(listener, "flickr.photosets.addPhoto", params);
    };
    /*
    this.createAlbum = function(aUploadListener, aUpload, aID) {
        var inst = this;
        var listener = {
            onCreateAlbumResult: function(aAlbum) {
            },
            onError: function(aError) {
            },
        }
        var params = {};
        params.photoset_id = aUpload.album;
        params.photo_id = aID;
        this.svc.reallyCreateAlbum(listener, call(listener, "flickr.photosets.addPhoto", params));
    };
    */
    this.finalizePhoto = function(aUploadListener, aUpload, aID) {
        try {
            var inst = this;
            var getPhotoListener = {
                onGetPhotoResult: function(aPhoto) {
                    aUploadListener.onUploadFinalized(aUpload, aPhoto);
                },
                onError: function(aError) {
                    aUploadListener.onError(null);
                }
            }
            inst.svc.getPhoto(getPhotoListener, aID);
        } catch(e) {
            debug(e + "\n");
        }
    };
    this.mAddingAlbums = false;
    this.mPhotos2Album = new Array();
    this.processPendingAlbumAdditions = function() {
        if(this.mAddingAlbums) return;
        if(this.mPhotos2Album.length==0) return;
        this.mAddingAlbums = true;

        var obj = this.mPhotos2Album[0];
        var photoid = obj.photoid;
        var albumid = obj.albumid;
        var inst = this;

                    debug("ya ya mkay here\n");
        if(this.svc.fakeAlbums[albumid] && !this.svc.realishAlbums[albumid]) {
            var listener = {
                onCreateAlbumResult: function(aAlbum) {
                    inst.svc.realishAlbums[albumid] = aAlbum.id;
                    inst.mAddingAlbums = false;
                },
                onError: function(aError) {
                    inst.mAddingAlbums = false;
                },
            }
//            var params = {};
//            params.photoset_id = photoid;
//            params.photo_id = albumid;
            var fakeAlbum = this.svc.fakeAlbums[albumid];
            this.svc.reallyCreateAlbum(listener,  fakeAlbum.title, photoid);
        }
        else {
            if(inst.svc.realishAlbums[albumid]) {
                albumid = inst.svc.realishAlbums[albumid];
            }
            var listener = {
                onResult: function(aXML) {
                    inst.finalizePhoto(obj.listener, obj.upload, photoid);
                    inst.mPhotos2Album.shift();
                    inst.mAddingAlbums = false;
                    inst.processPendingAlbumAdditions();
                    //re-enter to optimize
                },
                onError: function(aError) {
                    inst.finalizePhoto(obj.listener, obj.upload, photoid);
                    inst.mPhotos2Album.shift();
                    inst.mAddingAlbums = false;
                    //re-enter to optimize?? this is an error condition, por
                    //supuesto
                },
            }
            var params = {};
            params.photoset_id = albumid;
            params.photo_id = photoid;
            this.authenticatedCall(listener, "flickr.photosets.addPhoto", params);
        }
    };
    this.mCheckTicketsInProcess = false;
    this.processPendingUploadTickets = function() {
        if(this.mCheckTicketsInProcess) return;

        var ticketList = "";

        for(var p in this.mPendingTickets) {
            var obj = this.mPendingTickets[p];
            if(!obj) continue;//really should be removeing these - make it an array?
            ticketList += obj.ticketid + ",";
        }
        if(ticketList.length==0) return;

        this.mCheckTicketsInProcess = true;

        var params = {
            tickets: ticketList,
        };

        var inst = this;

        var ticketListener = {
            onResult: function (aXML) {
                inst.mCheckTicketsInProcess = false;
                var ticketList = aXML.getElementsByTagName("ticket");
                for (i = 0; i < ticketList.length; i++) {
                    var ticket = ticketList[i];
                    var id = ticket.getAttribute("id");
                    var photoid = ticket.getAttribute("photoid");
                    var complete = ticket.getAttribute("complete");
                    var invalid = ticket.getAttribute("invalid");
                    var obj = inst.mPendingTickets[id];

                    if (complete=="0") {
                        continue;
                    }
                    else if(invalid) {
                        obj.listener.onError(null);
                    }
                    else if (complete=="1") {
                        if(obj.upload.album && obj.upload.album.length>0) {
                            obj.photoid = photoid;
                            obj.albumid = obj.upload.album;
                            obj.listener = obj.listener;
                            inst.mPhotos2Album.push(obj);
                        }
                        else {
                            inst.finalizePhoto(obj.listener, obj.upload, photoid);
                        }
                    }
                    else if (complete=="2") {
                        obj.listener.onError(null);
                    }
                    inst.mPendingTickets[id] = null;//that doesn't really remove it tho
                }
            },
            onError: function (aXML) {
                inst.mCheckTicketsInProcess = false;
            },
        }

        this.call(ticketListener, "flickr.photos.upload.checkTickets", params);
    };
    this.mPendingTickets = new Object();
    this.upload = function(aListener, aPhoto, aParams, aUpload) {
        var inst = this;
        this.uploader = new PhotoUploader();
        var myListener = {
            onResult: function(aXML) {
                var rsp = aXML.getElementsByTagName("rsp")[0];
                var stat = rsp.getAttribute("stat");
                if(stat!="ok") {
                    var err = aXML.getElementsByTagName("err")[0];
                    aListener.onError(unescape(err.getAttribute("msg")));
                }
                else {
                    aListener.onUploadComplete(aUpload);
                    var ticketid = aXML.getElementsByTagName("ticketid")[0].firstChild.nodeValue;
                    debug(ticketid + "pre ticketid\n");
                    inst.mPendingTickets[ticketid] = {
                        ticketid: ticketid,
                        upload: aUpload,
                        listener: aListener,
                    }
                    debug(ticketid + "post ticketid\n");
                    /*
                    if(aUpload.album && aUpload.album.length>0) {
                        var ticketid = aXML.getElementsByTagName("ticketid")[0].firstChild.nodeValue;
                        inst.mPendingTickets[ticketid] = {
                            ticketid: ticketid,
                            upload: aUpload,
                            listener: aListener,
                        }
                    }
                    else {
                        aListener.onUploadFinalized(aUpload, null);
                    }
                    */
                }
            },
            onError: function(aErrorCode) {
                if (aErrorCode) {
                    aListener.onError(inst.getError('HTTP_ERROR', null, aErrorCode));
                } else {
                    aListener.onError(inst.getError('SERVICE_ERROR', null));
                }
            },
        }
        this.convertBools(aParams);
        this.convertTags(aParams);

        aParams.auth_token = this.authUser.token;
        aParams.api_key = this.api_key;
        aParams = this.appendSignature(aParams);

        this.uploader.setEndpoint(this.upload_endpoint);
        this.uploader.upload(myListener, aPhoto, aParams);
        return;
    };
    this.convertBools = function(aParams) {
        for(var p in aParams) {
            if(!p.match(/^is/)) continue;
            // I hope that this doesn't break anything
            if(aParams[p]=="true") aParams[p] = "1";
            if(aParams[p]=="false") aParams[p] = "0";
        }
    };
    this.convertTags = function(aParams) {
        for(var p in aParams) {
            if(p != "tags") continue;
            var tags = aParams[p].split(",");
            for(var i=0;i<tags.length;++i) {
                tags[i] = '"' + tags[i] + '"';
                tags[i] = tags[i].replace(/\"+/g,'"');
            }
            aParams[p] = tags.join(",");
        }
    };
    this.authenticatedCall = function(aListener, aMethod, aParams) {
        if(!aParams) aParams = {};
        aParams.auth_token = this.authUser.token;
        this.call(aListener, aMethod, aParams);
        var paramString = this.getParamString(aParams, true);
    };
    this.call = function(aListener, aMethod, aParams) {
        this.convertBools(aParams);
        this.convertTags(aParams);
        if(aParams==null) aParams = new Array();
        if(aMethod) aParams.method = aMethod;
        aParams.api_key = this.api_key;
        var paramString = this.getParamString(aParams, (aMethod==null));
        var url = this.endpoint + "?" + paramString;
        this._doCall(aListener, url, null);
    };
    this._doCall = function(aListener, aUrl, aContent) {
        debug("Sending " + aUrl + "\n");
        this.req = Components.classes[XMLHTTPREQUEST_CONTRACTID].createInstance(nsIXMLHttpRequest);
        this.req.open('GET', aUrl, true);
        var req = this.req;
        this.req.onreadystatechange = function (aEvt) {
            if(req.readyState == 4) {
                try {
                    if(req.status/100 == 2) {
                       try {
                            //dump(req.responseText);
                            var rsp = req.responseXML.getElementsByTagName("rsp")[0];
                            var stat = rsp.getAttribute("stat");
                            if(stat!="ok") {
                                var err = req.responseXML.getElementsByTagName("err")[0];
                                var error= inst.getError('SERVICE_ERROR', req.responseXML, null);

                                aListener.onError(error);
                            }
                            else {
                                aListener.onResult(req.responseXML);
                            }
                        } catch (ex) {
                            // error parsing xml
                            aListener.onError(inst.getError('SERVICE_ERROR', null));
                        }
                    }
                    else {
                        //  http errors
                        aListener.onError(inst.getError("HTTP_ERROR", req.status));
                    }
                } catch(e) {
                    // XMHTTPERROR (connection lost)
                    aListener.onError(inst.getError('HTTP_ERROR',null, "9001"));
                }
            }
        }
        this.req.send(null);
    };
    this.getError = function(aErrorType, aXML, aHTTPErrorCode) {
        var error = Components.classes[FLOCK_ERROR_CONTRACTID].createInstance(flockIError);
        if  (aErrorType == "HTTP_ERROR") {
            error.errorCode = aHTTPErrorCode;
        } else if (aErrorType == "SERVICE_ERROR") {
            var errorCode;
            var errorMessage;
            var serviceErrorMessage;
            try {
                errorCode = aXML.getElementsByTagName("err")[0].getAttribute('code');
                serviceErrorMessage = aXML.getElementsByTagName("err")[0].getAttribute('msg');
            } catch (ex) {
                errorCode = "999" // in case the error xml is invalid
            }
        
            switch (errorCode) {
                case "1":
                    error.errorCode = error.PHOTOSERVICE_INVALID_USER;
                break;
                
                case "3":
                    error.errorCode = error.PHOTOSERVICE_UPLOAD_ERROR;
                break;
                
                /*case "5":
                
                break;*/
                
                case "6":
                    error.errorCode = error.PHOTOSERVICE_BANDWIDTH_REACHED;
                break;
                
                case "10":
                    
                break;
                
                case "98":
                    error.errorCode = error.PHOTOSERVICE_LOGIN_FAILED;
                break;
                
                case "99":
                    error.errorCode = error.PHOTOSERVICE_USER_NOT_LOGGED_IN;
                break;
                
                case "100":
                break;
                
                case "105":
                    error.errorCode = error.PHOTOSERVICE_UNAVAILABLE;
                break;
                             
                case "999":
                    error.errorCode = error.PHOTOSERVICE_UNKNOWN_ERROR;
                break;
                
                default:
                    error.errorCode.PHOTOSERVICE_UNKNOWN_ERROR;
                    error.serviceErrorString = serviceErrorMessage;
                break;
            }
        }
        //debug('<<<<<<<<<<<<<<'  + error.errorString + '\n');
        return error;
    };
    this.getAuthUrl = function(aFrob, aPerms) {
        var api_key = this.api_key;
        var params = {
            api_key: api_key,
            perms: aPerms,
            frob: aFrob,
        }

        var paramString = this.getParamString(params, true);
        return this.auth_endpoint + "?" + paramString;
    };
    this.getParamString = function(aParams, aNoMethod) {
        aParams = this.appendSignature(aParams);
        var rval = "";
        if(!aNoMethod) rval += "method=" + aParams.method + "&";

        var count = 0;
        for(var p in aParams) {
            if(p.match(/method/)) continue;

            if(count++!=0) rval += "&";
            rval += p + "=" + aParams[p];
        }
        return rval;
    };
    this.appendSignature = function(aParams) {
        var keys = new Array();
        for(var p in aParams) {
            keys.push(p);
        }
        keys.sort();
        var preHash = this.api_secret;
        for(var i=0;i<keys.length;++i) {
            preHash += keys[i] + aParams[keys[i]];
        }
        debug("preHash " + preHash + "\n");
        
        var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
        converter.charset = "UTF-8";
        
        var inputStream = converter.convertToInputStream(preHash);
        aParams.api_sig = hex_md5_stream(inputStream);
        return aParams;
    };
}

function FlickrUser() { 
    this.token = "";
    this.perms = "";
    this.nsid = "";
    this.id = "";
    this.username = "";
    this.fullname = "";
}

FlickrUser.prototype = new Object();
