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

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

CU.import("resource://gre/modules/ISO8601DateUtils.jsm");
CU.import("resource:///modules/FlockOnetUtils.jsm");
CU.import("resource:///modules/FlockSvcUtils.jsm");
CU.import("resource:///modules/FlockStringBundleHelpers.jsm");
CU.import("resource:///modules/FlockMediaUtils.jsm");
CU.import("resource:///modules/FlockPhotoUploader.jsm");
CU.import("resource:///modules/FlockXPCOMUtils.jsm");
CU.import("resource:///modules/FlockPrefsUtils.jsm");
FlockXPCOMUtils.debug = false;

const ONET_MEDIA_CID = Components.ID("{6E5A1623-783A-4653-9371-1B182FDC4E50}");
const ONET_MEDIA_CONTRACTID = "@flock.com/webservice/media/onet;1";

const ONET_MEDIA_CLASS_NAME = "Onet.pl Foto";
const CLASS_SHORT_NAME = "onetmedia";
const ONET_MEDIA_MODULE_NAME = "onetmedia";

const ONET_FAVICON = "chrome://flock/content/services/onet/onetFavicon.png";

// Service Enabled Pref
const SERVICE_ENABLED_PREF = "flock.service.onet.enabled";

// Media
const ONET_MEDIA_DECORATION_ALBUM_LIMIT = 20;
const ONET_FEED_XML = "http://foto.onet.pl/flock/feed.xml";
const ONET_UPLOAD_ENDPOINT = "http://foto.onet.pl/flock/api";

const FLOCK_PHOTO_ALBUM_CONTRACTID = "@flock.com/photo-album;1";

/**
 * Create a lightweight quasi-nsISimpleEnumerator from a passed in array.
 *
 * Doing it here in JS prevents having to cross XPCOM boundaries, resulting
 * in a performance bump.
 *
 * @param aArray  The array to create the enumerator from.
 * @returns  the enumerator claiming to be a nsISimpleEnumerator.
 */
function createEnum(aArray) {
  return {
    QueryInterface: function (iid) {
      if (iid.equals(Components.interfaces.nsISimpleEnumerator)) {
        return this;
      }
      throw Components.results.NS_ERROR_NO_INTERFACE;
    },
    hasMoreElements: function() {
      return (aArray.length > 0);
    },
    getNext: function() {
      return aArray.shift();
    }
  };
}

/*************************************************************************
 * Component: OnetMediaService
 *************************************************************************/
function OnetMediaService() {
  var loader = CC["@mozilla.org/moz/jssubscript-loader;1"]
               .getService(CI.mozIJSSubScriptLoader);
  loader.loadSubScript("chrome://browser/content/utilityOverlay.js");

  OnetUtils.initService(this, CLASS_SHORT_NAME, ONET_MEDIA_CONTRACTID);

  // Initialize account constructor
  this._accountClassCtor = OnetMediaAccount;

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

  var converter = CC["@mozilla.org/intl/scriptableunicodeconverter"]
                  .createInstance(CI.nsIScriptableUnicodeConverter);
  converter.charset = "UTF-8";

  // Default Media Bar Channels
  this._channels = {
     "special:top_favorites": {
       title: converter.ConvertToUnicode("Zdjęcia dnia"),
       supportsSearch: false,
       feed: ONET_FEED_XML + "?format=atom&query=selectedphotos"
     },
     "special:added": {
       title: converter.ConvertToUnicode("Nowe zdjęcia"),
       supportsSearch: false,
       feed: ONET_FEED_XML + "?format=atom&query=newphotos"
     }
  };

  // Add default flockIMediaWebService methods
  var mws = FlockSvcUtils.flockIMediaWebService;
  mws.addDefaultMethod(this, "enumerateChannels");
  mws.addDefaultMethod(this, "getChannel");
  mws.addDefaultMethod(this, "getIconForQuery");
  mws.addDefaultMethod(this, "getMediaItemFormatter");

  // Add default flockIMediaUploadWebService methods
  var muws = FlockSvcUtils.flockIMediaUploadWebService;
  muws.addDefaultMethod(this, "getAlbumsForUpload");
}

