// 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;
const CU = Components.utils;

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

CU.import("resource:///modules/FlockSvcUtils.jsm");
CU.import("resource:///modules/FlockStringBundleHelpers.jsm");
CU.import("resource:///modules/FlockXMLUtils.jsm");
CU.import("resource://gre/modules/JSON.jsm");
CU.import("resource:///modules/webmailBackend.jsm");

const MODULE_NAME = "YahooMail";
const CLASS_NAME = "Flock Yahoo! Mail Service";
const CLASS_SHORT_NAME = "yahoomail";
const CLASS_TITLE = "Yahoo! Mail";
const CLASS_ID = Components.ID("{0f1532fe-aabc-4a78-8193-ce366010b697}");
const CONTRACT_ID = "@flock.com/yahoomail-service;1";

const SERVICE_ENABLED_PREF = "flock.service.yahoomail.enabled";

const YAHOOMAIL_URL = "http://mail.yahoo.com";
const YAHOOMAIL_MESSAGE_URL = "http://mrd.mail.yahoo.com";
const YAHOOMAIL_FAVICON = "chrome://flock/content/services/yahoomail/favicon.png";
const YAHOOMAIL_STRING_BUNDLE = "chrome://flock/locale/services/yahoomail.properties";

const FLOCK_ERROR_CONTRACTID = "@flock.com/error;1";
const PREFS = CC["@mozilla.org/preferences-service;1"]
              .getService(CI.nsIPrefBranch);

const STR_FOLDERURL = "folderURL";
const STR_ISCLASSICVIEW = "isClassicView";
const STR_ISUPGRADEABLE = "isAccountUpgradeable";
const STR_INTL = "intl";
const STR_UNREAD = "unreadMessages";
const STR_ISUTF16 = "isUTF16Encoded";
const STR_WAITING_TO_SEND = "waitingToSend";

const DOMAIN = "yahoo.com";
const MAX_UNREAD_COUNT = 50;  // arbitrary max unread messages to retrieve
const REFRESH_INTERVAL = 600;  // 10 min in seconds


/**************************************************************************
 * Component: Flock YahooMailService
 **************************************************************************/

// Constructor
function YahooMailService() {
  this._init();

  var ws = FlockSvcUtils.flockIWebService;
  ws.addDefaultMethod(this, "getAccount");
  ws.addDefaultMethod(this, "isHidden");
  ws.addDefaultMethod(this, "getAuthenticatedAccount");
  ws.addDefaultMethod(this, "getAccounts");
  ws.addDefaultMethod(this, "logout");
  ws.addDefaultMethod(this, "getStringBundle");

  var mws = FlockSvcUtils.flockIManageableWebService;
  mws.addDefaultMethod(this, "docRepresentsSuccessfulLogin");
  mws.addDefaultMethod(this, "getAccountIDFromDocument");
  mws.addDefaultMethod(this, "getCredentialsFromForm");
  mws.addDefaultMethod(this, "ownsDocument");
  mws.addDefaultMethod(this, "ownsLoginForm");
  mws.addDefaultMethod(this, "getSessionValue");

  FlockSvcUtils.flockIMailWebService.addDefaultMethod(this, "getLoginURL");
}


/**************************************************************************
 * Flock YahooMailService: XPCOM Component Creation
 **************************************************************************/

YahooMailService.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME,
  CLASS_ID,
  CONTRACT_ID,
  YahooMailService,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.nsIObserver,
    CI.nsITimerCallback,
    CI.flockIPollingService,
    CI.flockIWebService,
    CI.flockIManageableWebService,
    CI.flockIMailWebService
  ]
);

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


/**************************************************************************
 * Flock YahooMailService: Private Data and Methods
 **************************************************************************/

