// 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:///modules/FlockOnetUtils.jsm");
CU.import("resource:///modules/FlockSvcUtils.jsm");
CU.import("resource:///modules/FlockWebmailUtils.jsm");
CU.import("resource:///modules/FlockXPCOMUtils.jsm");
FlockXPCOMUtils.debug = false;

const ONET_WEBMAIL_CID = Components.ID("{FCA80195-7CBA-4CEE-A981-FA0042D42B8C}");
const ONET_WEBMAIL_CONTRACTID = "@flock.com/webservice/webmail/onet;1";

const ONET_WEBMAIL_CLASS_NAME = "Onet.pl Poczta";
const CLASS_SHORT_NAME = "onetwebmail";
const ONET_WEBMAIL_MODULE_NAME = "onetwebmail";

const ONET_FAVICON = "chrome://flock/content/services/onet/onetFavicon.png";
const ONET_WEBMAIL_FEED = "http://foto.onet.pl/flock/mail.feed.xml";

// Webmail
const REFRESH_INTERVAL_PREF = "flock.webmail.refreshInterval";
const STR_UNREAD = "unreadMessages";
const STR_WAITING_TO_SEND = "waitingToSend";
const STR_EMAIL = "email";
const STR_ISUTF16 = "isUTF16Encoded";

// From nsIXMLHttpRequest.idl
const XMLHTTPREQUEST_READYSTATE_COMPLETED = 4;

const HASH_PROPERTY_BAG_CONTRACTID = "@mozilla.org/hash-property-bag;1";

/*************************************************************************
 * Component: OnetWebmailService
 *************************************************************************/
function OnetWebmailService() {
  OnetUtils.initService(this, CLASS_SHORT_NAME, ONET_WEBMAIL_CONTRACTID);

  // Initialize account constructor
  this._accountClassCtor = OnetWebmailAccount;

  var prefs = CC["@mozilla.org/preferences-service;1"]
              .getService(CI.nsIPrefBranch2);
  prefs.addObserver(REFRESH_INTERVAL_PREF, this, false);
}

/*************************************************************************
 * OnetWebmailService: XPCOM Component Creation
 *************************************************************************/

OnetWebmailService.prototype = new FlockXPCOMUtils.genericComponent(
  ONET_WEBMAIL_CLASS_NAME,
  ONET_WEBMAIL_CID,
  ONET_WEBMAIL_CONTRACTID,
  OnetWebmailService,
  CI.nsIClassInfo.SINGLETON,
  [
    CI.nsIObserver,
    CI.nsITimerCallback,
    CI.flockIPollingService,
    CI.flockIWebService,
    CI.flockIAuthenticateNewAccount,
    CI.flockILoginWebService,
    CI.flockIMailWebService,
    CI.flockIRichContentDropHandler,
  ]
);

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

/*************************************************************************
 * OnetWebmailService: flockIWebService Implementation
 *************************************************************************/

// readonly attribute AString title;
OnetWebmailService.prototype.title = ONET_WEBMAIL_CLASS_NAME;

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

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

// DEFAULT: readonly attribute AString url;

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

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

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

/*************************************************************************
 * OnetWebmailService: flockILoginWebService interface
 *************************************************************************/

// DEFAULT: boolean docRepresentsSuccessfulLogin (in nsIDOMHTMLDocument aDocument);

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

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

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

// readonly attribute AString loginURL;
OnetWebmailService.prototype.__defineGetter__("loginURL",
function OnetWebmailService_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
 */
OnetWebmailService.prototype.addAccount =
function OnetWebmailService_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);

  // Get refresh interval from prefs and ensure it's within bounds
  FlockWebmailUtils.setRefreshInterval(account);

  // Add custom parameters to be used
  account.setCustomParam("isPrimary", false);
  account.setCustomParam("isSessionExpired", false);
  account.setCustomParam("hasNewMessages", false);
  account.setCustomParam("lastPrimaryDate", 0);
  account.setCustomParam(STR_UNREAD,
                         FlockWebmailUtils.createEmptyMessageAsJSON());
  account.setCustomParam(STR_WAITING_TO_SEND, "");
  account.setCustomParam(STR_EMAIL, aAccountId);
  account.setCustomParam(STR_ISUTF16, false);

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

