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

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;

Components.utils.import("resource:///modules/FlockCryptoHash.jsm");

const FLOCK_PHOTO_UPLOAD_CID        = Components.ID('{9d499deb-fd01-43d5-9eda-91783a37f3bc}');
const FLOCK_PHOTO_UPLOAD_FNAME      = "flock_photo_uploads.rdf";

const FLOCK_PHOTO_UPLOADS_CONTRACTID = '@mozilla.org/rdf/datasource;1?name=flock-photo-uploads';
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_ERROR_CONTRACTID        = "@flock.com/error;1";

const FLOCK_NS                      = 'http://flock.com/rdf#';
const FLOCK_RDF_UPLOAD_ROOT         = 'urn:flock:photo:upload';

function flock_getImageUtils() {
  return Cc['@flock.com/imagescaler;1'].createInstance(Ci.flockIImageScaler);
}

function flockPhotoUploadService() {
  var profd = Cc['@mozilla.org/file/directory_service;1'].getService(Ci.nsIProperties).get('ProfD', Ci.nsIFile);
  var file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
  try {
    file.initWithPath(profd.path);
  }
  catch(e) {
    var tmpd = Components.classes['@mozilla.org/file/directory_service;1'].
      getService(Ci.nsIProperties).get('TmpD', Ci.nsIFile);
    file.initWithPath(tmpd.path);
  }

  file.append(FLOCK_PHOTO_UPLOAD_FNAME);
  // if(!file.exists()) file.createUnique(0,0600);

  var ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
  var fileHandler = ios.getProtocolHandler('file').QueryInterface(Ci.nsIFileProtocolHandler);
  var spec = fileHandler.getURLSpecFromFile(file);

  this.listeners = Array();
  this.ds = new RDFDataSource(spec);
  this.dataSource = this.ds.getRawDataSource().QueryInterface (Ci.nsIRDFRemoteDataSource);
  this.uploadRoot = this.ds.getNode(FLOCK_RDF_UPLOAD_ROOT);
  this.uploadRoot.makeSeq();

  this.Flush();

  this.obs = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
  this.obs.addObserver(this, 'flock-data-ready', false);

  var inst = this;
  this.mTimerFunc = {
    notify: function(aTimer) {
      inst.Flush();
    }
  }
  this.mTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  this.running = false;
  this._isImporting = false;
  this.mImportQueue = Array();
  this.mAsyncCopier = Cc["@mozilla.org/network/async-stream-copier;1"].createInstance(Ci.nsIAsyncStreamCopier);
}


flockPhotoUploadService.prototype.init = function PUS_init () {
  // Logger
  this.logger = Cc['@flock.com/logger;1'].createInstance(Ci.flockILogger);
  this.logger.init("photoUploadService");

  // Prepare the data 
  this._coop = Cc["@flock.com/singleton;1"]
               .getService(Ci.flockISingleton)
               .getSingleton("chrome://flock/content/common/load-faves-coop.js")
               .wrappedJSObject;
}


flockPhotoUploadService.prototype.doSave = function(aListener) {
  this.mTimer.cancel();
  this.mTimer.initWithCallback(this.mTimerFunc,2*1000,0);//re-check token
}


flockPhotoUploadService.prototype.addListener = function(aListener) {
  this.listeners.push(aListener);
  debug("adding photo upload listener " + this.listeners.length + "\n");
}


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


flockPhotoUploadService.prototype.hasOnePhotoAccount = function PUS_hasOnePhotoAccount () {
  // Iterate through all accounts, looking for photo upload accounts.
  var accountsEnum = this._coop.accounts_root.children.enumerate();
  while (accountsEnum.hasMoreElements()) {
    var acctCoopObj = accountsEnum.getNext();
    if (!acctCoopObj) continue; // getNext() can return NULL when hasMoreElements() is TRUE.
    var svc = Cc[acctCoopObj.serviceId].getService(Ci.flockIWebService);
    if (svc instanceof Components.interfaces.flockIMediaUploadWebService) {
      return true;
    }
  }

  return false;
}