/*************************************************************************
 * OnetMediaService: XPCOM Component Creation
 *************************************************************************/

OnetMediaService.prototype = new FlockXPCOMUtils.genericComponent(
  ONET_MEDIA_CLASS_NAME,
  ONET_MEDIA_CID,
  ONET_MEDIA_CONTRACTID,
  OnetMediaService,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.nsITimerCallback,
    CI.flockIWebService,
    CI.flockIAuthenticateNewAccount,
    CI.flockILoginWebService,
    CI.flockIMediaWebService,
    CI.flockIMediaUploadWebService
  ]
);

// FlockXPCOMUtils.genericModule() categories
OnetMediaService.prototype._xpcom_categories = [
  { category: "wsm-startup" },
  { category: "flockWebService", entry: CLASS_SHORT_NAME },
  { category: "flockMediaProvider", entry: CLASS_SHORT_NAME }
];

/*************************************************************************
 * OnetMediaService: flockIWebService Implementation
 *************************************************************************/

// readonly attribute AString title;
OnetMediaService.prototype.title = ONET_MEDIA_CLASS_NAME;

// readonly attribute AString shortName;
OnetMediaService.prototype.shortName = CLASS_SHORT_NAME;

// readonly attribute AString icon;
OnetMediaService.prototype.icon = ONET_FAVICON;

// DEFAULT: readonly attribute AString url;

// readonly attribute AString contractId;
// ALMOST duplicated by nsIClassInfo::contractID
// with different case: contractId != contractID
OnetMediaService.prototype.contractId = ONET_MEDIA_CONTRACTID;

// readonly attribute AString urn;
OnetMediaService.prototype.urn = "urn:" + CLASS_SHORT_NAME + ":service";

// DEFAULT: nsIStringBundle getStringBundle();
// DEFAULT: boolean isHidden();

/*************************************************************************
 * OnetMediaService: flockILoginWebService interface
 *************************************************************************/

// readonly attribute AString domains;
OnetMediaService.prototype.__defineGetter__("domains",
function OnetMediaService_getdomains() {
  this._logger.debug("Getting attribute: domains");

  return this._wd.getString(CLASS_SHORT_NAME, "domains", "");
});

// readonly attribute boolean needPassword;
OnetMediaService.prototype.needPassword = false;

// readonly attribute AString loginURL;
OnetMediaService.prototype.__defineGetter__("loginURL",
function OnetMediaService_getloginURL() {
  this._logger.debug("Getting attribute: loginURL");

  return this._wd.getString(CLASS_SHORT_NAME, "loginURL", "");
});

/**
 * flockIWebServiceAccount addAccount (in AString aAccountId,
 *                                     in boolean aIsTransient,
 *                                     in flockIListener aFlockListener)
 * @see flockILoginWebService#addAccount
 */
OnetMediaService.prototype.addAccount =
function OnetMediaService_addAccount(aAccountId, aIsTransient, aFlockListener) {
  this._logger.debug(".addAccount('" + aAccountId + "', "
                                     + aIsTransient + ", aFlockListener)");

  if (!aAccountId) {
    if (aFlockListener) {
      var error = OnetUtils.api.getError(999, "No Account ID provided");
      aFlockListener.onError(error, arguments.callee.name);
    }
    return null;
  }
  aAccountId = aAccountId.toLowerCase();

  var pw = this._acUtils.getPassword(this.urn + ":" + aAccountId);
  var username = (pw) ? pw.username : aAccountId;

  var accountURN = this._acUtils.createAccount(this,
                                               aAccountId,
                                               username,
                                               null,
                                               aIsTransient);
  var account = this.getAccount(accountURN);
  account.setParam("service", this._coopObj);
  if (aFlockListener) {
    aFlockListener.onSuccess(account, "addAccount");
  }
  return account;
};