// DEFAULT: flockIWebServiceAccount getAccount(in AString aAccountUrn);
// DEFAULT: AString getAccountIDFromDocument(in nsIDOMHTMLDocument aDocument);
// DEFAULT: nsIStringEnumerator getAccountUrns();
// DEFAULT: flockIWebServiceAccount getAuthenticatedAccount();
// DEFAULT: nsIPassword getCredentialsFromForm(in nsIDOMHTMLFormElement aForm);
// 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
 */
OnetWebmailService.prototype.updateAccountStatusFromDocument =
function OnetWebmailService_updateAccountStatusFromDocument(aDocument,
                                                            aAccount,
                                                            aFlockListener)
{
  if (aAccount) {
    // We are currently logged in to aAcctUrn
    if (!aAccount.isAuthenticated()) {
      // The user has just been logged in
      FlockWebmailUtils.onLogin(aAccount);
      var inst = this;
      var loginListener = {
        onSuccess: function loginListener_onSuccess(aSubj, aTopic) {
          // Force refresh on login
          inst._authMgr.reportCompleteLoginToNetwork(aAccount);
          var pollerSvc = CC["@flock.com/poller-service;1"]
                          .getService(CI.flockIPollerService);
          //pollerSvc.forceRefresh(aAccount.urn);
          if (aFlockListener) {
            aFlockListener.onSuccess(aAccount, "login");
          }
        },
        onError: function loginListener_onError(aFlockError, aTopic) {
          if (aFlockListener) {
            aFlockListener.onError(aFlockError, aTopic);
          }
        }
      };
      aAccount.login(loginListener);
    }
  } else if (this._wd.detect(this.shortName, "loggedout", aDocument, null)) {
    // We're logged out (of all accounts), logout and deauthenticate the api
    this.logout();
  }
};

/**
 * void logout ()
 * @see flockILoginWebService#logout
 */
OnetWebmailService.prototype.logout =
function OnetWebmailService_logout(aDocument) {
  this._logger.debug(".logout()");
  this._acUtils
      .removeCookies(this._wd.getSessionCookies(CLASS_SHORT_NAME));
  this._acUtils.markAllAccountsAsLoggedOut(this);
  this._authMgr.reportCompleteLogoutFromNetwork(ONET_WEBMAIL_CONTRACTID);
  // Ensure the webmail icon does not indicate new unread messages while
  // logged out of the primary account
  if (!this._acUtils.primaryAccountIsHot()) {
    FlockWebmailUtils.lightIcon(false);
  }
};


/*************************************************************************
 * OnetWebmailService: flockIAuthenticateNewAccount interface
 *************************************************************************/

OnetWebmailService.prototype.authenticateNewAccount =
function OnetWebmailService_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");
};

/**************************************************************************
 * OnetWebmailService: flockIPollingService Implementation
 **************************************************************************/

/**
 * void refresh(in AString aUrn, in flockIPollingListener aPollingListener);
 * @see flockIPollingService#refresh
 */
OnetWebmailService.prototype.refresh =
function OnetWebmailService_refresh(aUrn, aPollingListener) {
  this._logger.debug("refresh(" + aUrn + ")");

  var account = this.getAccount(aUrn);
  if (!account.isAuthenticated()) {
    aPollingListener.onResult();
    return;
  }

  var svc = this;
  var refreshListener = {
    onSuccess: function refreshListener_onSuccess(aMsgs, aTopic) {
      svc._logger.debug("Success");
      FlockWebmailUtils.handleRefreshedMessages(svc, aUrn, aMsgs);
      aPollingListener.onResult();
    },
    onError: function refreshListener_onError(aFlockError, aTopic) {
      svc._logger.debug("unable to refresh account " + aUrn);
      svc.getAccount(aUrn)
         .setCustomParam(STR_UNREAD,
                         FlockWebmailUtils.createEmptyMessageAsJSON());
      aPollingListener.onError(aFlockError);
    }
  }
  this._refreshUnreadMessages(account, refreshListener);
};