flockPhotoUploadService.prototype.promptForAccount = function PUS_promptForAccount () {
  var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
  var win = wm.getMostRecentWindow('navigator:browser');

  var sbs = Cc["@mozilla.org/intl/stringbundle;1"]
            .getService(Ci.nsIStringBundleService);
  var sb = sbs
           .createBundle("chrome://flock/locale/photo/photo.properties");

  var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                  .getService(Components.interfaces.nsIPromptService);
  if (promptService.confirm(win, sb.GetStringFromName("flock.photo.alert.title"), sb.GetStringFromName("flock.photo.alert.text"))) {
    win.toggleSidebarWithMetric("flock_AccountsSidebarBroadcaster",
                                "AccountSidebar-Open",
                                null,
                                "PhotoUploaderNoAccounts",
                                true);
    var inst = this;
    var timerCallback = {
      notify: function cb_notify() {
        inst.obs.notifyObservers(null, "toggleAccountsArea", "media,people");
      }
    };
    var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    timer.initWithCallback(timerCallback, 300, Ci.nsITimer.TYPE_ONE_SHOT);
  }
}


flockPhotoUploadService.prototype._showUploaderWindow = function PUS__showUploaderWindow () {
  // Set pref that specifies whether the uploader has been used
  var prefService = Cc['@mozilla.org/preferences-service;1'].getService(Ci.nsIPrefService);
  prefService.getBranch("flock.photo.uploader.").setBoolPref("firstruncomplete", true);
  
  var wm = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
  var uploaderWindow = wm.getMostRecentWindow("Flock:PhotoUploader");
  var win = wm.getMostRecentWindow('navigator:browser');
  if (uploaderWindow) {
    uploaderWindow.focus();
  } else {
    win.open("chrome://browser/content/flock/photo/photoUploader.xul",
             "PhotoUploader",
             "chrome=1,resizable=1,width=680,height=660");
  }
}


flockPhotoUploadService.prototype.launchUploader = function PUS_launchUploader () {
  // Tell the user if he has to configure an account
  if (this.hasOnePhotoAccount())
    this._showUploaderWindow();
  else
    this.promptForAccount();
}


flockPhotoUploadService.prototype.getDefaultService = function() {
  var prefService = Cc['@mozilla.org/preferences-service;1'].getService(Ci.nsIPrefService);
  try {
    var pref = prefService.getBranch("flock.photo.").getCharPref("defaultUploadService");
    if (pref && pref.length)
      return pref;
    return null;
  } catch(e) {
    return null;
  }
}

flockPhotoUploadService.prototype.setDefaultService = function(aServiceId) {
  var prefService = Cc['@mozilla.org/preferences-service;1'].getService(Ci.nsIPrefService);
  prefService.getBranch("flock.photo.").setCharPref("defaultUploadService", aServiceId);
}



flockPhotoUploadService.prototype.save = function (aListener) {
  this.Flush();
}


flockPhotoUploadService.prototype.getURI = function (aURI) {
  return aURI;
}

flockPhotoUploadService.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(Ci.nsISupportsString);
    var val = supportsString.toString();
    params[keys[i]] = val;
  }
  return params;
}


flockPhotoUploadService.prototype.cancelUpload = function () {
  this.uploadListener.stop();
}

flockPhotoUploadService.prototype.__defineGetter__('isUploading', function () { return this.running; })
flockPhotoUploadService.prototype.__defineGetter__('isImporting', function () { return this._isImporting; })

flockPhotoUploadService.prototype.singleUpload = function(aListener, aAPI, aUpload) {
  var url = this.doTransform(aUpload);
  aAPI.upload(aListener, aUpload, url);
  return;
}

flockPhotoUploadService.prototype.addMediaToImportQueue = function (aUrl) {
  this.addToQueue(aUrl);
}

flockPhotoUploadService.prototype.importSingleMediaItem = function(aListener, aUrl) {
  this._isImporting = true;
  var inst = this;
  var listener = {
    onCreateStart: function(aTitle) {
      aListener.onMediaImportStart(aTitle, 1);
    },
    
    onCreateEnd: function(aPhotoUpload) {
      inst._isImporting = false;
      aListener.onMediaImportFinish(aPhotoUpload, 1);
    },
    
    onCreateError: function(aFilename) {
      inst._isImporting = false;
      aListener.onMediaImportError(aFilename);
    }
   }
   this.createPhotoUpload(listener, aUrl);
  },

flockPhotoUploadService.prototype.addToQueue = function (aUrl) {
  this.mImportQueue.push(aUrl);
}

