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

// Import this file with the following statement:
// CU.import("resource:///modules/FlockWebmailUtils.jsm");
var EXPORTED_SYMBOLS = ["FlockWebmailUtils"];

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

CU.import("resource:///modules/FlockPrefsUtils.jsm");

/**************************************************************************
 * Module: Webmail Back End
 **************************************************************************/

/* Note: This module depends on access to the following PRIVATE properties
         of webmail services and accounts being supported:
         - aAcct._WebDetective
*/

const STR_UNREAD = "unreadMessages";
const STR_WAITING_TO_SEND = "waitingToSend";
const AUTO_SET_PRIMARY_PREF = "flock.webmail.autoSetPrimaryAccount";
const REFRESH_INTERVAL_PREF = "flock.webmail.refreshInterval";

const REFRESH_INTERVAL_DEFAULT = 300;  // 5 min in seconds
const REFRESH_INTERVAL_MIN = 60;  // 1 min in seconds
const REFRESH_INTERVAL_MAX = 3600;  // 60 min in seconds

/**************************************************************************
 * Webmail Back End Public Interface
 **************************************************************************/

var FlockWebmailUtils = {};

FlockWebmailUtils.checkForPendingComposeMessage =
function FWU_checkForPendingComposeMessage(aAcct) {
  // Get pending compose URL
  var composeURL = aAcct.getCustomParam(STR_WAITING_TO_SEND);
  // Be sure the account is logged in
  if (composeURL && aAcct.isAuthenticated()) {
    // Clear pending URL from account
    aAcct.setCustomParam(STR_WAITING_TO_SEND, "");
    // Open pending compose message
    FlockWebmailUtils.openComposeMessageTab(aAcct, composeURL);
  }
};

FlockWebmailUtils.composeMessage =
function FWU_composeMessage(aAcct, aMailtoString) {
  // Get the compose URL from the account
  var composeURL = aAcct.getComposeURL(aMailtoString);
  if (!composeURL) {
    return false;
  }

  // If the account is logged in, open the compose page
  if (aAcct.isAuthenticated()) {
    return FlockWebmailUtils.openComposeMessageTab(aAcct, composeURL);
  }

  var acUtils = CC["@flock.com/account-utils;1"]
                .getService(CI.flockIAccountUtils);

  // Not logged in -- check if another account is logged in and we can log out
  // of it
  if (acUtils.ensureNoAuthenticatedAccountsBeforeLogin(aAcct)) {
    // Save the compose URL to be sent once we've logged in
    aAcct.setCustomParam(STR_WAITING_TO_SEND, composeURL);

    // Open a login window
    var service = aAcct.getService();
    var win = CC["@mozilla.org/appshell/window-mediator;1"]
              .getService(CI.nsIWindowMediator)
              .getMostRecentWindow("navigator:browser");
    win.openUILinkIn(service.loginURL, "tab");
  }

  return true;
};

FlockWebmailUtils.createEmptyMessageAsJSON =
function FWU_createEmptyMessageAsJSON(aLastMessageDate) {
  var unreadMessages = {
    lastCheckedDate: Date.now(),
    newestMessageDate: aLastMessageDate ? aLastMessageDate : 0,
    unreadMessageCount: 0,
    messages: []
  };
  var nsJSON = CC["@mozilla.org/dom/json;1"].createInstance(CI.nsIJSON);
  return nsJSON.encode(unreadMessages);
};

FlockWebmailUtils.ensurePrimaryAccountExists =
function FWU_ensurePrimaryAccountExists() {
  var mostRecentPrimaryAccount = null;
  var mostRecentPrimaryDate = 0;
  // Get configured webmail accounts
  var acctsEnum = CC["@flock.com/account-utils;1"]
                  .getService(CI.flockIAccountUtils)
                  .getAccountsByInterface("flockIMailWebService");
  while (acctsEnum.hasMoreElements()) {
    var acct = acctsEnum.getNext()
                        .QueryInterface(CI.flockIWebmailAccount);
    if (acct) {
      if (acct.isPrimary()) {
        // Found the primary account, so we're done
        return;
      }
      // Find most recent account to be set primary
      var lastPrimaryDate = acct.getCustomParam("lastPrimaryDate");
      // We include = here to catch the case where there are accounts but none
      // of them have ever been set to primary. This way, there will still be
      // a candidate to make primary.
      if (lastPrimaryDate >= mostRecentPrimaryDate) {
        mostRecentPrimaryDate = lastPrimaryDate;
        mostRecentPrimaryAccount = acct;
      }
    }
  }
  // There's no primary account so set most-recent as primary
  if (mostRecentPrimaryAccount) {
    mostRecentPrimaryAccount.setAsPrimary();
  }
};