// DEFAULT: boolean docRepresentsSuccessfulLogin (in nsIDOMHTMLDocument aDocument);
// DEFAULT: AString getAccountIDFromDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: nsIStringEnumerator getAccountUrns();
// DEFAULT: flockIWebServiceAccount getAccount(in AString aAccountUrn);
// DEFAULT: flockIWebServiceAccount getAuthenticatedAccount();
// DEFAULT: nsIPassword getCredentialsFromForm(in nsIDOMHTMLFormElement aForm);

/**
 * void logout ()
 * @see flockILoginWebService#logout
 */
OnetMediaService.prototype.logout =
function OnetMediaService_logout(aDocument) {
  this._logger.debug(".logout()");
  this._acUtils.removeCookies(this._wd.getSessionCookies(CLASS_SHORT_NAME));
  this._acUtils.markAllAccountsAsLoggedOut(this);
  this._authMgr.reportCompleteLogoutFromNetwork(ONET_MEDIA_CONTRACTID);
};

// DEFAULT: boolean ownsDocument (in nsIDOMHTMLDocument aDocument);
// DEFAULT: boolean ownsLoginForm (in nsIDOMHTMLFormElement aForm)
// DEFAULT: void removeAccount (in AString aUrn);

/**
 * void updateAccountStatusFromDocument (in nsIDOMHTMLDocument aDocument,
 *                                       flockIWebServiceAccount aAccount,
 *                                       in flockIListener aFlockListener)
 * @see flockILoginWebService#updateAccountStatusFromDocument
 */
OnetMediaService.prototype.updateAccountStatusFromDocument =
function OnetMediaService_updateAccountStatusFromDocument(aDocument,
                                                          aAccount,
                                                          aFlockListener)
{
  if (aAccount) {
    // We are currently logged in to aAcctUrn
    if (!aAccount.isAuthenticated()) {
      this._authMgr.reportCompleteLoginToNetwork(aAccount);
      // Login the account and authenticate api
      aAccount.login(aFlockListener);
    }
  } else if (this._wd.detect(this.shortName, "loggedout", aDocument, null)) {
    // We're logged out (of all accounts), logout and deauthenticate the api
    this.logout();
  }
};

/*************************************************************************
 * OnetMediaService: flockIAuthenticateNewAccount interface
 *************************************************************************/

OnetMediaService.prototype.authenticateNewAccount =
function OnetMediaService_authenticateNewAccount(aFlockListener) {
  this._logger.info("authenticateNewAccount(aFlockListener)");
  var wm = CC["@mozilla.org/appshell/window-mediator;1"]
           .getService(CI.nsIWindowMediator);
  var win = wm.getMostRecentWindow("navigator:browser");
  win.openUILinkIn(this.url, "tab");
};

/*************************************************************************
 * OnetMediaService: flockIMediaUploadWebService Implementation
 *************************************************************************/

/**
 * void getAccountStatus(in flockIListener aListener);
 * @see flockIMediaUploadWebService#getAccountStatus
 */
OnetMediaService.prototype.getAccountStatus =
function OnetMediaService_getAccountStatus(aFlockListener) {
  var result = FlockSvcUtils.newResults();

  result.setPropertyAsAString("maxSpace", this.maxSpace);
  result.setPropertyAsAString("usedSpace", this.usedSpace);

  result.setPropertyAsAString("maxFileSize", 5000*1024);

  result.setPropertyAsAString("usageUnits", "bytes");
  result.setPropertyAsBool("isPremium", true);

  aFlockListener.onSuccess(result, "");
};

// DEFAULT: void getAlbumsForUpload(in flockIListener aListener,
//                                  in AString aUsername);

/**
 * void getAlbums(in flockIListener aFlockListener, in AString aUsername);
 * @see flockIMediaWebService#getAlbums
 */