flockPhotoUploadService.prototype.startImportQueue = function (aListener) {
  var inst = this;
  
  var processor = {
    count: 0,
    start: function() {
      for(var i=0;i<inst.listeners.length;++i) {
        inst.listeners[i].onMediaBatchImportStart(inst.mImportQueue.length);
      }
      this.next();
    },
    
    next: function() {
      if (inst.mImportQueue.length == 0) this.finish();
      this.count++;
      var url = inst.mImportQueue.shift();
      try {
        inst.createPhotoUpload(this, url);
      } catch (ex) {
        this.onCreateError(url);
      }
    },
    
    onCreateStart: function(aTitle) {
      for(var i=0;i<inst.listeners.length;++i) {
        inst.listeners[i].onMediaImportStart(aTitle, this.count);
      }  
    },
    
    onCreateEnd: function(aMediaUpload) {
      for(var i=0;i<inst.listeners.length;++i) {
        inst.listeners[i].onMediaImportFinish(aMediaUpload, this.count);
      }
     this.next();      
    },
    
    onCreateError: function(aFilename) {
      for(var i=0;i<inst.listeners.length;++i) {
        inst.listeners[i].onMediaImportError(aFilename);
      }  
    },

    onError: function(aError) {
      for(var i=0;i<inst.listeners.length;++i) {
        inst.listeners[i].onError(aError);
      }  
    },
        
    finish: function() {
      inst._isImporting = false;
      for(var i=0;i<inst.listeners.length;++i) {
        inst.listeners[i].onMediaBatchImportFinish();
      }
    }
  }
  
  this._isImporting = true;
  processor.start();
}

flockPhotoUploadService.prototype.cancelImport = function () {
  //this.mAsyncCopier.cancel(null);
  this._isImporting = false;
  this.mImportQueue = Array(); // flush the array
  // signal we're done
  for(var i=0;i<this.listeners.length;++i) {
    this.listeners[i].onMediaBatchImportFinish();
  }  
}


flockPhotoUploadService.prototype.getUniqueFname = function(aFName) {
  aFName.match(/.+\.(.+)/);
  var ext = RegExp.$1;
  if(!ext) ext = "";
  var now = new Date();
  var name = aFName + now + flock_photo.util.callCount++;
  var sinkFileName = FlockCryptoHash.md5(name);
  sinkFileName = sinkFileName + "." + ext;
  return sinkFileName;
}

function getOtherFName(aFName, aType) {
  aFName.match(/.+\.(.+)/);
  var ext = RegExp.$1;
  debug(aFName + "." + aType + "." + ext + "\n");
  return aFName + "." + aType + "." + ext;
}

flockPhotoUploadService.prototype.cropPhotoUpload = function (aPhotoUpload, aX1, aY1, aX2, aY2) {
  var rotation = parseInt(aPhotoUpload.rotation);
  var cropString = aPhotoUpload.crop;
  var coords = cropString.split(",");
  var x1, y1, x2, y2;
  x1 = parseFloat(coords[0]);
  y1 = parseFloat(coords[1]);
  x2 = parseFloat(coords[2]);
  y2 = parseFloat(coords[3]);

  // rotate coords back to coordinate system of the original photo
  function rotateLeftNinetyDegrees() {
    var tmp = aX1;
    aX1 = aY1;
    aY1 = 1 - aX2;
    aX2 = aY2;
    aY2 = 1 - tmp;
  }

  var tmp = rotation;
  while(tmp%360!=0) {
    rotateLeftNinetyDegrees();
    tmp -= 90;
  }

  var oldW = x2 - x1;
  var oldH = y2 - y1;

  aX1 = x1 + oldW * aX1;
  aX2 = x1 + oldW * aX2;

  aY1 = y1 + oldH * aY1;
  aY2 = y1 + oldH * aY2;

  this.regenerateThumbs(aPhotoUpload, rotation, aX1, aY1, aX2, aY2);
}

flockPhotoUploadService.prototype.revertPhotoUpload = function (aPhotoUpload) {
  this.regenerateThumbs(aPhotoUpload,0,0,0,1,1);
}

flockPhotoUploadService.prototype.doCrop= function (aPhotoUpload, aDegrees, aSize) {
  var originalPath = aPhotoUpload.originalFilePath;
  var workingPath = aPhotoUpload.workingFilePath;
  var newWorkingFileName = flock_photo.util.getUniqueFname(workingPath);
  var newWorkingFile = flock_getTmpFile(newWorkingFileName);
  var cropString = aPhotoUpload.crop;

  var rotation = parseInt(aPhotoUpload.rotation);
  var degrees = rotation;
  var size = null;

  if (aDegrees != null) degrees = aDegrees;
  if (aSize != null) size = aSize;
  // do the crop
  if ((cropString == "0,0,1,1") && (aDegrees == 0)) {
    flock_photo.util.resize(size, originalPath, newWorkingFile.path);
  }
  else {
    var coords = cropString.split(",");
    var x1, y1, x2, y2;
    x1 = parseFloat(coords[0]);
    y1 = parseFloat(coords[1]);
    x2 = parseFloat(coords[2]);
    y2 = parseFloat(coords[3]);


    var origDims = flock_photo.util.getDimensions(originalPath);

    var x = Math.floor(x1 * origDims.x);
    var y = Math.floor(y1 * origDims.y);
    var w = Math.ceil((x2 - x1) * origDims.x);
    var h = Math.ceil((y2 - y1) * origDims.y);
    flock_photo.util.cropExt(originalPath,
                      newWorkingFile.path,
                      w,
                      h,
                      x,
                      y,
                      degrees,
                      size,
                      origDims.px,
                      origDims.py
                      );
  }

  // create the working file
  return newWorkingFile;
}