FlockWebmailUtils.getPrimaryAccount =
function FWU_getPrimaryAccount() {
  // Get configured webmail accounts
  var acctsEnum = CC["@flock.com/account-utils;1"]
                  .getService(CI.flockIAccountUtils)
                  .getAccountsByInterface("flockIMailWebService");
  while (acctsEnum.hasMoreElements()) {
    var acct = acctsEnum.getNext()
                        .QueryInterface(CI.flockIWebmailAccount);
    if (acct && acct.isPrimary()) {
      return acct;
    }
  }
  return null;
};

FlockWebmailUtils.handleAuthError =
function FWU_handleAuthError(aAcct) {
  if (aAcct) {
    // Set expired session flag
    aAcct.setCustomParam("isSessionExpired", true);
    // Log out the account
    aAcct.logout(null);
    // Light the webmail icon
    FlockWebmailUtils.lightIcon(true);
  }
};

FlockWebmailUtils.handleRefreshedMessages =
function FWU_handleRefreshedMessages(aService, aURN, aMsgs) {
  var nsJSON = CC["@mozilla.org/dom/json;1"].createInstance(CI.nsIJSON);
  // Get the mail account
  var account = aService.getAccount(aURN)
                        .QueryInterface(CI.flockIWebmailAccount);
  if (account) {
    // Get old unread messages object
    var oldUnreads = nsJSON.decode(account.getCustomParam(STR_UNREAD));
    // Get new unread messages object
    var newUnreads = nsJSON.decode(aMsgs);

    // Check for new messages
    if (newUnreads.newestMessageDate > oldUnreads.newestMessageDate) {
      // Got new messages
      account.setCustomParam("hasNewMessages", true);
      // If this is the primary account, light the icon
      if (account.isPrimary()) {
        FlockWebmailUtils.lightIcon(true);
      }
    } else if (newUnreads.newestMessageDate < oldUnreads.newestMessageDate) {
      // new messages have been read
      account.setCustomParam("hasNewMessages", false);
      // If this is the primary account, unlight the icon
      if (account.isPrimary()) {
        FlockWebmailUtils.lightIcon(false);
      }
    }

    // Save new unreads
    account.setCustomParam(STR_UNREAD, aMsgs);
  }
};

FlockWebmailUtils.handleTextareaDrop =
function FWU_handleTextareaDrop(aService, aFlavours, aTextarea) {
  var dropCallback = function fwu_dropCallback(aFlav) {
    // Get the data object
    var dataObj = {}, len = {};
    aFlavours.getTransferData(aFlav, dataObj, len);

    // Bring text area into focus and save its state before we mess with it
    aTextarea.focus();
    var caretPos = aTextarea.selectionEnd;
    var currentValue = aTextarea.value;

    // The data will be dropped at the current insertion point, but we don't
    // want to drop it at the end of the message, in case there's a signature
    // present. So in this case, we'll drop at the top of the message.
    if (currentValue.length > 0 && currentValue.length == caretPos) {
      caretPos = 0;
    }

    // Get the Rich DnD service
    var richDnDSvc = CC["@flock.com/rich-dnd-service;1"]
                     .getService(CI.flockIRichDNDService);

    // We'll only add a breadcrumb if there isn't one already present.
    var breadcrumb = "";
    if (!richDnDSvc.hasBreadcrumb(aTextarea)) {
      breadcrumb = richDnDSvc.getBreadcrumb("plain");
    }

    // If there's no breadcrumb, we'll add a newline to provide separation
    // between dropped item and existing text
    if (!breadcrumb) {
      breadcrumb = "\n";
    }

    // Insert the data and possibly breadcrumb into the message
    var data = dataObj.value.QueryInterface(CI.nsISupportsString)
                            .data.replace(/: /, ":\n");
    aTextarea.value = currentValue.substring(0, caretPos)
                    + data
                    + breadcrumb
                    + currentValue.substring(caretPos);

    // Put the insertion point at the end of the inserted item
    caretPos += data.length + 1;

    // If the item was added to the end of the message, which will happen when
    // starting with an empty message, we'll add a newline after the insertion
    // point. This will ensure that subsequent drops will be placed after this
    // item. If we don't do this, the next drop will be added to the top of the
    // message, following our rules above, which is undesirable. Note that this
    // will only occur when there is no breadcrumb.
    if (caretPos == aTextarea.value.length) {
      aTextarea.value += "\n";
    }
    aTextarea.setSelectionRange(caretPos, caretPos);
  };

  return aService._handleTextareaDrop(aService.shortName,
                                      aService._coopObj.domains,
                                      aTextarea,
                                      dropCallback);
};