OnetMediaService.prototype.getAlbums =
function OnetMediaService_getAlbums(aFlockListener, aUsername) {
  this._logger.info(".getAlbums(..., '" + aUsername + "')");

  var inst = this;
  var albumListener = {
    onSuccess: function getAlbums_albumListener_onSuccess(aSubject) {
      var albums = [];
      // Iterate over the user's albums
      var result = aSubject.wrappedJSObject;
      var resultAlbums = result.albums
      for each(var album in resultAlbums) {
        var newAlbum = CC[FLOCK_PHOTO_ALBUM_CONTRACTID]
                       .createInstance(CI.flockIPhotoAlbum);
        // The media bar will conveniently strip the albumId (only used
        // by Onet for upload)
        newAlbum.id = album.albumToken + ":" + album.albumId;
        newAlbum.title = album.name;
        inst._logger.debug(".getAlbums() Found album: " + newAlbum.title);
        albums.push(newAlbum);
      }

      // Update the user storage quota
      inst.maxSpace = result.quota * 1024;
      inst.usedSpace = (result.quota - result.quotaFree) * 1024;
      if (aFlockListener) {
        aFlockListener.onSuccess(createEnum(albums), "success");
      }
    },
    onError: function getAlbums_albumListener_onError(aFlockError) {
      inst._logger.debug(".getAlbums() error: " + aFlockError.errorString);
      if (aFlockListener) {
        aFlockListener.onError(aFlockError, null);
      }
    }
  }
  OnetUtils.api.getAlbums(albumListener, aUsername);
};

/**
 * boolean supportsFeature(in AString aFeature);
 * @see flockIMediaUploadWebService#supportsFeature
 */
OnetMediaService.prototype.supportsFeature =
function OnetMediaService_supportsFeature(aFeature) {
  var supports = {
    albumCreation: true,
    contacts: false,
    filename: false,
    privacy: false,
    tags: false,
    description: false,
    title: true
  };
  return supports[aFeature];
};

/**
 * void upload (in flockIPhotoUploadAPIListener aUploadListener,
 *              in flockIPhotoUpload aUpload, in AString aFilename)
 * @see flockIMediaUploadWebService#upload
 */
OnetMediaService.prototype.upload =
function OnetMediaService_upload(aUploadListener, aUpload, aFilename) {
  this._logger.debug(".upload(aUploadListener, aUpload, '" + aFilename + "')");

  var title = aUpload.title;
  if (FlockPrefsUtils.getBoolPref("flock.photo.uploader.breadcrumb.enabled")) {
    if (title) {
      title += " - ";
    }
    title += flockGetString("services/services",
                            "flock.uploader.breadcrumb");
  }

  // Are we attempting to upload to a specific album, if not
  // Onet uses 0 as the default
  var uploadAlbumId = 0;
  if (aUpload.album) {
    // Onet uses an albumId for upload and an albumToken for photo searching
    var posColon = aUpload.album.lastIndexOf(":");
    uploadAlbumId = aUpload.album.substring(posColon + 1);
  }

  this._logger.debug(".upload() to album: " + uploadAlbumId);

  var inst = this;
  var svcUploadListener = {
    onResult: function OnetMediaService_upload_onResult(aResponseText) {
      // Note: Evaluating using nsJSON.decode() fails with resultant data
      var s = new CU.Sandbox("about:blank");
      inst._logger.debug(".upload onResult " + aResponseText);
      var result = CU.evalInSandbox("[" + aResponseText + "]", s);
      result = result[0];
      if (result && result.status && result.status.code == 0) {
        aUploadListener.onUploadComplete(aUpload);
        var newMediaItem = _getMediaItem(result.data.imgUrl);
        newMediaItem.title = aUpload.title;
        aUploadListener.onUploadFinalized(aUpload, newMediaItem);
      } else {
        var error = OnetUtils.api.getError(999, aResponseText);
        aUploadListener.onError(error, null);
      }
    },
    onError: function OnetMediaService_upload_onError(aErrorCode) {
      inst._logger.debug(".upload() onError: " + aErrorCode);
      try {
        if (aErrorCode) {
          aUploadListener.onError(OnetUtils.api.getHttpError(aErrorCode));
        } else {
          aUploadListener.onError(OnetUtils.api.getError(null, null));
        }
      } catch (ex) {
        // Unable to return error code.
      }
    },
    onProgress: function OnetMediaService_upload_onProgress(aCurrentProgress) {
      aUploadListener.onProgress(aCurrentProgress);
    }
  };

  // Prepare the upload
  var paramsForPrepare = { wrappedJSObject: { albumId: uploadAlbumId,
                                              desc: title } };

  var uploaderParams = OnetUtils.api.prepare("uploadPhoto", paramsForPrepare)
                                    .wrappedJSObject;

  var uploader = new FlockPhotoUploader();
  uploader.setEndpoint(ONET_UPLOAD_ENDPOINT);
  uploader.photoParam = "image";
  uploader.upload(svcUploadListener, aFilename, uploaderParams, aUpload);
};