flockPhotoUploadService.prototype.regenerateThumbs = function (aPhotoUpload, aDegrees, aX1, aY1, aX2, aY2) {
  if(aX1!=null) {
    aPhotoUpload.crop = aX1 + "," + aY1 + "," + aX2 + "," + aY2;
    var newWorkingFile = this.doCrop(aPhotoUpload, 0, FLOCK_PREVIEW_SIZE);
    var newWorkingSpec = flock_photo.util.getURLFromFile(newWorkingFile);
    flock_photo.util.removeFileByPath(aPhotoUpload.workingFilePath);
    aPhotoUpload.workingFilePath = newWorkingFile.path;
    aPhotoUpload.workingFileSpec = newWorkingSpec;
  }

  var originalPath = aPhotoUpload.originalFilePath;
  var previewPath = aPhotoUpload.previewFilePath;
  var thumbPath = aPhotoUpload.thumbFilePath;
  var workingPath = aPhotoUpload.workingFilePath;

  var rotation = aDegrees;
  var newPreviewFileName = flock_photo.util.getUniqueFname(previewPath);
  var newPreviewFile = flock_getTmpFile(newPreviewFileName);
  flock_photo.util.rotate(rotation, aPhotoUpload.workingFilePath, newPreviewFile.path);

  var newPreviewSpec = flock_photo.util.getURLFromFile(newPreviewFile);

  var newThumbFileName = flock_photo.util.getUniqueFname(thumbPath);
  var newThumbFile = flock_getTmpFile(newThumbFileName);
  flock_photo.util.thumb(FLOCK_THUMB_SIZE, newPreviewFile.path, newThumbFile.path);
  var newThumbSpec = flock_photo.util.getURLFromFile(newThumbFile);

  aPhotoUpload.previewFilePath = newPreviewFile.path;
  aPhotoUpload.previewFileSpec = newPreviewSpec;

  aPhotoUpload.thumbFilePath = newThumbFile.path;
  aPhotoUpload.thumbFileSpec = newThumbSpec;
  aPhotoUpload.rotation = rotation + "";

  flock_photo.util.removeFileByPath(thumbPath);
  flock_photo.util.removeFileByPath(previewPath);
}

flockPhotoUploadService.prototype.rotatePhotoUpload = function (aPhotoUpload, aDegrees) {
  var rotation = (parseInt(aPhotoUpload.rotation) + aDegrees) % 360;
  this.regenerateThumbs(aPhotoUpload, rotation, null, null, null, null);
}