/**************************************************************************
 * OnetWebmailService: private members
 **************************************************************************/

OnetWebmailService.prototype._buildUnreadMessagesJSON =
function OnetWebmailService__buildUnreadMessagesJSON(aXml) {
  this._logger.debug("_buildUnreadMessagesJSON()" +
                     CC["@mozilla.org/xmlextras/xmlserializer;1"]
                     .getService(CI.nsIDOMSerializer)
                     .serializeToString(aXml));

  // Iterate over the feed entries and form a message array
  var messageInfos = aXml.getElementsByTagName("entry");
  var messages = [];
  var newestMessageDate = 0;
  var unreadCount = 0;

  for (var i = 0; i < messageInfos.length; i++) {
    var msg = messageInfos[i];
    var who = msg.getElementsByTagName("contributor")[0];
    var name = who.getElementsByTagName("name")[0].firstChild.nodeValue;
    var email = who.getElementsByTagName("email")[0].firstChild.nodeValue;
    var fromString = email;
    var msgUrl = msg.getElementsByTagName("link")[0].getAttribute("href");
    var subject = msg.getElementsByTagName("title")[0].firstChild.nodeValue;

    messages.push({
      from: fromString,
      subject: subject,
      url: msgUrl
    });

    // Determine if the the message has been read; currently the api is
    // returning <newcount /> with incorrect data, check the message
    // data itself for an accurate value
    if (parseInt(msg.getElementsByTagName("unreaded")[0].firstChild.nodeValue))
    {
      unreadCount++;
    }

    // Grab the date of the newest message
    if (!newestMessageDate) {
      newestMessageDate = msg.getElementsByTagName("updated")[0]
                             .firstChild.nodeValue;
    }
  }

  var unreadMessages = {
    lastCheckedDate: Date.now(),
    newestMessageDate: newestMessageDate,
    unreadMessageCount: unreadCount,
    messages: messages
  };

  var nsJSON = CC["@mozilla.org/dom/json;1"].createInstance(CI.nsIJSON);
  return nsJSON.encode(unreadMessages);
};

OnetWebmailService.prototype._refreshUnreadMessages =
function OnetWebmailService__refreshUnreadMessages(aAccount, aFlockListener) {
  this._logger.debug("._refreshUnreadMessages() aAccount: " + aAccount);

  var req = CC["@mozilla.org/xmlextras/xmlhttprequest;1"]
            .createInstance(CI.nsIXMLHttpRequest);
  req instanceof Components.interfaces.nsIJSXMLHttpRequest;
  req.mozBackgroundRequest = true;
  req.open("GET", ONET_WEBMAIL_FEED, true);

  var inst = this;
  req.onreadystatechange = function onready(aEvt) {
    inst._logger.debug("._refreshUnreadMessages() ReadyState: "
                       + req.readyState);
    if (req.readyState == XMLHTTPREQUEST_READYSTATE_COMPLETED) {
      var status = req.status;
      inst._logger.debug("._refreshUnreadMessages() status: " + status);
      if (status == 200 || status == 201) {
        var responseXML = req.responseXML;
        inst._logger.debug("._refreshUnreadMessages() "
                           + "response: " + req.responseText);
        try {
          var msgs = inst._buildUnreadMessagesJSON(responseXML);
          aFlockListener.onSuccess(msgs);
        } catch (ex) {
          inst._logger.error("_processUnreadMessages " + ex);
        }
      } else {
        if (status == 401) {
          // Unauthorized, deauthenticate the coop account
          inst._acUtils.markAllAccountsAsLoggedOut(inst);
        }
        // HTTP errors (0 for connection lost)
        inst._logger.debug(".call() HTTP Error");
        aFlockListener.onError(OnetUtils.api.getError(status),
                               arguments.callee.name);
      }
    }
  }
  req.send(null);
};

/**************************************************************************
 * OnetWebmailService: nsIObserver Implementation
 **************************************************************************/