/**
 * void createAlbum(in flockIListener aFlockListener, in AString aAccountId ..);
 * @see flockIMediaUploadWebService#createAlbum
 */
OnetMediaService.prototype.createAlbum =
function OnetMediaService_createAlbum(aFlockListener, aAccountId, aAlbumName) {
  this._logger.debug(".createAlbum(aFlockListener, '" + aAlbumName + "')");

  // Trim whitespace from front and end of string
  var trimmedTitle = aAlbumName.replace(/^\s+|\s+$/g, "");
  if (trimmedTitle) {
    OnetUtils.api.createAlbum(aFlockListener, aAccountId, trimmedTitle);
  } else {
    var e = OnetUtils.api.getError(CI.flockIError.PHOTOSERVICE_EMPTY_ALBUMNAME);
    aFlockListener.onError(e, arguments.callee.name);
  }
};

/**************************************************************************
 * OnetMediaService: flockIMediaWebService Implementation
 **************************************************************************/

// DEFAULT: nsISimpleEnumerator enumerateChannels ()

/**
 * void decorateForMedia(in nsIDOMHTMLDocument aDocument,
 *                       in flockIWebServiceAccount aAccount);
 * @see flockIMediaWebService#decorateForMedia
 */
OnetMediaService.prototype.decorateForMedia =
function OnetMediaService_decorateForMedia(aDocument, aAccount) {
  this._logger.debug(".decorateForMedia('" + aDocument.URL + "')");

  var results = FlockSvcUtils.newResults();
  if (this._wd.detect(this.shortName, "person", aDocument, results)) {
    this._logger.debug(".decorateForMedia() Media page detected");

    // Onet returns an encrypted uid of the username
    var uid = null;
    try {
      uid = results.getPropertyAsAString("uid");
      this._logger.debug(".decorateForMedia() Found a uid: " + uid);
    } catch (ex) {
      // Unable to retrieve a uid
    }

    var username = null;
    try {
      username = results.getPropertyAsAString("username");
      this._logger.debug(".decorateForMedia() Found a username: " + username);
    } catch (ex) {
      // Unable to retrieve a username
    }

    // Initialize our media array that will contain any found media streams
    var mediaArr = [];

    if (uid && username) {
      // Setup our uid query as we have found a stream, the page does not
      // contain the full email address, just the username which can't be
      // searched via the API.
      var media = {
        name: username,
        query: "searchunique:" + uid + "|username:" + username,
        label: username,
        favicon: this.icon,
        service: this.shortName
      };
      mediaArr.push(media);
      var inst = this;
      var userAlbumsFlockListener = {
        onSuccess: function getUserAlbums_onSuccess(aXml, aTopic) {
          inst._logger.debug(".getUserAlbums_onSuccess()");
          var resultsEnum = inst._handleFeedResult(aXml);
          while (resultsEnum.hasMoreElements()) {
            var album = resultsEnum.getNext();
            if (!album) {
              continue;
            }
            album.QueryInterface(CI.flockIMediaItem);

            // Onet also uses an encoded albumToken, URL's are formed this way:
            // http://foto.onet.pl/{albumToken},u.html
            var albumUrl = album.webPageUrl;
            var pattern = "http://foto.onet.pl/";
            var index = albumUrl.indexOf(pattern);

            if (index != -1) {
              index += pattern.length;
              // Extract the album token from the URL
              var posLastComma = albumUrl.lastIndexOf(",");
              var albumToken = albumUrl.substring(index, posLastComma);
              var albumTitle = album.title;
              inst._logger.debug(".getUserAlbums_onSuccess() Found an album: "
                                 + albumTitle);

              // pass the albumToken (contains the encrypted uid)
              var media = {
                name: albumTitle,
                query: "album:" + albumToken
                     + "|username:" + username
                     + "|albumlabel:" + albumTitle,
                label: albumTitle,
                favicon: inst.icon,
                service: inst.shortName
              };
              mediaArr.push(media);
            }
          }
          // If we have added items to be discovered, notify the user
          if (mediaArr.length > 0) {
            inst._logger.debug(".decorateForMedia() Found = "
                               + mediaArr.length + " albums");
            if (!aDocument._flock_decorations) {
              aDocument._flock_decorations = {};
            }
            // Merge in the new results if there are decorations present
            if (aDocument._flock_decorations.mediaArr) {
              aDocument._flock_decorations.mediaArr =
                aDocument._flock_decorations.mediaArr.concat(mediaArr);
            } else {
              aDocument._flock_decorations.mediaArr = mediaArr;
            }
            // Notify that we have updated the media decorations

            // XXX: Race condition with secondary IFRAME document load
            // -> http://foto.onet.pl/kropkaif.html, this problem lies deeper
            // than this service (TODO): Look at how
            // IFlockWebDetective.asyncDetect() has been used to deal with
            // waiting for IFRAME documents to load

            inst.obs.notifyObservers(aDocument, "media", "media:update");
          }
        },
        onError: function getUserAlbums_onError(aFlockError, aTopic) {
          inst._logger.error(".getUserAlbums_onError()");
        }
      };

      // Construct an album query based on the current uid
      var feedUrl = ONET_FEED_XML
                  + "?format=atom"
                  + "&limit=" + ONET_MEDIA_DECORATION_ALBUM_LIMIT
                  + "&query=newuseralbums"
                  + "&uid=" + uid;
      OnetUtils.api.getFeed(userAlbumsFlockListener, feedUrl);
    }
  }
};