flockPhotoUploadService.prototype.createPhotoUpload = function (aListener, aUrl) {
  var url = aUrl;

  var sourceFile = flock_getFileFromURL(url);
  var sourceBufferStream = flock_getBufferredInputStream(sourceFile);
  var sourceTitle = sourceFile.leafName;
  var sourceFilename = url;

  var sinkFileName = this.getUniqueFname(sourceFilename);
  var sinkFile = flock_getTmpFile(sinkFileName);
  var sinkBufferStream = flock_getBufferredOutputStream(sinkFile);

  this.mAsyncCopier.init(sourceBufferStream, sinkBufferStream, null, true, true, FLOCK_IO_CHUNKSIZE);
  aListener.onCreateStart(sourceTitle); 

  var inst = this;

  var requestObserver = {
    fname: sourceTitle,
    asyncCopier: this.mAsyncCopier,
    url: url,
    sinkFile: sinkFile,
    sinkFileName: sinkFileName,
    preview: false,
    onStartRequest: function(aRequest, aContext) {
    },
    onStopRequest: function(aRequest, aContext) {
      var sourcePath = this.sinkFile.path;
      var thumbPath = getOtherFName(sourcePath, "thumb");
      var previewPath = getOtherFName(sourcePath, "preview");
      var workingPath = getOtherFName(sourcePath, "working");
      flock_photo.util.resizeAsync(FLOCK_PREVIEW_SIZE, sourcePath, previewPath, this);
    },
    onFinished: function requestObserver_onFinished (aResult) {
      debug("onFinished: " + aResult + "\n");
      if (!inst._isImporting) return;
      var sourcePath = this.sinkFile.path;
      var thumbPath = getOtherFName(sourcePath, "thumb");
      var previewPath = getOtherFName(sourcePath, "preview");
      var workingFilePath = getOtherFName(sourcePath, "working");

      // if file size is zero
      if (!flock_photo.util.getFileFromPath(sourcePath).fileSize) {
        var error = Components.classes[FLOCK_ERROR_CONTRACTID].createInstance(Ci.flockIError);
        error.errorCode = error.PHOTOSERVICE_FILE_EMPTY;
        aListener.onError(error);
        return;
      }
      
      try {
        if(!this.preview) {
          var previewFile = flock_photo.util.getFileFromPath(previewPath);
          previewFile.copyTo(null, getOtherFName(this.sinkFile.leafName, "working"));
          this.preview = true;
          flock_photo.util.thumbAsync(FLOCK_THUMB_SIZE, previewPath, thumbPath, this);
        } else {
          var thumbFile = flock_photo.util.getFileFromPath(thumbPath);
          var thumbSpec = flock_getURLFromFile(thumbFile);

          var previewFile = flock_photo.util.getFileFromPath(previewPath);
          var previewSpec = flock_getURLFromFile(previewFile);

          var workingFile = flock_photo.util.getFileFromPath(workingFilePath);

          var date = new Date();
          
          var photo = Components.classes['@flock.com/photo-upload;1'].createInstance(Ci.flockIPhotoUpload);
          photo.originalFileSpec = flock_getURLFromFile(this.sinkFile);
          photo.originalFilePath = sourcePath;
          photo.workingFilePath = workingFilePath;
          photo.thumbFileSpec = thumbSpec;
          photo.thumbFilePath = thumbPath;
          photo.previewFileSpec = previewSpec;
          photo.previewFilePath = previewPath;
          photo.privacy_use_batch = "true";
          photo.is_public = "true";
          photo.tags = "";
          photo.notes = "";
          photo.description = "";
          photo.is_friend = "false";
          photo.is_family = "false";
          photo.title = this.fname;
          photo.fileName = this.fname;
          photo.state = "";
          photo.rotation = "0";
          photo.crop = "0,0,1,1";
          photo.id = "uri:" + FlockCryptoHash.md5(sourcePath);
          photo.album = ""; // null for now
          aListener.onCreateEnd(photo);
        }
      } catch (ex) {
        debug("photoupload: " + ex + " in " + ex.fileName+" line "+ex.lineNumber + "\n");
        debug("photoupload: " + ex.description + "\n");
        aListener.onCreateError(this.fname);
      }
    }
  };

  this.mAsyncCopier.asyncCopy(requestObserver, requestObserver.sinkFile);
}

flockPhotoUploadService.prototype.doTransform = function (aUpload) {
  var url = aUpload.originalFileSpec;
  var path = aUpload.originalFilePath;

  // if there are no changes to be made do not create new copy (as it changes jpg compression), just return path
  // see bug 3891

  if(flock_getBoolPref(FLOCK_PREF_PHOTO_RESIZE)==true || aUpload.crop!="0,0,1,1" || parseInt(aUpload.rotation)!=0) {
    var maxDim = null;
    if(flock_getBoolPref(FLOCK_PREF_PHOTO_RESIZE)==true) {
      var maxDim = flock_getIntPref(FLOCK_PREF_PHOTO_RESIZE_DIMENSIONS);
    }
    var newFile = this.doCrop(aUpload, null, maxDim);
    url = flock_getURLFromFile(newFile);

    flock_photo.util.transferExif(aUpload.originalFileSpec, url);
    return url;
  }

  return aUpload.originalFileSpec;
}