FlockWebmailUtils.lightIcon =
function FWU_lightIcon(aHasNew) {
  CC["@mozilla.org/observer-service;1"]
    .getService(CI.nsIObserverService)
    .notifyObservers(null, "new-webmail-notification", aHasNew);
};

FlockWebmailUtils.onLogin =
function FWU_onLogin(aAcct) {
  // Do we need to set this as the primary account?
  if (FlockPrefsUtils.getBoolPref(AUTO_SET_PRIMARY_PREF)) {
    aAcct.setAsPrimary();
  } else {
    FlockWebmailUtils.ensurePrimaryAccountExists();
  }

  // Clear expired session flag
  aAcct.setCustomParam("isSessionExpired", false);
  // Do a refresh every time we log in
  aAcct.setParam("nextRefresh", new Date(0));
  // Ensure account is pollable
  aAcct.setParam("isPollable", true);
};

FlockWebmailUtils.openComposeMessageTab =
function FWU_openComposeMessageTab(aAcct, aComposeURL) {
  if (aAcct && aComposeURL) {
    // Get the current browser window
    var win = CC["@mozilla.org/appshell/window-mediator;1"]
              .getService(CI.nsIWindowMediator)
              .getMostRecentWindow("navigator:browser");
    if (win) {
      var browser = win.getBrowser();
      var onComposeLoad = function FWU_compose_onload() {
        // Do any handling necessary after the page has loaded
        browser.removeEventListener("load", onComposeLoad, true);
        aAcct.onComposeURLLoaded(aComposeURL, browser);
      };
      browser.addEventListener("load", onComposeLoad, true);
      // Open the url in a new tab on the window
      win.openUILinkIn(aComposeURL, "tab");
      return true;
    }
  }
  return false;
};

FlockWebmailUtils.parseMailParams =
function FWU_parseMailParams(aAcct, aComposeURL, aComposeParamStr) {
  // Given a compose URL for an account, parse out the mailto fields.
  var paramList = {};
  if (aAcct && aComposeURL) {
    // Get query string
    var parts = aComposeURL.split("?");
    var query = parts[1] ? parts[1] : aComposeURL;
    // Remove anchor hooks
    query = query.split("#")[0];

    // Get name-value pairs
    var pairs = query.split("&");

    // If the mailto keys for this service differ from "to", "subject", "body",
    // "cc" and "bcc", we'll get their equivalents
    var composeParamStr = aComposeParamStr ? aComposeParamStr : "composeParams";
    var cParams = aAcct._WebDetective
                       .getString(aAcct._shortName, composeParamStr, "")
                       .split(",");
    // Fill a hash with these keys
    var paramSubs = {};
    for each (var keys in cParams) {
      var pair = keys.split("=");
      paramSubs[pair[1]] = pair[0];
    }

    // Go through the pairs and handle each as appropriate, adding them
    // to the corresponding parameter.
    for each (var pair in pairs) {
      parts = pair.split("=");
      var name = decodeURIComponent(parts.shift());
      // Look for substitute
      if (paramSubs[name]) {
        name = paramSubs[name]; 
      }
      var value = parts.join("=");
      if (name && value) {
        paramList[name] = decodeURIComponent(value);
      }
    }
  }

  return paramList;
};