/**
 * void findByUsername(in flockIListener aFlockListener, in AString aUsername);
 * @see flockIMediaWebService#findByUsername
 */
OnetMediaService.prototype.findByUsername =
function OnetMediaService_findByUsername(aFlockListener, aUsername) {
  aFlockListener.onError(null, arguments.callee.name);
};

// DEFAULT: flockIMediaChannel getChannel (in AString AChannelId)
// DEFAULT: flockIMediaItemFormatter getMediaItemFormatter ()

/**
 * void migrateAccount(in AString aId, in AString aUsername);
 * @see flockIMediaWebService#migrateAccount
 */
OnetMediaService.prototype.migrateAccount =
function OnetMediaService_migrateAccount(aId, aUsername) {
  this._logger.debug(".migrateAccount('" + aId + "', '" + aUsername + "')");
};

// readonly attribute boolean supportsUsers
OnetMediaService.prototype.supportsUsers = true;

/**
 * boolean supportsSearch (in AString aQuery)
 * @see flockIMediaWebService#supportsSearch
 */
OnetMediaService.prototype.supportsSearch =
function OnetMediaService_supportsSearch(aQueryString) {
  return false;
};

/* void search(in flockIListener aFlockListener,
 *             in AString aQuery,
 *             in long aCount,
 *             in long aPage,
 *             in AString aRequestId);
 * @see flockIMediaWebService#search
 */