flockPhotoUploadService.prototype.upload = function (aAPI) {
  if(this.isUploading) { return; }
  var inst = this;
  this.api = aAPI;
  this.uploadListener = {
    init: function(aAPI, aKids, aSvc, aMax) {
      this.api = aAPI;
      this.svc = aSvc;
      this.kids = aKids;
      this.count = 0;
      this.max = aMax;
      this.imageUtils = flock_getImageUtils();
      this.svc.running = true;
      this.doUpload();
    },
    stop: function() {
      this.svc.doSave();
      this.svc.running = false;
      // XXX andy: this isn't in the idl...
      //this.api.cancelUpload();
      for(var i=0;i<this.svc.listeners.length;++i) {
        this.svc.listeners[i].onUploadComplete();
      }
    },
    doUpload: function() {
      if (!this.svc.running || !this.kids.hasMoreElements()) {
        if(this.svc.running) this.stop();
        return;
      }
      ++this.count;
      this.uploadResource = this.kids.getNext();
      var upload = this.svc.getPhotoUpload(this.uploadResource.getTarget(FLOCK_NS + "id").getValue());
      var url = inst.doTransform(upload);
      var params = new Array();
      params.title = upload.title;
      params.description = upload.description;
      params.is_family = upload.is_family;
      params.is_friend = upload.is_friend;
      params.is_public = upload.is_public;
      params.async = "1";
      params.tags = upload.tags;
      params.notes = upload.notes;
      params.album = upload.album;
      this.currentUpload = upload;
      this.uploadResource.addTargetOnce(FLOCK_NS + "state", "uploading");
      for (var i=0;i<this.svc.listeners.length;++i) {
        this.svc.listeners[i].onUploadStart(this.currentUpload, this.count, this.max);
      }
      this.api.upload(this, upload, url);
    },
    onUploadComplete: function(aUpload) {
      this.doUpload();
      for(var i=0;i<this.svc.listeners.length;++i) {
        this.svc.listeners[i].onUpload(aUpload, this.count, this.max);
      }
      this.svc.removePhotoUpload(aUpload, false);
    },
    onUploadFinalized: function(aUpload, aPhoto) {
    },
    onError: function(aError) {
      this.uploadResource.addTargetOnce(FLOCK_NS + "state", "error");
      for(var i=0;i<this.svc.listeners.length;++i) {
        if(this.svc.running) //don't send back error on cancelUpload
          this.svc.listeners[i].onUploadError(this.currentUpload, this.count, this.max, aError);
      }
      this.stop();
      debug("error uploading\n");
    },
    onProgress: function(currentProgress) {
      for(var i=0;i<this.svc.listeners.length;++i) {
        this.svc.listeners[i].onUploadProgress(currentProgress);
      }
    }
  };
  var kids = this.uploadRoot.getChildren();

  // XXX have to reverse the order of the child nodes
  // so that the service gets the oldest photo first uploaded.
  var orderedKidsEnumerator  = {
    mList: Array(),
    currentIndex: 0,
    add : function(aItem) {
      this.mList.unshift(aItem);
    },
    getNext : function() {
      this.currentIndex++;
      return this.mList[this.currentIndex-1];
    },
    hasMoreElements : function() {
      return this.mList[this.currentIndex] != null ? true : false;
    },
    getChildCount: function() {
      return this.mList.length;
    }
  }
  while (kids.hasMoreElements())
    orderedKidsEnumerator.add(kids.getNext());

  this.uploadListener.init(this.api, orderedKidsEnumerator, this, orderedKidsEnumerator.getChildCount());
}

flockPhotoUploadService.prototype.isNewUpload = function (aUpload) {
  if(aUpload.getTarget(FLOCK_NS + "id")) return false;
  return true;
}

flockPhotoUploadService.prototype.savePhotoUpload = function (aUpload) {
  debug("flockPhotoUploadService.prototype.savePhotoUpload " + aUpload.id + "\n");
  debug("flockPhotoUploadService.prototype.savePhotoUpload notes = " + aUpload.notes + "\n");
  var upload = this.ds.getNode(this.getURI(aUpload.id));
  upload.addTargetOnce(FLOCK_NS + "id", aUpload.id);
  upload.addTargetOnce(FLOCK_NS + "originalFileSpec", aUpload.originalFileSpec);
  upload.addTargetOnce(FLOCK_NS + "originalFilePath", aUpload.originalFilePath);
  upload.addTargetOnce(FLOCK_NS + "workingFilePath", aUpload.workingFilePath);
  upload.addTargetOnce(FLOCK_NS + "thumbFilePath", aUpload.thumbFilePath);
  upload.addTargetOnce(FLOCK_NS + "thumbFileSpec", aUpload.thumbFileSpec);
  upload.addTargetOnce(FLOCK_NS + "previewFilePath", aUpload.previewFilePath);
  upload.addTargetOnce(FLOCK_NS + "previewFileSpec", aUpload.previewFileSpec);
  upload.addTargetOnce(FLOCK_NS + "fileName", aUpload.fileName);
  upload.addTargetOnce(FLOCK_NS + "title", aUpload.title);
  upload.addTargetOnce(FLOCK_NS + "privacy_use_batch", aUpload.privacy_use_batch);
  upload.addTargetOnce(FLOCK_NS + "is_public", aUpload.is_public);
  upload.addTargetOnce(FLOCK_NS + "is_friend", aUpload.is_friend);
  upload.addTargetOnce(FLOCK_NS + "is_family", aUpload.is_family);
  upload.addTargetOnce(FLOCK_NS + "tags", aUpload.tags);
  upload.addTargetOnce(FLOCK_NS + "notes", aUpload.notes);
  upload.addTargetOnce(FLOCK_NS + "state", aUpload.state);
  upload.addTargetOnce(FLOCK_NS + "description", aUpload.description);
  upload.addTargetOnce(FLOCK_NS + "rotation", aUpload.rotation);
  upload.addTargetOnce(FLOCK_NS + "album", aUpload.album);
  upload.addTargetOnce(FLOCK_NS + "crop", aUpload.crop);
  this.doSave();
}