// Service initialization
YahooMailService.prototype._init =
function ymSvc__init() {
  FlockSvcUtils.getLogger(this).info(".init()");

  // Determine whether this service has been disabled, and unregister if so
  if (PREFS.getPrefType(SERVICE_ENABLED_PREF) &&
      !PREFS.getBoolPref(SERVICE_ENABLED_PREF))
  {
    this._logger.info("Pref " + SERVICE_ENABLED_PREF
                              + " set to FALSE... not initializing.");
    this.deleteCategories();
    return;
  }

  this.api = new YahooMailAPI();
  profiler = CC["@flock.com/profiler;1"].getService(CI.flockIProfiler);
  var evtID = profiler.profileEventStart("yahoomail-init");

  var obs = CC["@mozilla.org/observer-service;1"]
            .getService(CI.nsIObserverService);
  obs.addObserver(this, "xpcom-shutdown", false);

  this._accountClassCtor = YahooMailAccount;
  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getCoop(this);
  if (this._coop.Service.exists(this.urn)) {
    this._c_svc = this._coop.get(this.urn);
  } else {
    this._c_svc = new this._coop.Service(this.urn, {
      name: CLASS_SHORT_NAME,
      desc: CLASS_TITLE
    });
  }
  this._c_svc.serviceId = CONTRACT_ID;
  this._c_svc.logoutOption = true;
  // Note that using FlockSvcUtils.getWD here adds the "_WebDetective"
  // property to the service.
  this._c_svc.domains =
      FlockSvcUtils.getWD(this).getString(CLASS_SHORT_NAME, "domains", DOMAIN);
  this._c_svc.loginURL =
      FlockSvcUtils.getWD(this).getString(CLASS_SHORT_NAME,
                                          "userlogin",
                                          YAHOOMAIL_URL);

  // Load auth manager
  this._authMgr = CC["@flock.com/authmgr;1"]
                  .getService(CI.flockIAuthenticationManager);
  this._authMgr.joinNetwork(CONTRACT_ID, "yahoo");

  // Update auth states
  try {
    if (this._WebDetective
            .detectCookies(CLASS_SHORT_NAME, "loggedoutCookies", null))
    {
      this._acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID);
    }
  } catch (ex) {
    this._logger.error("ERROR updating auth states for "
                       + MODULE_NAME
                       + ": "
                       + ex);
  }

  // Initialize member that specifies path for localized string bundle
  this._stringBundlePath = YAHOOMAIL_STRING_BUNDLE;

  profiler.profileEventEnd(evtID, "");
}


/**************************************************************************
 * Flock YahooMailService: flockIWebService Implementation
 **************************************************************************/

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

// readonly attribute AString title;
YahooMailService.prototype.title = CLASS_TITLE;

// readonly attribute AString icon;
YahooMailService.prototype.icon = YAHOOMAIL_FAVICON;

// readonly attribute AString url;
YahooMailService.prototype.url = YAHOOMAIL_URL;

// readonly attribute AString contractId;
// ALMOST duplicated by nsIClassInfo::contractID
// with different case: contractId != contractID
// FIXME: File a bug for this as IDL is case-insensitive.
YahooMailService.prototype.contractId = CONTRACT_ID;

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

// readonly attribute boolean needPassword;
YahooMailService.prototype.needPassword = true;

// flockIWebServiceAccount addAccount(in AString aAccountID,
//                                    in boolean aIsTransient,
//                                    in flockIListener aListener);
YahooMailService.prototype.addAccount =
function ymSvc_addAccount(aAccountID, aIsTransient, aListener) {
  this._logger.debug("addAccount('" + aAccountID + "', "
                                    + aIsTransient + ", aListener)");

  if (!aAccountID) {
    if (aListener) {
      var error = CC[FLOCK_ERROR_CONTRACTID].createInstance(CI.flockIError);
      // XXX See bug 10749 - flockIError cleanup
      error.errorCode = 9999;
      error.errorString = "No Account ID provided";
      aListener.onError(this, "addAccount", error);
    }
    return null;
  }

  var account = null;
  var pw = this._acUtils.getPassword(this.urn + ":" + aAccountID);
  var user = pw ? pw.user : aAccountId;

  // Create a new account and add it to coop if it doesn't already exist
  var accountURN = "urn:flock:" + this.shortName + ":account:" + aAccountID;
  if (!this._coop.Account.exists(accountURN)) {
    var coopAccount = new this._coop.Account(accountURN, {
      accountId: aAccountID,
      favicon: this.icon,
      isPollable: true,
      isTransient: aIsTransient,
      name: user,
      refreshInterval: REFRESH_INTERVAL,
      service: this._c_svc,
      serviceId: this.contractId
    });
    this._coop.accounts_root.children.addOnce(coopAccount);

    // Add custom parameters to be used
    var params = CC["@mozilla.org/hash-property-bag;1"]
                 .createInstance(CI.nsIWritablePropertyBag2);
    params.setPropertyAsBool("isPrimary", false);
    params.setPropertyAsBool("hasNewMessages", false);
    params.setPropertyAsInt32("lastPrimaryDate", 0);
    params.setPropertyAsAString(STR_FOLDERURL, "");
    params.setPropertyAsBool(STR_ISCLASSICVIEW, false);
    params.setPropertyAsBool(STR_ISUTF16, false);
    params.setPropertyAsBool(STR_ISUPGRADEABLE, true);
    params.setPropertyAsAString(STR_INTL, "");
    params.setPropertyAsAString(STR_UNREAD,
                                webmailBackend.createEmptyMessageAsJSON());
    params.setPropertyAsAString(STR_WAITING_TO_SEND, "");
    this._acUtils.addCustomParamsToAccount(params, accountURN);
  }

  account = this.getAccount(accountURN);

  if (aListener) {
    aListener.onSuccess(account, "addAccount");
  }

  return account;
}