FlockWebmailUtils.reformatMailParams =
function FWU_reformatMailParams(aAcct, aMailParams, aAddrSep, aComposeParamStr)
{
  // If aMailParams contains "mailto:", it will probably have a "?"
  // separating the recipient from the rest of the params.  Replace
  // with "to=" and "&", respectively.
  var params = "";
  if (aMailParams) {
    if (aMailParams.toLowerCase().indexOf("mailto:") == 0) {
      aMailParams = aMailParams.replace(/mailto:/i, "to=");
      aMailParams = aMailParams.replace(/\?/, "&");
    }

    // Get name-value pairs from parameters
    var pairs = aMailParams.split("&");

    // Go through the pairs and handle each as appropriate, adding them
    // to the corresponding parameter.
    var paramValues = {};
    for each (var pair in pairs) {
      var parts = pair.split("=");
      var name = decodeURIComponent(parts.shift()).toLowerCase();
      var value = parts.join("=");
      if (name && value) {
        switch (name) {
          case "to":
          case "cc":
          case "bcc":
            if (paramValues[name]) {
              // If there are mutiple address, separate by semi-colon
              paramValues[name] += aAddrSep + value;
            } else {
              paramValues[name] = value;
            }
            break;
          case "body":
            if (paramValues[name]) {
              // If there are multiple bodies, separate by newlines
              paramValues[name] += "\n" + value;
            } else {
              paramValues[name] = value;
            }
            break;
          case "subject":
            // If there are multiple subjects, use last one
            paramValues[name] = value;
            break;
        }
      }
    }

    // Replace the mailto keys with their webservice equivalents when
    // they differ from "to", "subject", "body", "cc" and "bcc"
    var composeParamStr = aComposeParamStr ? aComposeParamStr : "composeParams";
    var cParams = aAcct._WebDetective
                       .getString(aAcct._shortName, composeParamStr, "")
                       .split(",");
    // Fill a hash with these keys
    var paramSubs = {};
    for each (var keys in cParams) {
      var pair = keys.split("=");
      paramSubs[pair[0]] = pair[1];
    }

    function addParameterPair(aField) {
      if (paramValues[aField]) {
        params = params
               + (params ? "&" : "")
               + (paramSubs[aField] ? paramSubs[aField] : aField)
               + "="
               + paramValues[aField];
      }
    }

    // Build a new parameter string with these updated values
    addParameterPair("to");
    addParameterPair("cc");
    addParameterPair("bcc");
    addParameterPair("subject");
    addParameterPair("body");
  }

  return params;
};

// Get refresh interval from prefs, ensure it is within bounds, and set for
// account
FlockWebmailUtils.setRefreshInterval =
function FWU_setRefreshInterval(aAccount) {
  var refreshInterval = FlockPrefsUtils.getIntPref(REFRESH_INTERVAL_PREF);
  if (!refreshInterval) {
    refreshInterval = REFRESH_INTERVAL_DEFAULT;
  } else if (refreshInterval < REFRESH_INTERVAL_MIN) {
    refreshInterval = REFRESH_INTERVAL_MIN;
  } else if (refreshInterval > REFRESH_INTERVAL_MAX) {
    refreshInterval = REFRESH_INTERVAL_MAX;
  }
  aAccount.setParam("refreshInterval", refreshInterval);
};

// Set the refresh interval for all the service's accounts
// Uses the browser pref webmail refresh interval
FlockWebmailUtils.setRefreshIntervalForAllAccounts =
function FWU_setRefreshIntervalForAllAccounts(aSvc) {
  var accountUrns = aSvc.getAccountUrns();
  while (accountUrns.hasMore()) {
    var account = aSvc.getAccount(accountUrns.getNext());
    this.setRefreshInterval(account);
    if (account.isAuthenticated()) {
      var pollerSvc = CC["@flock.com/poller-service;1"]
                      .getService(CI.flockIPollerService);
      pollerSvc.forceRefresh(account.urn);
    }
  }
}