flockPhotoUploadService.prototype.addPhotoUpload = function (aReference, aUpload) {
  debug("flockPhotoUploadService.prototype.addPhotoUpload " + aUpload.state + "\n");
  for(var i=0;i<this.listeners.length;++i) {
    this.listeners[i].onUploadAdd(null);
  }

  var newUpload = this.ds.getNode(this.getURI(aUpload.id));
  this.uploadRoot.removeChildForReal(newUpload);

  if(this.isNewUpload(newUpload)) {
    this.savePhotoUpload(aUpload);
  }
  if(aReference) {
    var ref = this.ds.getNode(this.getURI(aReference.id));
    var index = this.uploadRoot.getChildIndex(ref);
    this.uploadRoot.addChildAt(newUpload, index);
  }
  else {
    this.uploadRoot.addChildAt(newUpload, 1);
  }
  this.doSave();
  for(var i=0;i<this.listeners.length;++i) {
    this.listeners[i].onUploadAdd(aUpload);
  }
}


flockPhotoUploadService.prototype.removePhotoUpload = function (aUpload, aDontDelete) {
  try {
    aUpload = this.getPhotoUpload(aUpload.id);
  }
  catch(e) {}
  var toDelete = this.ds.getNode(this.getURI(aUpload.id));
  this.uploadRoot.removeChild(toDelete);
  this.ds.deleteRecursive(toDelete);
  this.doSave();
  if(!aDontDelete) {
    try {
    flock_photo.util.removeFileByPath(aUpload.workingFilePath);
    flock_photo.util.removeFileByPath(aUpload.originalFilePath + "_backup");
    flock_photo.util.removeFileBySpec(aUpload.originalFileSpec);
    flock_photo.util.removeFileBySpec(aUpload.previewFileSpec);
    flock_photo.util.removeFileBySpec(aUpload.thumbFileSpec);
    flock_photo.util.removeFileByPath(aUpload.originalFilePath);
    flock_photo.util.removeFileByPath(aUpload.previewFilePath);
    flock_photo.util.removeFileByPath(aUpload.thumbFilePath);
    } catch(e) {
    }
  }
  for(var i=0;i<this.listeners.length;++i) {
    this.listeners[i].onUploadRemove();
  }
}

flockPhotoUploadService.prototype.reorderPhotoUpload = function (aReference, aUpload) {
  this.addPhotoUpload();
  return;
}

flockPhotoUploadService.prototype.getPhotoUpload = function (aURI) {
  var upload = this.ds.getNode(this.getURI(aURI));
  var rval = new Object();
  rval.id = upload.getTarget(FLOCK_NS + "id").getValue();
  rval.originalFileSpec = upload.getTarget(FLOCK_NS + "originalFileSpec").getValue();
  rval.originalFilePath = upload.getTarget(FLOCK_NS + "originalFilePath").getValue();
  rval.workingFilePath = upload.getTarget(FLOCK_NS + "workingFilePath").getValue();
  rval.thumbFileSpec = upload.getTarget(FLOCK_NS + "thumbFileSpec").getValue();
  rval.thumbFilePath = upload.getTarget(FLOCK_NS + "thumbFilePath").getValue();
  rval.previewFileSpec = upload.getTarget(FLOCK_NS + "previewFileSpec").getValue();
  rval.previewFilePath = upload.getTarget(FLOCK_NS + "previewFilePath").getValue();
  rval.fileName = upload.getTarget(FLOCK_NS + "fileName").getValue();
  rval.title = upload.getTarget(FLOCK_NS + "title").getValue();
  rval.privacy_use_batch = upload.getTarget(FLOCK_NS + "privacy_use_batch").getValue();
  rval.is_public = upload.getTarget(FLOCK_NS + "is_public").getValue();
  rval.is_friend = upload.getTarget(FLOCK_NS + "is_friend").getValue();
  rval.is_family = upload.getTarget(FLOCK_NS + "is_family").getValue();
  rval.tags = upload.getTarget(FLOCK_NS + "tags").getValue();
  rval.notes = upload.getTarget(FLOCK_NS + "notes").getValue();
  rval.state = upload.getTarget(FLOCK_NS + "state").getValue();
  rval.description = upload.getTarget(FLOCK_NS + "description").getValue();
  rval.rotation = upload.getTarget(FLOCK_NS + "rotation").getValue();
  rval.album = upload.getTarget(FLOCK_NS + "album").getValue();
  rval.crop = upload.getTarget(FLOCK_NS + "crop").getValue();
  return rval;
}