// void removeAccount(in AString aUrn);
YahooMailService.prototype.removeAccount =
function ymSvc_removeAccount(aUrn) {
  this._logger.debug("removeAccount('" + aUrn + "')");
  this._acUtils.removeAccount(aUrn);
  webmailBackend.ensurePrimaryAccountExists();
}

// DEFAULT: flockIWebServiceAccount getAccount(in AString aUrn);
// DEFAULT: nsISimpleEnumerator getAccounts();
// DEFAULT: void logout();

// readonly attribute long status;
YahooMailService.prototype.status = CI.flockIWebService.STATUS_UNKNOWN;


/**************************************************************************
 * Flock YahooMailService: flockIManageableWebService Implementation
 **************************************************************************/

// readonly attribute AString domains;
YahooMailService.prototype.__defineGetter__("domains",
function ymSvc_getDomains() {
  this._logger.debug("Getting property: domains");
  return this._c_svc.domains;
});

// readonly attribute AString loginURL;
YahooMailService.prototype.__defineGetter__("loginURL",
function ymSvc_getLoginURL() {
  this._logger.debug("Getting property: loginURL");
  return this._c_svc.loginURL;
});

// DEFAULT: boolean docRepresentsSuccessfulLogin(in nsIDOMHTMLDocument aDocument);
// DEFAULT: AString getAccountIDFromDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: nsIPassword getCredentialsFromForm(in nsIDOMHTMLFormElement aForm);
// DEFAULT: boolean ownsDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: boolean ownsLoginForm(in nsIDOMHTMLFormElement aForm);

// void updateAccountStatusFromDocument(in nsIDOMHTMLDocument aDocument,
//                                      in AString aAcctURN);
YahooMailService.prototype.updateAccountStatusFromDocument =
function ymSvc_updateAccountStatusFromDocument(aDocument, aAcctURN) {
  this._logger.debug("updateAccountStatusFromDocument('" + aAcctURN + "')");
  if (aAcctURN) {
    // We are currently logged in to aAcctURN
    if (!this._acUtils.ensureOnlyAuthenticatedAccount(aAcctURN)) {
      // Just logged in
      webmailBackend.onLogin(this.getAccount(aAcctURN));
      this._authMgr.reportAccountAuthState(aAcctURN, true);
    }

    // Retrieve the language
    var results = FlockSvcUtils.newResults();
    if (this._WebDetective
            .detect(this.shortName, "language", aDocument, results))
    {
      var intl = results.getPropertyAsAString(STR_INTL);
      this._logger.debug("language = " + intl);
      this._acUtils.setAccountCustomParamValue(aAcctURN, STR_INTL, intl);
    }

    // Store whether we are in Classic Yahoo! Mail view or modern view
    var isClassicView = !this._WebDetective.detect(this.shortName,
                                                   "modernView",
                                                   aDocument,
                                                   null);
    this._logger.debug(isClassicView ? "using classic view"
                                     : "using modern view");
    this._acUtils.setAccountCustomParamValue(aAcctURN,
                                             STR_ISCLASSICVIEW,
                                             isClassicView);
    this._acUtils
        .setAccountCustomParamValue(aAcctURN, STR_ISUTF16, isClassicView);

    // Store the current session's URL and path as it could potentially be
    // variable due to one of the following:
    // 1) Classic Yahoo! Mail view or modern view
    // 2) Different server farms
    // 3) Different country 
    var ios = CC["@mozilla.org/network/io-service;1"]
              .getService(CI.nsIIOService); 
    var uri = ios.newURI(aDocument.URL, null, null);
    var url = uri.prePath;
    if (isClassicView) {
      url += FlockSvcUtils.getWD(this).getString(this.shortName,
                                                 "classicURLPath",
                                                 "/ym/ShowFolder");
    } else {
      var idx = uri.path.indexOf("?");
      if (idx > -1) {
        url += uri.path.substr(0, idx);
      } else {
        url += uri.path;
      }
    }
    this._logger.debug("url + path = " + url);
    this._acUtils.setAccountCustomParamValue(aAcctURN, STR_FOLDERURL, url);

    // Check if this account is upgradeable to pro status or not
    var upgradeable = true;
    if (isClassicView) {
      upgradeable = this._WebDetective.detect(this.shortName,
                                              "upgradeAccountClassic",
                                              aDocument,
                                              null);
    } else if (this._WebDetective.detect(this.shortName,
                                         "upgradeAccount",
                                         aDocument,
                                         results))
    {
      upgradeable = (results.getPropertyAsAString("hasMailPlus") == "false");
    }
    this._logger.debug("upgradeable = " + upgradeable);
    this._acUtils.setAccountCustomParamValue(aAcctURN,
                                             STR_ISUPGRADEABLE,
                                             upgradeable);

    // Do we have a pending compose message?
    webmailBackend.checkForPendingComposeMessage(this.getAccount(aAcctURN));
  } else {
    // No login was detected.  So let's see if we can detect a logged out
    // condition.
    var mailLogout =
      this._WebDetective.detect(this.shortName, "loggedout", aDocument, null);
    var yahooNetworkLogout =
      this._WebDetective
          .detect(this.shortName, "completeLogout", aDocument, null);
    if (mailLogout || yahooNetworkLogout) {
      this._logger.debug("We're logged out!");

      // Yes, we're logged out.  We do slightly different things depending on
      // whether we've just logged out of Mail, or the whole Yahoo network.
      var loggedoutURN = null;
      if (mailLogout) {
        loggedoutURN =
          this._acUtils.getFirstAuthenticatedAccountForService(CONTRACT_ID);
      }
      this._acUtils.markAllAccountsAsLoggedOut(CONTRACT_ID);
      if (mailLogout && loggedoutURN) {
        this._authMgr.reportAccountAuthState(loggedoutURN, false);
      }
      if (yahooNetworkLogout) {
        this._authMgr.reportCompleteLogoutFromNetwork(CONTRACT_ID);
      }

      // Ensure the webmail icon does not indicate new unread messages while
      // logged out of the primary account
      webmailBackend.updateIcon();
    }
  }
}