OnetMediaService.prototype.search =
function OnetMediaService_search(aFlockListener,
                                 aQueryString,
                                 aCount,
                                 aPage,
                                 aRequestId)
{
  this._logger.debug(".search(..., '" + aQueryString + "', " + aCount + ", "
                                      + aPage + ")");

  var aQuery = new QueryHelper(aQueryString);
  var inst = this;

  var myFeedListener = {
    onSuccess: function myFeedListener_onSuccess(aXml, aTopic) {
      var resultsEnum = inst._handleFeedResult(aXml);
      aFlockListener.onSuccess(resultsEnum, aRequestId);
    },
    onError: function myFeedListener_onError(aFlockError, aTopic) {
      aFlockListener.onError(aFlockError, aRequestId);
    }
  };

  // Calculate the offset based on page number and item count
  var fromOffset = 0;
  if (aPage > 1) {
    fromOffset = (aPage - 1) * aCount;
  }

  // Construct a generic base feed URL
  var onetFeedBase = ONET_FEED_XML
                   + "?format=atom"
                   + "&from=" + fromOffset
                   + "&limit=" + aCount;

  if (aQuery.favorites && !aQuery.user) {
    aQuery.user = aQuery.favorites;
  }

  var feedUrl = null;

  // General Search
  if (aQuery.search) {
    feedUrl = onetFeedBase
            + "&query=search"
            + "&phrase=" + encodeURIComponent(aQuery.search);
  } else if (aQuery.album) {
    // Grab a user's album specific photos
    feedUrl = onetFeedBase
            + "&query=album" + "&album=" + aQuery.album;
  } else if (aQuery.user) {
    feedUrl = onetFeedBase + "&query=newuserphotos";
    if (aQuery.user.indexOf("@") != -1) {
      // Grab a user's photos (from Mediabar > My Accounts)
      feedUrl += "&email=" + encodeURIComponent(aQuery.user);
    } else {
      // When the user expands the control box and clicks the username, or when
      // the user right clicks the photo and selects "More from this person"
      feedUrl += "&uid=" + encodeURIComponent(aQuery.user);
    }
  } else if (aQuery.searchunique) {
    // Grab a user's photos (from media detection). UserId's found on front end
    // pages are encrypted and require a unique lookup by the encrypted value
    feedUrl = onetFeedBase
            + "&query=newuserphotos"
            + "&uid=" + encodeURIComponent(aQuery.searchunique);
  } else if (aQuery.special != "favorites") {
    // Default channels
    if (aPage > 1) {
      return;
    }
    var channel = this._channels[aQuery.stringVal];
    if (channel && channel.feed) {
      feedUrl = channel.feed;
    }
  }

  if (feedUrl) {
    OnetUtils.api.getFeed(myFeedListener, feedUrl);
  }
};

/**************************************************************************
 * OnetMediaService: private members
 **************************************************************************/

function _getMediaItem(aThumbnail) {
  var newMediaItem = CC["@flock.com/photo;1"]
                     .createInstance(CI.flockIMediaItem);

  newMediaItem.init(CLASS_SHORT_NAME, null);
  newMediaItem.has_miniView = "false";
  newMediaItem.thumbnail = aThumbnail;
  newMediaItem.midSizePhoto = aThumbnail;
  newMediaItem.largeSizePhoto = aThumbnail;
  newMediaItem.is_public = "true";
  newMediaItem.is_video = false;
  return newMediaItem;
};