flockPhotoUploadService.prototype.cleanPhotoUploadCache = function () {
  debug("Clean the cache.\n");
  try {
    var sinkFile = Cc["@mozilla.org/file/directory_service;1"]
      .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
    sinkFile.append("flock_imagecache");
    if (sinkFile.exists()) {
      debug("Image cache found\n");
      var files = sinkFile.directoryEntries;
      while (files.hasMoreElements()) {
        var file = files.getNext().QueryInterface(Ci.nsIFile);
        try {
          file.remove(false);
        } catch (e) {
          debug("could not delete file: " + file.path + " exception: " + e + "\n");
        }
      }
    } else {
      debug("Image cache not found\n");
    }
  } catch (e) {
    debug("exception cleaning the cache: " + e + "\n");
  }
}

// nsIObserver
flockPhotoUploadService.prototype.observe =
function PUS_observe (subject, topic, state)
{
  switch (topic) {
    case 'flock-data-ready':
      this.obs.removeObserver(this, 'flock-data-ready');
      this.init();
      return;
  }
}

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

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

  loader.loadSubScript(aSpec);
}

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

flockPhotoUploadService.prototype.flags = Ci.nsIClassInfo.SINGLETON;
flockPhotoUploadService.prototype.classDescription = "Flock Photo Service";
flockPhotoUploadService.prototype.getInterfaces = function (count) {
  var interfaceList = [Ci.flockIPhotoUploadService, Ci.nsIClassInfo, Ci.nsIObserver];
  count.value = interfaceList.length;
  return interfaceList;
}
flockPhotoUploadService.prototype.getHelperForLanguage = function (count) {return null;}

// the nsISupports implementation
flockPhotoUploadService.prototype.QueryInterface =
function (iid) {
  if (!iid.equals(Ci.flockIPhotoUploadService) &&
    !iid.equals(Ci.nsIRDFDataSource) &&
    !iid.equals(Ci.nsIClassInfo) &&
    !iid.equals(Ci.nsIObserver) &&
    !iid.equals(Ci.nsISupports))
    throw Cr.NS_ERROR_NO_INTERFACE;
  if (iid.equals(Ci.nsIRDFDataSource) && !this.dataSourceSetup) {
  }
  return this;
}

// Module implementation
var Module = new Object();

Module.registerSelf =
function (compMgr, fileSpec, location, type)
{
  compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);

  compMgr.registerFactoryLocation(FLOCK_PHOTO_UPLOAD_CID,
                                  "Flock Photo JS Component",
                                  FLOCK_PHOTO_UPLOADS_CONTRACTID,
                                  fileSpec,
                                  location,
                                  type);
  var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
  categoryManager.addCategoryEntry("flock-startup", "Flock Photo Upload Service", "service," + FLOCK_PHOTO_UPLOADS_CONTRACTID, true, true);
}

Module.getClassObject =
function (compMgr, cid, iid) {
  if (!cid.equals(FLOCK_PHOTO_UPLOAD_CID))
    throw Cr.NS_ERROR_NO_INTERFACE;

  if (!iid.equals(Ci.nsIFactory))
    throw Cr.NS_ERROR_NOT_IMPLEMENTED;

  return PhotoUploadServiceFactory;
}

Module.canUnload =
function(compMgr) {
  return true;
}

/* factory object */
var PhotoUploadServiceFactory = new Object();

PhotoUploadServiceFactory.createInstance =
function (outer, iid) {
  if (outer != null)
    throw Components.results.NS_ERROR_NO_AGGREGATION;

  return (new flockPhotoUploadService()).QueryInterface(iid);
}

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