/**************************************************************************
 * Flock YahooMailService: flockIMailWebService Implementation
 **************************************************************************/

// DEFAULT: AString getLoginURL();


/**************************************************************************
 * Flock YahooMailService: flockIPollingService Implementation
 **************************************************************************/

// void refresh(in AString aUrn, in flockIPollingListener aListener);
YahooMailService.prototype.refresh =
function ymSvc_refresh(aURN, aPollingListener) {
  this._logger.debug("refresh(" + aURN + ")");

  var account = this.getAccount(aURN);
  if (!account.isAuthenticated()) {
    return;
  }
  var svc = this;
  var listener = {
    onResult: function ymSvc_refresh_onResult(aValue) {
      webmailBackend.handleRefreshedMessages(svc, aURN, aValue);
      aPollingListener.onResult();
    },
    
    onError: function ymSvc_refresh_onError() {
      svc._logger.debug("unable to refresh account " + aURN);
      svc._acUtils
         .setAccountCustomParamValue(aURN,
                                     STR_UNREAD,
                                     webmailBackend.createEmptyMessageAsJSON());
      aPollingListener.onError(null);
    }
  }
  var country = this._acUtils.getAccountCustomParam(aURN, STR_INTL);
  this.api.refreshUnreadMessages(listener,
                                 country,
                                 this.getAccount(aURN)
                                     .getCustomParam(STR_ISCLASSICVIEW));
}


/**************************************************************************
 * Flock YahooMailService: nsIObserver Implementation
 **************************************************************************/

YahooMailService.prototype.observe =
function ymSvc_observe(aSubject, aTopic, aState) {
  switch (aTopic) {
    case "xpcom-shutdown":
      var obs = CC["@mozilla.org/observer-service;1"]
                .getService(CI.nsIObserverService);
      obs.removeObserver(this, "xpcom-shutdown");
      break;
  }
}


/**************************************************************************
 * Component: Flock YahooMailAccount
 **************************************************************************/

// Constructor
function YahooMailAccount() {
  FlockSvcUtils.getLogger(this).init(CLASS_SHORT_NAME + ":Account");
  this._logger.info(".init()");
  FlockSvcUtils.getACUtils(this);
  FlockSvcUtils.getCoop(this);
  // TODO XXX: Should be able to use FlockSvcUtils.getWD() here, but it can
  //           only be called by the service.  Need to fix.
  this._WebDetective = CC["@flock.com/web-detective;1"]
                       .getService(CI.flockIWebDetective);

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

  // TODO XXX: Setting a private _shortName when adding the
  //           flockIMailWebServiceAccount default methods should only be a
  //           temporary measure until the account has reference to its service
  this._shortName = CLASS_SHORT_NAME;
  var mwsa = FlockSvcUtils.flockIMailWebServiceAccount;
  mwsa.addDefaultMethod(this, "getUnreadMessages");
  mwsa.addDefaultMethod(this, "isPrimary");
  mwsa.addDefaultMethod(this, "refreshUnreadMessageList");
  mwsa.addDefaultMethod(this, "setAsPrimary");
}


/**************************************************************************
 * Flock YahooMailAccount: XPCOM Component Creation
 **************************************************************************/