OnetMediaService.prototype._handleFeedResult =
function OnetMediaService__handleFeedResult(aXml) {
  var photos = [];
  var nextIdx = 0;

  var entries = aXml.getElementsByTagName("entry");
  for (var i = 0; i < entries.length; i++) {
    var item = entries[i];

    var thumbnail = item.getElementsByTagName("link")[1].getAttribute("href");
    var newMediaItem = _getMediaItem(thumbnail);

    newMediaItem.title = item.childNodes[1].textContent;
    newMediaItem.webPageUrl = item.getElementsByTagName("link")[0]
                                  .getAttribute("href");
    var published = item.getElementsByTagName("published")[0].textContent;
    newMediaItem.uploadDate = ISO8601DateUtils.parse(published).getTime();
    // Undo the scaling applied in _handlePhotosResult() and ensure that the ID
    // is truly unique so tooltips work properly.
    newMediaItem.id = "" + (newMediaItem.uploadDate / 1000) + nextIdx++;

    var author = item.getElementsByTagName("author")[0];
    newMediaItem.username = author.childNodes[1].textContent;

    // Currently their API only supports calls with an encrypted username, the
    // uri contains this encrypted value, extract it here.
    var uri = author.childNodes[3].textContent;
    var uriRegExp = new RegExp(this._wd.getString(CLASS_SHORT_NAME,
                                                  "extractEncryptedUidFromUri",
                                                  ""));
    var encryptedUid = uriRegExp.exec(uri);
    if (encryptedUid[1]) {
      // Match found
      newMediaItem.userid = encryptedUid[1];
    }

    photos.push(newMediaItem);
  }

  // Return a simple enumerator
  var resultsEnum = {
    hasMoreElements: function resultsEnum_hasMoreElements() {
      return (photos.length > 0);
    },
    getNext: function resultsEnum_getNext() {
      return photos.shift();
    },
    QueryInterface: function resultsEnum_QueryInterface(aIID) {
      if (aIID.equals(CI.nsISimpleEnumerator)) {
        return this;
      }
      throw CR.NS_ERROR_NO_INTERFACE;
    }
  };

  return resultsEnum;
};

/*************************************************************************
 * Component: OnetMediaAccount
 *************************************************************************/
function OnetMediaAccount() {
  FlockSvcUtils.getLogger(this);
  this._logger.init("OnetMediaAccount");

  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getCoop(this);

  var wsa = FlockSvcUtils.flockIWebServiceAccount;
  wsa.addDefaultMethod(this, "getService");
  wsa.addDefaultMethod(this, "logout");
  wsa.addDefaultMethod(this, "getParam");
  wsa.addDefaultMethod(this, "setParam");
  wsa.addDefaultMethod(this, "getCustomParam");
  wsa.addDefaultMethod(this, "getAllCustomParams");
  wsa.addDefaultMethod(this, "setCustomParam");
  wsa.addDefaultMethod(this, "isAuthenticated");
}

/**************************************************************************
 * Onet Account: XPCOM Component Creation
 **************************************************************************/

OnetMediaAccount.prototype = new FlockXPCOMUtils.genericComponent(
  ONET_MEDIA_CLASS_NAME + " Account",
  "",
  "",
  OnetMediaAccount,
  0,
  [
    CI.flockIWebServiceAccount,
    CI.flockIMediaAccount,
    CI.flockIMediaUploadAccount
  ]
);

/**************************************************************************
 * Onet Account: flockIWebServiceAccount Implementation
 **************************************************************************/

// DEFAULT: nsIPropertyBag getAllCustomParams ()
// DEFAULT: nsIVariant getCustomParam (in AString aParamName)
// DEFAULT: nsIVariant getParam (in AString aParamName)
// DEFAULT: flockILoginWebService getService()
// DEFAULT: boolean isAuthenticated ()

/**
 * void keep()
 * @see flockIWebServiceAccount#keep
 */
OnetMediaAccount.prototype.keep =
function OnetMediaAccount_keep() {
  this._logger.debug("OnetMediaAccount_keep()");
  this.coopObj.isTransient = false;
  this._acUtils.makeTempPasswordPermanent(this.getService().urn
                                          + ":"
                                          + this.coopObj.accountId);
};

/**
 * void login(in flockIListener aFlockListener)
 * @see flockIWebServiceAccount#login
 */
OnetMediaAccount.prototype.login =
function OnetMediaAccount_login(aFlockListener) {
  OnetUtils.loginAccount(this, CLASS_SHORT_NAME, aFlockListener);
};

// DEFAULT: void logout (in flockIListener aFlockListener)
// DEFAULT: void setCustomParam (in AString aParamName, in nsIVariant aValue)
// DEFAULT: void setParam (in AString aParamName, in nsIVariant aValue)

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

// Create array of components.
var componentsArray = [OnetMediaService];

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