OnetWebmailService.prototype.observe =
function OnetWebmailService_observe(aSubject, aTopic, aState) {
  switch (aTopic) {
    case "nsPref:changed":
      if (aState == REFRESH_INTERVAL_PREF) {
        this._logger.debug("Observer called: refresh interval changed\n");
        FlockWebmailUtils.setRefreshIntervalForAllAccounts(this);
      }
      break;
  }
};

/**************************************************************************
 * OnetWebmailService: flockIRichContentDropHandler Implementation
 **************************************************************************/

/**
 * boolean handleDrop (in nsITransferable aFlavours,
 *                     in nsIDOMHTMLTextAreaElement aTextarea)
 * @see flockIRichContentDropHandler#handleDrop
 */
OnetWebmailService.prototype.handleDrop =
function OnetWebmailService_handleDrop(aFlavours, aTextarea) {
  return FlockWebmailUtils.handleTextareaDrop(this, aFlavours, aTextarea);
};

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

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

  this._WebDetective = CC["@flock.com/web-detective;1"]
                       .getService(CI.flockIWebDetective);

  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");

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

}

/**************************************************************************
 * OnetWebmailAccount: XPCOM Component Creation
 **************************************************************************/

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

/**************************************************************************
 * OnetWebmailAccount: 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
 */
OnetWebmailAccount.prototype.keep =
function OnetWebmailAccount_keep() {
  this._logger.debug("OnetWebmailAccount_keep()");
  this.coopObj.isTransient = false;
  this._acUtils.makeTempPasswordPermanent(this.getService().urn
                                          + ":"
                                          + this.coopObj.accountId);
};

/**
 * void login(in flockIListener aFlockListener)
 * @see flockIWebServiceAccount#login
 */
OnetWebmailAccount.prototype.login =
function OnetWebmailAccount_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)

/**************************************************************************
 * OnetWebmailAccount: flockIWebmailAccount Implementation
 **************************************************************************/

/**
 * AString getComposeURL(in AString aMailParams);
 * @see flockIWebmailAccount#getComposeURL
 */
OnetWebmailAccount.prototype.getComposeURL =
function OnetWebmailAccount_getComposeURL(aMailParams) {
   var params = FlockWebmailUtils.reformatMailParams(this, aMailParams, ",");
   if (params) {
     // Convert newlines to line breaks
     params = params.replace(/%0a/gi, "<br />")
                    .replace(/%0d/gi, "<br />");
   }

  // Get URL and add params
  var url = this._WebDetective.getString(CLASS_SHORT_NAME, "ComposeURL", "")
                              .replace("%params%", params);
  this._logger.debug("compose URL is: " + url);
  return url;
};

/**
 * AString getEmailAddress();
 * @see flockIWebmailAccount#getEmailAddress
 */
OnetWebmailAccount.prototype.getEmailAddress =
function OnetWebmailAccount_getEmailAddress() {
  this._logger.debug("getEmailAddress()");
  var email = this.getCustomParam(STR_EMAIL);
  if (!email) {
    email = this.coopObj.accountId;
  }
  this._logger.debug("email address is: " + email);
  return email;
};

/**
 * AString getInboxURL();
 * @see flockIWebmailAccount#getInboxURL
 */
OnetWebmailAccount.prototype.getInboxURL =
function OnetWebmailAccount_getInboxURL() {
  var url = this._WebDetective.getString(CLASS_SHORT_NAME,
                                         "InboxURL",
                                         "");
  this._logger.debug("getInboxURL(): " + url);
  return url;
};

// DEFAULT: AString getUnreadMessages (in boolean aCheckNow)
// DEFAULT: AString getUpgradeAccount ()
// DEFAULT: boolean isPrimary ()
// DEFAULT: boolean isSessionExpired ()
// DEFAULT: void onComposeURLLoaded (in AString aComposeURL,
//                                   in nsIDOMXULElement aTabBrowser)
// DEFAULT: void refreshUnreadMessageList ()
// DEFAULT: void setAsPrimary ()

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

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

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