// Use genericComponent() for the goodness it provides (QI, nsIClassInfo, etc),
// but do NOT add YahooMailAccount to the list of constructors passed
// to FlockXPCOMUtils.genericModule().
YahooMailAccount.prototype = new FlockXPCOMUtils.genericComponent(
  CLASS_NAME + " Account",
  "",
  "",
  YahooMailAccount,
  0,
  [
    CI.flockIWebServiceAccount,
    CI.flockIMailWebServiceAccount
  ]
  );


/**************************************************************************
 * Flock YahooMailAccount: flockIWebServiceAccount Implementation
 **************************************************************************/

// readonly attribute AString urn;
YahooMailAccount.prototype.urn = "";

// void activate(in flockIListener aListener);
YahooMailAccount.prototype.activate =
function ymAcct_activate(aListener) {
  this._logger.debug("activate()");
  this._coop.get(this.urn).isAuthenticated = true;
  webmailBackend.onLogin(this);

  if (aListener) {
    aListener.onSuccess(this, "activate");
  }
}

// DEFAULT: void deactivate(in flockIListener aListener);
// DEFAULT: void login(in flockIListener aListener);

// void logout(in flockIListener aListener);
YahooMailAccount.prototype.logout =
function ymAcct_logout(aListener) {
  this._logger.debug("logout()");
  var c_acct = this._coop.get(this.urn);
  if (c_acct.isAuthenticated) {
    c_acct.isAuthenticated = false;
    webmailBackend.updateIcon();
    CC[c_acct.serviceId].getService(CI.flockIWebService).logout();
  }
  if (aListener) {
    aListener.onSuccess(this, "logout");
  }
}

// void keep();
YahooMailAccount.prototype.keep =
function ymAcct_keep() {
  this._logger.debug("keep()");
  this._coop.get(this.urn).isTransient = false;
  var urn = this.urn.replace("account:", "service:").replace("flock:", "");
  this._acUtils.makeTempPasswordPermanent(urn);

  // Automatically set the webmail account as the default mail client on the
  // first time the user configures a webmail account
  if (PREFS.getPrefType("flock.webmail.promptAsDefaultMailer") &&
      PREFS.getBoolPref("flock.webmail.promptAsDefaultMailer"))
  {
    PREFS.setBoolPref("flock.webmail.promptAsDefaultMailer", false);
    PREFS.setBoolPref("flock.webmail.useAsDefaultMailer", true);
  }
}

// DEFAULT: void isAuthenticated();


/**************************************************************************
 * Flock YahooMailAccount: flockIMailWebServiceAccount Implementation
 **************************************************************************/

// AString getComposeURL(in AString aMailParams);
YahooMailAccount.prototype.getComposeURL =
function ymAcct_getComposeURL(aMailParams) {
  this._logger.debug("getComposeURL('" + aMailParams + "')");
  var params = webmailBackend.reformatMailParams(this, aMailParams, ";");
  if (params && !this.getCustomParam(STR_ISCLASSICVIEW)) {
    // The "new" Yahoo! Mail dies if there are newlines and carriage returns
    // in the params, so we need to replace them. We use 0x08, framed by
    // spaces, as it is very unlikely to encounter this sequences in a mailto
    // string (and if we did, we'd want to replace it regardless). The spaces
    // ensure that the 0x08 is not "bound" to its neighbors when Y!Mail parses
    // the content. These new values will get replaced when we call
    // onComposeURLLoaded() later on. 
    params = params.replace(/%0a/gi, "%20%08%20")
                   .replace(/%0d/gi, "%20%08%20");
  }

  // Add params and country code to URL
  var url = this._WebDetective.getString(CLASS_SHORT_NAME, "composeURL", "")
                              .replace("%params%", params)
                              .replace("%intl%", this.getCustomParam(STR_INTL));
  this._logger.debug("compose URL is: " + url);
  return url;
}

YahooMailAccount.prototype.onComposeURLLoaded =
function ymAcct_onComposeURLLoaded(aComposeURL, aTabBrowser) {
  this._logger.debug("onComposeURLLoaded()");
  // There are two issues to deal with after the compose page has loaded:
  // 1. In the "New" view, we may have replaced linebreaks and carriage returns
  //    with "%20%08%20", so we have to look for those in the message body and
  //    replace with "<br >".
  // 2. In the "Classic" view, newlines are accepted but not displayed when
  //    using the rich text editor, so we need to look for this case and
  //    manipulate the newlines as well.
  if (this._acUtils.getAccountCustomParam(this.urn, STR_ISCLASSICVIEW)) {
    this._onComposeURLLoadedClassicView(aComposeURL, aTabBrowser);
  } else {
    this._onComposeURLLoadedNewView(aComposeURL, aTabBrowser);
  }
}

// AString getEmailAddress();
YahooMailAccount.prototype.getEmailAddress =
function ymAcct_getEmailAddress() {
  this._logger.debug("getEmailAddress()");
  var email;
  var accountId = this._coop.get(this.urn).accountId;
  if (accountId.indexOf("@") != -1) {
    // Bug 11611. Yahoo has partnered with DSL affiliates (such as AT&T/SBC) to
    // provide webmail support for their customers. In this case, users log in
    // with their affiliated email address instead of a Yahoo account id. We
    // don't need to tack on "yahoo.com" to the account in this case.
    email = accountId;
  } else {
    // Determine the proper domain name to add on to the account name to create
    // the email address.
    var domain = "com";
    var country = this._acUtils.getAccountCustomParam(this.urn, STR_INTL);
    if (country) {
      domain = this._WebDetective.getString(CLASS_SHORT_NAME, country, "com");
    }
    email = accountId + "@yahoo." + domain;
  }
  this._logger.debug("email address is: " + email);
  return email;
}

// AString getInboxURL();
YahooMailAccount.prototype.getInboxURL =
function ymAcct_getInboxURL() {
  var url = this._acUtils.getAccountCustomParam(this.urn, STR_FOLDERURL);
  if (this._acUtils.getAccountCustomParam(this.urn, STR_ISCLASSICVIEW)) {
    url += "?" + this._WebDetective.getString(CLASS_SHORT_NAME,
                                              "classicInboxParam",
                                              "rb=Inbox");
  } else {
    url += this._WebDetective
               .getString(CLASS_SHORT_NAME, "inboxURLAppend", "");
  }
  this._logger.debug("getInboxURL(): " + url);
  return url;
}

// DEFAULT: AString getUnreadMessages(in boolean aCheckNow);

// AString getUpgradeAccount();
YahooMailAccount.prototype.getUpgradeAccount =
function ymAcct_getUpgradeAccount() {
  this._logger.debug("getUpgradeAccount()");
  var upTitle = "";
  var upUrl = "";
  if (this._acUtils.getAccountCustomParam(this.urn, STR_ISUPGRADEABLE)) {
    upTitle = this._WebDetective
                  .getString(CLASS_SHORT_NAME, "upgradeAccountTitle", "");
    // Load the brandingUtilities utility
    CC["@mozilla.org/moz/jssubscript-loader;1"]
      .getService(CI.mozIJSSubScriptLoader)
      .loadSubScript("chrome://fbranding/content/brandingUtilities.js");
    upUrl = brandingUtilities.buildWebsiteUrl("webmail",
                                              "webmail",
                                              "upgradeyahoopro",
                                              "none");
  }
  return JSON.toString({title: upTitle, url: upUrl});
}

// DEFAULT: boolean isPrimary();
// DEFAULT: void refreshUnreadMessageList();
// DEFAULT: void setAsPrimary();

YahooMailAccount.prototype._onComposeURLLoadedClassicView =
function ymAcct__onComposeURLLoadedClassicView(aComposeURL, aTabBrowser) {
  this._logger.debug("_onComposeURLLoadedClassicView()");
  // The Classic view's rich text editor does not display newlines so we need to
  // pound them into the message body after it has loaded, replacing the "%0A"
  // sequences with "<br />".
  var inst = this;
  var composerListener = {
    onSuccess: function cv_composeListener_onSuccess(aSubj, aTopic) {
      inst._logger.info("cv_composerListener: onSuccess()");
      // Get the rich text composer's iFrame. If we don't find it, then the
      // plain  text composer is loaded and we don't need to do anything else.
      var composerIFrameId = inst._WebDetective
                                 .getString(CLASS_SHORT_NAME,
                                            "classicViewComposerIFrame",
                                            "rteEditorComposition0");
      var iFrame = aTabBrowser.contentDocument.getElementById(composerIFrameId);
      if (iFrame && iFrame instanceof CI.nsIDOMHTMLIFrameElement) {
        var bodyListener = {
          onSuccess: function cv_bodyListener_onSuccess(aSubj, aTopic) {
            inst._logger.info("cv_bodyListener: onSuccess()");
            // Replace newlines with breaks
            var body = iFrame.contentDocument.body;
            body.innerHTML = body.innerHTML.replace(/\x0A/g, "<br />");
          },
          onError: function cv_bodyListener_onError(aSubj, aTopic, aError) {
            inst._logger.error("cv_bodyListener: onError()");
          }
        };
        // Wait for the body to get filled
        inst._WebDetective
            .asyncDetect(CLASS_SHORT_NAME, "classicViewComposerBody",
                         iFrame, null, bodyListener, 250, 80);
      }
    },
    onError: function cv_composerListener_onError(aSubj, aTopic, aError) {
      inst._logger.error("cv_composerListener: onError()");
    }
  };
  // Wait for the text editor to load
  this._WebDetective
      .asyncDetect(CLASS_SHORT_NAME, "classicViewComposer",
                   aTabBrowser, null, composerListener, 250, 80);
}

YahooMailAccount.prototype._onComposeURLLoadedNewView =
function ymAcct__onComposeURLLoadedNewView(aComposeURL, aTabBrowser) {
  this._logger.debug("_onComposeURLLoadedNewView()");
  // The New view cannot handle newlines in the compose URL so we need to pound
  // them into the message body after it has loaded, replacing the "%20%08%20"
  // sequences we inserted in getComposeURL() with "<br />".
  var inst = this;
  var composerListener = {
    onSuccess: function nv_composeListener_onSuccess(aSubj, aTopic) {
      inst._logger.info("nv_composerListener: onSuccess()");
      // Get the composer's iFrame
      var composerIFrameId = inst._WebDetective
                                 .getString(CLASS_SHORT_NAME,
                                            "newViewComposerIFrame",
                                            "compArea_test_1");
      var iFrame = aTabBrowser.contentDocument.getElementById(composerIFrameId);
      if (iFrame && iFrame instanceof CI.nsIDOMHTMLIFrameElement) {
        var bodyListener = {
          onSuccess: function nv_bodyListener_onSuccess(aSubj, aTopic) {
            inst._logger.info("nv_bodyListener: onSuccess()");
            // Restore newlines and returns as breaks
            var body = iFrame.contentDocument.body;
            body.innerHTML = body.innerHTML.replace(/ ?\x08 ?/g, "<br />");

            // Ensure the subject content is properly URL decoded while we
            // are here; See bug 11734
            var subjectId = inst._WebDetective.getString(CLASS_SHORT_NAME,
                                                         "composeSubjectId",
                                                         "");
            var subject = aTabBrowser.contentDocument.getElementById(subjectId);
            if (subject &&
                subject instanceof CI.nsIDOMHTMLInputElement &&
                subject.value.length > 0)
            {
              subject.value = flockXMLDecode(subject.value);
            }
          },
          onError: function nv_bodyListener_onError(aSubj, aTopic, aError) {
            inst._logger.error("nv_bodyListener: onError()");
          }
        };
        // Wait for the body to get filled
        inst._WebDetective
            .asyncDetect(CLASS_SHORT_NAME, "newViewComposerBody",
                         iFrame, null, bodyListener, 250, 80);
      }
    },
    onError: function nv_composerListener_onError(aSubj, aTopic, aError) {
      inst._logger.error("nv_composerListener: onError()");
    }
  };
  // Wait for new message tab to load
  this._WebDetective
      .asyncDetect(CLASS_SHORT_NAME, "newViewComposer",
                   aTabBrowser, null, composerListener, 250, 80);
}

/**************************************************************************
 * Component: Flock YahooMailAPI
 **************************************************************************/

// Constructor
function YahooMailAPI() {
  FlockSvcUtils.getLogger(this).info(".init() - API");
}

YahooMailAPI.prototype.appid = "flock-search";

YahooMailAPI.prototype.wssid = "";

YahooMailAPI.prototype.url = "";

YahooMailAPI.prototype.shortName = CLASS_SHORT_NAME;

YahooMailAPI.prototype.refreshUnreadMessages =
function ymAPI_refreshUnreadMessages(aListener, aCountryCode, aIsClassicView) {
  this._logger.debug("refreshUnreadMessages('" + aCountryCode + "', "
                                               + aIsClassicView + ")");
  var method = "ListMessages";
  // Retrieve a reasonable number of unread messages as retrieving all unread
  // messages may be excessive and unnecessary
  // groupBy=unRead messages with flag unRead=0 will be at top of message list
  // returned by Yahoo
  var param = "&fid=Inbox&groupBy=unRead&numInfo=" + MAX_UNREAD_COUNT;
  this._setYahooSpecificURL(aCountryCode, method, param);
  this._processUnreadMessages(aListener,
                              aCountryCode,
                              method,
                              param,
                              aIsClassicView);
}

YahooMailAPI.prototype._buildUnreadMessagesJSON =
function ymAPI__buildUnreadMessagesJSON(aXML, aIsClassicView) {
  this._logger.debug("_buildUnreadMessagesJSON()");
  var encodeMID = FlockSvcUtils.getWD(this)
                               .getString(CLASS_SHORT_NAME,
                                          "encodeMessageIDForBetaView",
                                          null);
  var baseMsgUrl = FlockSvcUtils.getWD(this)
                                .getString(CLASS_SHORT_NAME,
                                           "messageUrl",
                                           YAHOOMAIL_MESSAGE_URL + "/msg?");
  var folder = aXML.getElementsByTagName("folder")[0];
  var count = parseInt(folder.getAttribute("unread"));
  var messageInfos = aXML.getElementsByTagName("messageInfo");
  var messages = [];
  var newestMessageDate = 0;
  for (var i = 0; i < messageInfos.length; i++) {
    var msg = messageInfos[i];
    var unreadElement = msg.getElementsByTagName("flags")[0];
    if (unreadElement && unreadElement.hasAttribute("isRead")) {
      var unread = parseInt(unreadElement.getAttribute("isRead"));
      // We reached the end of our unread message this message has been read
      if (unread == 1) {
        break;
      }
    }
    var from = msg.getElementsByTagName("from")[0];
    var fromString = "";
    if (from && from.hasChildNodes()) {
      fromString = from.getElementsByTagName("name")[0].firstChild.nodeValue;
    }
    var mid = msg.getAttribute("mid");
    // Due to a server side issue in Yahoo! Mail's new Beta view, the encoded
    // message id in the URL does not get passed along properly when the page
    // is redirected.  We need to encode the message id twice.
    // Since Yahoo may fix this issue server side, we may need to bypass our
    // initial encoding altogether.
    if (!aIsClassicView && encodeMID) {
      mid = encodeURIComponent(mid);
    }
    var msgUrl = baseMsgUrl + "fid=Inbox&mid=" + encodeURIComponent(mid);
    this._logger.debug("  from = " + fromString
                     + "\n  subject = " + msg.getAttribute("subject")
                     + "\n  message URL = " + msgUrl);
    messages.push({
      from: fromString,
      subject: msg.getAttribute("subject"),
      url: msgUrl
    });

    // Grab the date of the newest message
    if (!newestMessageDate) {
      newestMessageDate = msg.getAttribute("receivedDate");
    }
  }

  var unreadMessages = {
    lastCheckedDate: Date.now(),
    newestMessageDate: newestMessageDate,
    unreadMessageCount: count,
    messages: messages
  };
  return JSON.toString(unreadMessages);
}

YahooMailAPI.prototype._setYahooSpecificURL =
function ymAPI__setYahooSpecificURL(aCountryCode, aMethod, aParams) {
  this._logger.debug("_setYahooSpecificURL('" + aCountryCode + "', '"
                                              + aMethod + "', '"
                                              + aParams + "')");
  var apiUrl = FlockSvcUtils.getWD(this).getString(CLASS_SHORT_NAME,
                                                   "mailAPIUrl",
                                                   YAHOOMAIL_URL);
  apiUrl = "http://" + aCountryCode + "." + apiUrl;
  this.url = apiUrl + aMethod + "&appid=" + this.appid
           + aParams + "&wssid=" + this.wssid;
}

YahooMailAPI.prototype._processUnreadMessages =
function ymAPI__processUnreadMessages(aListener,
                                      aCountryCode,
                                      aMethod,
                                      aParam,
                                      aIsClassicView)
{
  this._logger.debug("_processUnreadMessages('" + aCountryCode + "', '"
                                                + aMethod + "', '"
                                                + aParam + "', "
                                                + aIsClassicView + ")");
  var req = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
            .createInstance(CI.nsIXMLHttpRequest);
  req instanceof Components.interfaces.nsIJSXMLHttpRequest;
  req.open('GET', this.url, true);
  var inst = this;
  var count = 0;
  var messages = [];
  req.onreadystatechange = function onready(aEvt) {
    if (req.readyState == 4) {
      try {
        if (req.status == 200 || req.status == 201) {
          try {
            var msgs = inst._buildUnreadMessagesJSON(req.responseXML,
                                                     aIsClassicView);
            aListener.onResult(msgs);
          } catch (e) {
            inst._logger.error("_processUnreadMessages " + e
                               + " " + e.lineNumber);
          }
        } else {
          var fault = req.responseXML;
          if (fault.getElementsByTagName("url")[0]) {
            var url = fault.getElementsByTagName("url")[0]
                           .firstChild.nodeValue;
            var urlString = "&wssid=";
            var index = url.lastIndexOf(urlString) + urlString.length;
            inst.wssid = url.substring(index, url.length);
            inst._setYahooSpecificURL(aCountryCode, aMethod, aParam);
            return inst._processUnreadMessages(aListener,
                                               aCountryCode,
                                               aMethod,
                                               aParam,
                                               aIsClassicView);
          }
        }
      } catch (e) {
         inst._logger.error("_processUnreadMessages " + e + " " +
                            " " + e.lineNumber + "\n");
         aListener.onError();
      }
    }
  }
  req.send(null);
}


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

// Create array of components.
var gComponentsArray = [YahooMailService];

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

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

