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

const SERVICES_LOCALE_PREFIX = "chrome://flock/locale/services/";
const SERVICES_LOCALE_POSTFIX = ".properties";
const SERVICES_DEFAULT_PROPERTIES_FILE = "chrome://flock/locale/services/services.properties";

var EXPORTED_SYMBOLS = ["FlockSvcUtils"];
CU.import("resource:///modules/FlockXMLUtils.jsm");

/******************************************************************************
 * Flock Service Utilities
 ******************************************************************************/

var FlockSvcUtils = {
  // Interfaces for which we can add default implementations.
  nsIObserver: {},
  flockIWebService: {},
  flockIAuthenticateNewAccount: {},
  flockILoginWebService: {},
  flockISocialWebService: {},
  flockIMediaWebService: {},
  flockIMediaEmbedWebService: {},
  flockIMediaUploadWebService: {},
  flockIWebServiceAccount: {},
  flockIMailWebService: {},
  flockIWebmailAccount: {},
  flockISocialAccount: {},
  flockIRichContentDropHandler: {},
  flockIWebServiceAPI: {}
};

/******************************************************************************
 * Utility Functions
 ******************************************************************************/

// Get the Logger.
// This adds the "_logger" property to the service.
FlockSvcUtils.getLogger =
function FlockSvcUtils_getLogger(aComponent)
{
  if (!aComponent._logger) {
    aComponent._logger = CC["@flock.com/logger;1"]
                         .createInstance(CI.flockILogger);
    if (aComponent.shortName) {
      // No shortName? Caller must call init.
      aComponent._logger.init(aComponent.shortName);
    }
  }
  return aComponent._logger;
};

// Get Account Utilities.
// This adds the "_acUtils" property to the service.
FlockSvcUtils.getACUtils =
function FlockSvcUtils_getACUtils(aComponent)
{
  if (!aComponent._acUtils) {
    aComponent._acUtils = CC["@flock.com/account-utils;1"]
                          .getService(CI.flockIAccountUtils);
  }
  return aComponent._acUtils;
};

// Get Web Detective.
// This adds the "_WebDetective" property to the service.
// As a side effect, also adds the "_acUtils" property.
FlockSvcUtils.getWD =
function FlockSvcUtils_getWD(aComponent)
{
  if (!aComponent._WebDetective) {
    if (aComponent.shortName) {
      aComponent._WebDetective = this.getACUtils(aComponent)
                                     .useWebDetective(aComponent.shortName
                                     + ".xml");
    } else {
      // aComponent is not a Flock service.
      throw CR.NS_ERROR_INVALID_ARG;
    }
  }
  return aComponent._WebDetective;
};

// Get Coop.
// This adds the "_coop" property to the service.
FlockSvcUtils.getCoop =
function FlockSvcUtils_getCoop(aComponent)
{
  if (!aComponent._coop) {
    aComponent._coop = CC["@flock.com/singleton;1"]
                       .getService(CI.flockISingleton)
                       .getSingleton("chrome://flock/content/common/load-faves-coop.js")
                       .wrappedJSObject;
  }
  return aComponent._coop;
};

// Get Coop Service Object.
// This adds the coop service object to the service.
FlockSvcUtils.getCoopService =
function FlockSvcUtils_getCoopService(aComponent) {
  if (!aComponent._coopObj) {
    var atts = {name: aComponent.shortName,
                desc: aComponent.title,
                serviceId: aComponent.contractID,
                domains: aComponent.domains};
    var _coop = this.getCoop(aComponent);
    aComponent._coopObj = new _coop.Service(aComponent.urn, atts);
  }
  return aComponent._coopObj;
};

// Helper to create a "results" object commonly returned from services.
FlockSvcUtils.newResults =
function FlockSvcUtils_newResults()
{
  return CC["@mozilla.org/hash-property-bag;1"]
         .createInstance(CI.nsIWritablePropertyBag2);
};

// Encodes/decodes a string into the ROT13 format.
FlockSvcUtils.scrambleString =
function FlockSvcUtils_scrambleString(aString)
{
  function swapChar(aChar) {
    var code = (aChar <= "Z" ? 90 : 122) >= (aChar = aChar.charCodeAt(0) + 13);
    return String.fromCharCode(code ? aChar : aChar - 26)
  }

  return aString.replace(/[a-zA-Z]/g, swapChar);
};

/******************************************************************************
 * nsIObserver Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.nsIObserver.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var svc = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {

    case "observe":
      if (!proto.observe) {
        proto.observe = function observe(aSubject, aTopic, aData) {};
      }
      break;

    default:
      throw "FlockSvcUtils.nsIObserver.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)
};


/******************************************************************************
 * flockIWebService Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.flockIWebService.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var svc = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {
    case "url":
      if (!proto.url) {
        proto.__defineGetter__("url",
          function defaultImpl_getter_url() {
            this._logger.debug("getter_url()");
            var wd = FlockSvcUtils.getWD(svc);
            return wd.getLocalizedString(svc.shortName, "homePageURL", null);
          }
        );
      }
      break;

    case "getStringBundle":
      // nsIStringBundle getStringBundle();
      if (!proto.getStringBundle) {
        proto.getStringBundle =
        function defaultImpl_getStringBundle() {
          svc._logger.debug("getStringBundle()");
          if (!svc._stringBundle) {
            var sbs = CC["@mozilla.org/intl/stringbundle;1"]
                      .getService(CI.nsIStringBundleService);

            try {

              var bundlePath = svc._stringBundlePath;

              if (!bundlePath) {
                // A default has not been specified so assemble the standard 
                // bundle path for services (this will satisfy Flock created
                // services)
                bundlePath = SERVICES_LOCALE_PREFIX
                           + svc.shortName
                           + SERVICES_LOCALE_POSTFIX;
              }

              // Create the string bundle
              svc._stringBundle = sbs.createBundle(bundlePath);

            } catch (e) {
              // Could not create the bundle so use the default bundle for 
              // services in Flock
              svc._stringBundle =
                sbs.createBundle(SERVICES_DEFAULT_PROPERTIES_FILE);
            }
          }

          return svc._stringBundle;
        };
      }
      break;

    case "loginURL":
      if (!proto.loginURL) {
        proto.__defineGetter__("loginURL",
          function defaultImpl_getter_loginURL() {
            this._logger.debug("getter_loginURL()");
            var wd = FlockSvcUtils.getWD(svc);
            var url = wd.getLocalizedString(svc.shortName, "loginURL", null);
            return url ? url : svc.url;
          }
        );
      }
      break;

    case "isHidden":
      if (!proto.isHidden) {
        proto.isHidden =
        function defaultImpl_isHidden() {
          var pref = "flock.service." + this.shortName + ".hidden";
          var prefService = CC["@mozilla.org/preferences-service;1"]
                            .getService(CI.nsIPrefBranch);
          if (prefService.getPrefType(pref)) {
            var isHidden = prefService.getBoolPref(pref);
            return isHidden;
          }

          return false;
        };
      }
      break;

    default:
      throw "FlockSvcUtils.flockIWebService.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)
};


/******************************************************************************
 * flockIAuthenticateNewAccount Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.flockIAuthenticateNewAccount.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var svc = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {

    case "authenticateNewAccount":
      // void authenticateNewAccount();
      if (!proto.authenticateNewAccount) {
        proto.authenticateNewAccount =
        function defaultImpl_authenticateNewAccount() {
          svc._logger
             .debug("{flockIAuthenticateNewAccount}.authenticateNewAccount()");

          var wm = CC["@mozilla.org/appshell/window-mediator;1"]
                   .getService(CI.nsIWindowMediator);
          var win = wm.getMostRecentWindow("navigator:browser");
          win.openUILinkIn(svc.url, "tab");
        };
      }
      break;

    default:
      throw "FlockSvcUtils.flockIAuthenticateNewAccount.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)
};


/******************************************************************************
 * flockIMediaWebService Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.flockIMediaWebService.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var svc = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {

    case "getChannel":
      if (!proto.getChannel) {
        proto.getChannel = function getChannel(aChannelId) {
          if (!(aChannelId in this._channels)) {
            return null;
          }

          var mc = CC["@flock.com/media-channel;1"]
                   .createInstance(CI.flockIMediaChannel);
          mc.id = aChannelId;
          mc.title = this._channels[aChannelId].title
          mc.supportsSearch = this._channels[aChannelId].supportsSearch;
          return mc;
        }
      }
      break;

    case "enumerateChannels":
      if (!proto.enumerateChannels) {
        proto.enumerateChannels = function enumerateChannel() {
          var enumerateChannelsRecursively =
          function enumerateChannels_Recursively(aChannelObj) {
            var ar = [];
            for (var id in aChannelObj) {
              var mc = CC["@flock.com/media-channel;1"]
                       .createInstance(CI.flockIMediaChannel);
              mc.id = id;
              mc.title = aChannelObj[id].title;
              mc.supportsSearch = aChannelObj[id].supportsSearch;
              var subChannels = null;
              if (aChannelObj[id].subChannels) {
                // Recursively append subChannels
                subChannels =
                  enumerateChannelsRecursively(aChannelObj[id].subChannels);
              }
              mc.subChannels = subChannels;
              ar.push(mc);
            }
            var rval = {
              getNext: function rval_getNext() {
                var rval = ar.shift();
                return rval;
              },
              hasMoreElements: function rval_hasMoreElements() {
                return (ar.length > 0);
              }
            };
            return rval;
          };
          return enumerateChannelsRecursively(this._channels);
        }
      }
      break;

    case "getMediaItemFormatter":
      if (!proto.getMediaItemFormatter) {
        proto.getMediaItemFormatter = function getMediaItemFormatter() {
          // Use the default formatting functions defined in flockPhoto.js
          return null;
        }
      }
      break;

    case "getIconForQuery":
      if (!proto.getIconForQuery) {
        proto.getIconForQuery = function getIconForQuery(aQuery) {
          return svc.icon;
        }
      }
      break;

    default:
      throw "FlockSvcUtils.flockIMediaWebService.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)
};


/******************************************************************************
 * flockIMediaEmbedWebService Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.flockIMediaEmbedWebService.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var svc = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {

    case "getSharingContent":
      if (!proto.getSharingContent) {
        proto.getSharingContent =
        function getSharingContent(aSrc, aProp) {
          svc._logger
             .debug("{flockIMediaEmbedWebService}.getSharingContent()");
          return false;
        }
      }
      break;

    default:
      throw "FlockSvcUtils.flockIMediaEmbedWebService.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)
};

/******************************************************************************
 * flockIMediaWebService Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.flockIMediaUploadWebService.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var svc = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {

    case "getAlbumsForUpload":
      if (!proto.getAlbumsForUpload) {
        proto.getAlbumsForUpload = function getAlbumsForUpload(aFlockListener,
                                                               aUsername)
        {
          svc.getAlbums(aFlockListener, aUsername);
        }
      }
      break;

    default:
      throw "FlockSvcUtils.flockIMediaUploadWebService.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)
};


/******************************************************************************
 * flockILoginWebService Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.flockILoginWebService.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var svc = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {

    case "loginURL":
      if (!proto.loginURL) {
        proto.__defineGetter__("loginURL",
          function defaultImpl_getter_loginURL() {
            this._logger.debug("getter_loginURL()");
            var wd = FlockSvcUtils.getWD(svc);
            var url = wd.getLocalizedString(svc.shortName, "loginURL", null);
            return url ? url : svc.url;
          }
        );
      }
      break;

    case "getAccount":
      // flockIWebServiceAccount getAccount(in AString aUrn);
      // NOTE: This requires an account constructor to be in a private
      //       property on the service, named "_accountClassCtor".
      if (!proto.getAccount) {
        proto.getAccount =
        function defaultImpl_getAccount(aUrn) {
          svc._logger.debug("{flockILoginWebService}.getAccount('" + aUrn + "')");
          var c_acct = FlockSvcUtils.getCoop(svc).get(aUrn);
          if (!c_acct) {
            return null;
          }
          // This is the constructor for the service account class.
          var acct = new svc._accountClassCtor();
          acct.urn = c_acct.id();
          acct.username = c_acct.name;  // XXX This is not in the IDL.
          acct.coopObj = c_acct;
          return acct;
        };
      }
      break;

    case "getAuthenticatedAccount":
      if (!proto.getAuthenticatedAccount) {
        proto.getAuthenticatedAccount =
        function getAuthenticatedAccount() {
          svc.QueryInterface(CI.nsIClassInfo);
          var accounts = FlockSvcUtils.getCoop(svc).Account.find(
          {
            serviceId: svc.contractID,
            isAuthenticated: true
          });
          if (accounts.length) {
            var acct = new svc._accountClassCtor();
            acct.urn = accounts[0].id();
            acct.username = accounts[0].name;  // XXX This is not in the IDL.
            acct.coopObj = accounts[0];
            return acct;
          }
          return null;
        }
      }
      break;

    case "getAccounts":
      // nsISimpleEnumerator getAccounts();
      if (!proto.getAccounts) {
        proto.getAccounts =
        function defaultImpl_getAccounts() {
          svc._logger.debug("{flockILoginWebService}.getAccounts()");
          return FlockSvcUtils.getACUtils(svc)
                              .getAccountsForService(svc.contractId);
        };
      }
      break;

    case "removeAccount":
      // flockIWebServiceAccount removeAccount(in AString aUrn);
      if (!proto.removeAccount) {
        proto.removeAccount =
        function defaultImpl_removeAccount(aUrn) {
          svc._logger.debug("{flockILoginWebService}.removeAccount('" + aUrn + "')");
          FlockSvcUtils.getACUtils(this).removeAccount(aUrn);
        };
      }
      break;

    case "logout":
      // void logout();
      if (!proto.logout) {
        proto.logout =
        function defaultImpl_logout() {
          svc._logger.debug("{flockILoginWebService}.logout()");
          var cookies = FlockSvcUtils.getWD(svc)
                                     .getSessionCookies(svc.shortName);
          if (cookies) {
            FlockSvcUtils.getACUtils(svc).removeCookies(cookies);
          }
        };
      }
      break;

    case "docRepresentsSuccessfulLogin":
      // boolean docRepresentsSuccessfulLogin(in nsIDOMHTMLDocument aDocument);
      if (!proto.docRepresentsSuccessfulLogin) {
        proto.docRepresentsSuccessfulLogin =
        function defaultImpl_docRepresentsSuccessfulLogin(aDocument) {
          svc._logger.debug("{flockILoginWebService}.docRepresentsSuccessfulLogin()");
          return FlockSvcUtils.getWD(svc).detect(svc.shortName, "loggedin",
                                                 aDocument, null );
        };
      }
      break;

    case "getAccountIDFromDocument":
      // AString getAccountIDFromDocument(in nsIDOMHTMLDocument aDocument);
      if (!proto.getAccountIDFromDocument) {
        proto.getAccountIDFromDocument =
        function defaultImpl_getAccountIDFromDocument(aDocument) {
          svc._logger.debug("{flockILoginWebService}.getAccountIDFromDocument()");
          var results = FlockSvcUtils.newResults();
          if (FlockSvcUtils.getWD(svc).detect(svc.shortName, "accountinfo",
                                              aDocument, results))
          {
            return results.getPropertyAsAString("accountid");
          }
          return null;
        };
      }
      break;

    case "getCredentialsFromForm":
      // flockILoginInfo getCredentialsFromForm(in nsIDOMHTMLFormElement aForm);
      if (!proto.getCredentialsFromForm) {
        proto.getCredentialsFromForm =
        function defaultImpl_getCredentialsFromForm(aForm) {
          svc._logger.debug("{flockILoginWebService}.getCredentialsFromForm()");

          aForm.QueryInterface(CI.nsIDOMHTMLFormElement);
          var wd = FlockSvcUtils.getWD(svc);
          var formType = "login";
          var results = FlockSvcUtils.newResults();
          if (!wd.detectForm(svc.shortName, formType, aForm, results)) {
            formType = "signup";
            results = FlockSvcUtils.newResults();
            if (!wd.detectForm(svc.shortName, formType, aForm, results)) {
              formType = "changepassword";
              results = FlockSvcUtils.newResults();
              if (!wd.detectForm(svc.shortName, formType, aForm, results)) {
                results = null;
              }
            }
          }
          if (results) {

          var pw = {
              QueryInterface: function defaultImpl_gcff_pw_QueryInterface(aIID) {
                if (!aIID.equals(CI.nsISupports) &&
                    !aIID.equals(CI.nsILoginInfo) &&
                    !aIID.equals(CI.flockILoginInfo))
                {
                  throw CI.NS_ERROR_NO_INTERFACE;
                }
                return this;
              },
              username: results.getPropertyAsAString("username"),
              password: results.getPropertyAsAString("password"),
              formType: formType
            };
            return pw;
          }
          return null;
        };
      }
      break;

    case "getSessionValue":
      // AString getSessionValue()
      if (!proto.getSessionValue) {
        proto.getSessionValue =
        function defaultImpl_getSessionValue() {
          var acUtils = FlockSvcUtils.getACUtils(svc);
          var cookies = FlockSvcUtils.getWD(svc)
                                     .getSessionCookies(svc.shortName);
          var ckStr, ckUrl = "", val = "";
          while (cookies && cookies.hasMoreElements()) {
            var cookie = cookies.getNext().QueryInterface(CI.nsICookie);
            var url = cookie.host + cookie.path;
            var lengthDiff = ckUrl.length - url.length;
            // no need to re-get the cookie if it's a subdomain of the domain
            if (lengthDiff < 0 || ckUrl.indexOf(url, lengthDiff) == -1) {
              ckUrl = url;
              ckStr = acUtils.getCookie("http://" + ckUrl, null);
            }
            var index = ckStr ? ckStr.indexOf(cookie.name + "=") : -1;
            if (index != -1) {
              index += cookie.name.length + 1;
              var end = ckStr.indexOf(";", index);  
              val += unescape(ckStr.substring(index, 
                                              end == -1 ? ckStr.length : end));
            }
          }
          svc._logger.info(".getSessionValue(): " + val);
          return val;
        };
      }
      break;

    case "ownsDocument":
      // boolean ownsDocument(in nsIDOMHTMLDocument aDocument);
      if (!proto.ownsDocument) {
        proto.ownsDocument =
        function defaultImpl_ownsDocument(aDocument) {
          svc._logger.debug("{flockILoginWebService}.ownsDocument()");
          // Note that ownsDocument() only gets called for documents hosted
          // in a domain for this service... so if no further tests are
          // necessary, it's ok to just return 'true' here.
          return true;
        };
      }
      break;

    case "ownsLoginForm":
      // boolean ownsLoginForm(in nsIDOMHTMLFormElement aForm);
      if (!proto.ownsLoginForm) {
        proto.ownsLoginForm =
        function defaultImpl_ownsLoginForm(aForm) {
          svc._logger.debug("{flockILoginWebService}.ownsLoginForm()");

          aForm.QueryInterface(CI.nsIDOMHTMLFormElement);
          var wd = FlockSvcUtils.getWD(svc);
          if (wd.detectForm(svc.shortName, "login", aForm,
                            FlockSvcUtils.newResults()))
          {
            return true;
          }
          if (wd.detectForm(svc.shortName, "signup", aForm,
                            FlockSvcUtils.newResults()))
          {
            return true;
          }
          if (wd.detectForm(svc.shortName, "changepassword", aForm,
                            FlockSvcUtils.newResults()))
          {
            return true;
          }
          return false;
        };
      }
      break;

    default:
      throw "FlockSvcUtils.flockILoginWebService.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)
};

/******************************************************************************
 * flockISocialWebService Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.flockISocialWebService.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var svc = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {

    case "markAllMediaSeen":
      // void markAllMediaSeen();
      if (!proto.markAllMediaSeen) {
        proto.markAllMediaSeen =
        function defaultImpl_markAllMediaSeen(aPersonURN) {
          svc._logger.debug("{flockISocialWebService}.markAllMediaSeen()");
          var identity = FlockSvcUtils.getCoop(this).get(aPersonURN);
          if (!identity) {
            svc._logger.error("flockISocialWebService.markAllMediaSeen: "
                              + "identity \""
                              + aPersonURN
                              + "\" doesn't exist.");
            return;
          }
          identity.unseenMedia = 0;
        };
      }
      break;

    default:
      throw "FlockSvcUtils.flockISocialWebService.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)
};


/******************************************************************************
 * flockIWebServiceAccount Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var svc = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {

    case "getService":
      if (!proto.getService) {
        proto.getService =
        function defaultImpl_getService() {
          var c_acct = FlockSvcUtils.getCoop(svc).get(this.urn);
          return CC[c_acct.serviceId].getService(CI.flockIWebService);
        };
      }
      break;

    case "login":
      // void login(in flockIListener aFlockListener);
      if (!proto.login) {
        proto.login =
        function defaultImpl_login(aFlockListener) {
          svc._logger.debug("{flockIWebServiceAccount}.login()");
          var c_acct = this.coopObj;
          if (!c_acct.isAuthenticated) {
            c_acct.isAuthenticated = true;
          }
          if (aFlockListener) {
            aFlockListener.onSuccess(this, "login");
          }
        };
      }
      break;

    case "logout":
      // void logout(in flockIListener aFlockListener);
      if (!proto.logout) {
        proto.logout =
        function defaultImpl_logout(aFlockListener) {
          svc._logger.debug("{flockIWebServiceAccount}.logout()");
          var c_acct = this.coopObj;
          if (c_acct.isAuthenticated) {
            c_acct.isAuthenticated = false;
            CC[c_acct.serviceId].getService(CI.flockIWebService).logout();
          }
          if (aFlockListener) {
            aFlockListener.onSuccess(this, "logout");
          }
        };
      }
      break;

    case "keep":
      if (!proto.keep) {
        proto.keep =
        function defaultImpl_keep() {
          var c_acct = this.coopObj;
          c_acct.isTransient = false;
          var svc = CC[c_acct.serviceId].getService(CI.flockIWebService);
          var key = svc.urn + ":" + c_acct.accountId;
          FlockSvcUtils.getACUtils(this).makeTempPasswordPermanent(key);
        };
      }
      break;

    case "isAuthenticated":
      // boolean isAuthenticated();
      if (!proto.isAuthenticated) {
        proto.isAuthenticated =
        function isAuthenticated()
        {
          svc._logger.debug("{flockIWebServiceAccount}.isAuthenticated()");
          return this.coopObj.isAuthenticated;
        };
      }
      break;

    case "getCustomParam":
      if (!proto.getCustomParam) {
        proto.getCustomParam =
        function getCustomParam(aParamName) {
          svc._logger.info(".getCustomParam('" + aParamName + "')");
          var paramList = this.coopObj.accountParamList;
          if (!paramList) {
            var msg = "paramList not defined";
            _reportError("getCustomParam", msg);
            return null;
          }
          param = paramList.getParam(aParamName);
          if (param) {
            return _returnType(param.value, param.type);
          } else {
            var msg = aParamName + " not found";
            _reportError("getParam", msg);
            return null;
          }
        }
      }
      break;

    case "setCustomParam":
      if (!proto.setCustomParam) {
        proto.setCustomParam =
        function setCustomParam(aParamName, aValue)
        {
          svc._logger.info(".setParamValue('" + aParamName + "','"
                                              + aValue + "')");
          var param = null;
          var paramList = this.coopObj.accountParamList;
          var val = null;
          if (!paramList) {
            FlockSvcUtils.getACUtils(svc)
                         .addCustomParamsToAccount(FlockSvcUtils.newResults(),
                                                   this.coopObj.id());
            paramList = this.coopObj.accountParamList;
          }
          param = paramList.getParam(aParamName);
          if (param) {
            param.value = aValue;
          } else {
            FlockSvcUtils.getCoop(this);
            // bug 12025 cannot make FlockSvcUtils.getCoop(this).AccountParam
            // causes an serious account coop object crash
            var param = new this._coop.AccountParam(
              {
                key: aParamName,
                value: aValue,
                type: typeof(aValue)
              });
            paramList.children.add(param);
          }
        }
      }
      break;

    case "setParam":
      if (!proto.setParam) {
        proto.setParam = function setParam(aParamName, aValue) {
          try {
            this.coopObj.set(aParamName, aValue);
          } catch(e) {
            _reportError("setParam", aParamName + " does not exist!");
          }
        }
      }
      break;

    case "getParam":
      if (!proto.getParam) {
        proto.getParam = function getParam(aParamName) {
          try {
            return this.coopObj.get(aParamName);
          } catch (e) {
            _reportError("getParam", aParamName + " does not exist!");
            return null;
          }
        }
      }
      break;

    case "getAllCustomParams":
      if (!proto.getAllCustomParams) {
        proto.getAllCustomParams =
        function getAllCustomParams()
        {
          var paramsList = CC["@mozilla.org/hash-property-bag;1"]
                           .createInstance(CI.nsIWritablePropertyBag);
          var accountParamList = this.coopObj.accountParamList;
          if (!accountParamList) {
            var msg = "accountParamList not defined";
            this._reportError("getAllCustomParams", msg);
          }
          var list = accountParamList.children.enumerate();

          while (list.hasMoreElements()) {
            var param = list.getNext();
            var paramValue = _returnType(param.value, param.type);
            paramsList.setProperty(param.key, paramValue);
          }
          return paramsList;
        }
      }
      break;
      
    default:
      throw "FlockSvcUtils.flockIWebServiceAccount.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)

  function _returnType(aValue, aType)
  {
    var ret = null;
    switch (aType) {
      case "boolean":
        ret = (aValue == "true") ? true : false;
        break;

      case "number":
        ret = parseInt(aValue);
        break;

      default:
        ret = aValue;
        break;
    }
    return ret;
  }

  function _reportError(aMethodName, aMsg) {
    svc._logger.error("error." + aMethodName + ":" + aMsg);
    Components.Exception(aMsg);
  }
  
};

/******************************************************************************
 * flockISocialAccount Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.flockISocialAccount.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var account = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {
    case "formatFriendActivityForDisplay":
      // AString formatFriendActivityForDisplay(in AString aFriendActivityUrn);
      if (!proto.formatFriendActivityForDisplay) {
        proto.formatFriendActivityForDisplay =
        function defaultImpl_formatFriendActivityForDisplay(aFriendActivityUrn) {
          account._logger.debug("formatFriendActivityForDisplay(" 
                                + aFriendActivityUrn  + ")");

          var friendActivity = FlockSvcUtils.getCoop(account)
                                            .get(aFriendActivityUrn);
          var svc = account.getService();
          var bundle = svc.getStringBundle();
          var result = "";

          switch (friendActivity.updateType) {
            case "profile":
              result =
                bundle.GetStringFromName("flock.friendFeed.lastUpdateTypePretty.profile");
              break;

            case "status":
              if (friendActivity.updateValue == "") {
                result =
                  bundle.GetStringFromName("flock.friendFeed.lastUpdateTypePretty.statusCleared");
              } else {
                result = friendActivity.updateValue;
              }
              break;

            case "media":
              result =
                bundle.GetStringFromName("flock.friendFeed.lastUpdateTypePretty.mediaUpdated");
              break;
          }

          return (flockXMLDecode(result));
        };
      }
      break;

    case "getFriendCount":
      // long getFriendCount();
      if (!proto.getFriendCount) {
        proto.getFriendCount = 
        function getFriendCount() {
          var count = 0;
          var c_acct = FlockSvcUtils.getCoop(account).get(this.urn);
          var c_friendslist = c_acct.friendsList;
          if (c_friendslist) {
            var friends = c_friendslist.children.enumerate();
            while (friends.hasMoreElements()) {
              count++;
              friends.getNext();
            }
          }
          return count;
        };
      }
      break;

    case "getInviteFriendsURL":
      // AString getInviteFriendsURL();
      if (!proto.getInviteFriendsURL) {
        proto.getInviteFriendsURL =
        function getInviteFriendsURL() {
          var svc = account.getService();
          return FlockSvcUtils.getWD(account)
                              .getString(svc.shortName.toLowerCase(),
                                         "inviteFriendsURL",
                                         null);
        };
      }
      break;

    case "enumerateFriends":
      if (!proto.enumerateFriends) {
        proto.enumerateFriends =
        function enumerateFriends() {
          function FriendsEnum(aCoopAccount) {
            this._enum = aCoopAccount.friendsList.children.enumerate();
          }
          FriendsEnum.prototype = {
            hasMoreElements: function FriendsEnum_hasMoreElements() {
              return this._enum.hasMoreElements();
            },
            getNext: function FriendsEnum_getNext() {
              var coopPerson = this._enum.getNext();
              var person = CC["@flock.com/person;1"]
                           .createInstance(CI.flockIPerson);
              person.accountId = coopPerson.accountId;
              person.lastUpdate = coopPerson.lastUpdate;
              person.lastUpdateType = coopPerson.lastUpdateType;
              person.name = coopPerson.name;
              person.avatar = coopPerson.avatar;
              person.statusMessage = coopPerson.statusMessage;
              person.unseenMedia = coopPerson.unseenMedia;
              return person;
            }
          }
          var coopAccount = FlockSvcUtils.getCoop(account).get(this.urn);
          return new FriendsEnum(coopAccount);
        };
      }
      break;

    case "getFormattedFriendUpdateType":
      // AString getFormattedFriendUpdateType(aLastUpdateType, aStatus)
      if (!proto.getFormattedFriendUpdateType) {
        proto.getFormattedFriendUpdateType =
        function defaultImpl_mgetFormattedFriendUpdateType(aLastUpdateType,
                                                           aStatus)
        {
          var pplProperties = "chrome://flock/locale/people/people.properties";
          var sb = CC["@mozilla.org/intl/stringbundle;1"]
                   .getService(CI.nsIStringBundleService)
                   .createBundle(pplProperties);
          switch (aLastUpdateType) {
            case "profile":
              return sb.GetStringFromName("flock.people.mecard"
                                          + ".lastUpdateTypePretty.profile");
              break;
              
            case "status":
              if (aStatus == "") {
                return sb.GetStringFromName("flock.people.mecard"
                                            + ".lastUpdateTypePretty"
                                            + ".statusCleared");
              }
              return sb.GetStringFromName("flock.people.mecard"
                                          + ".lastUpdateTypePretty"
                                          + ".statusUpdated");
              break;

            case "media":
              return sb.GetStringFromName("flock.people.mecard"
                                          + ".lastUpdateTypePretty"
                                          + ".mediaUpdated");
              break;
          }
          return null;
        };
      }
      break;

    default:
      throw "FlockSvcUtils.flockISocialAccount.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)
};

/******************************************************************************
 * flockIRichContentDropHandler Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.flockIRichContentDropHandler.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var svc = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {

    case "_handleTextareaDrop":
      if (!proto._handleTextareaDrop) {
        proto._handleTextareaDrop =
        function handleTextareaDrop(aSvcName, aDomains, aTextarea, aCallback) {
          const WD_XPATH = 0;
          const WD_FLAVOUR = 1;

          if (aTextarea instanceof CI.nsIDOMHTMLTextAreaElement) {
            var doc = aTextarea.ownerDocument;
            if (doc instanceof CI.nsIDOMHTMLDocument) {
              var wd = FlockSvcUtils.getWD(this);
              var domainsArray = aDomains.split(",");
              for each (var domain in domainsArray) {
                if (wd.testDomain(doc.URL, domain)) {
                  // Retrieve the specific fields from Web Detective to which
                  // we cannot DnD
                  var fields = wd.getString(aSvcName,
                                            "avoidDnDXPathFields", null);
                  if (fields) {
                    fields = fields.split(";");
                    for each (var avoidDnD in fields) {
                      var xPath = wd.getString(aSvcName, avoidDnD, null);
                      if (xPath) {
                        var results = doc.evaluate(xPath, doc, null,
                                                   CI.nsIDOMXPathResult.ANY_TYPE,
                                                   null);
                        if (results) {
                          var node = results.iterateNext();
                          while (node) {
                            if (node == aTextarea) {
                              // The matching field does not accept rich content, bail
                              return true;
                            }
                            node = results.iterateNext();
                          }
                        }
                      }
                    }
                  }

                  // Retrieve the specific fields from Web Detective to which
                  // we can DnD
                  var pairs = [];
                  fields = wd.getString(aSvcName, "dndXPathFields", null);
                  if (fields) {
                    fields = fields.split(";");
                    for each (var xpfFields in fields) {
                      pairs.push(xpfFields);
                    }
                  }

                  // Go through the list of DnD fields to find a match
                  for each (var xpfPair in pairs) {
                    var xpf = xpfPair.split(",");
                    var xPath = wd.getString(aSvcName, xpf[WD_XPATH], null);
                    if (xPath) {
                      var results = doc.evaluate(xPath, doc, null,
                                                 CI.nsIDOMXPathResult.ANY_TYPE,
                                                 null);
                      if (results) {
                        var node = results.iterateNext();
                        while (node) {
                          if (node == aTextarea) {
                            // Let the service perform the drop via the callback
                            aCallback(xpf[WD_FLAVOUR]);
                            return true;
                          }
                          node = results.iterateNext();
                        }
                      }
                    }
                  }
                }
              }
            }
          }
          return false;
        }
      }
      break;

    default:
      throw "FlockSvcUtils.flockIRichContentDropHandler.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)
};

/******************************************************************************
 * flockIWebmailAccount Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.flockIWebmailAccount.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var svc = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {

    case "onComposeURLLoaded":
      if (!proto.onComposeURLLoaded) {
        proto.onComposeURLLoaded =
        function defaultImpl_onComposeURLLoaded(aMailParams, aTabBrowser) {
          // Do nothing
        }
      }
      break;

    case "getInboxURL":
      if (!proto.getInboxURL) {
        proto.getInboxURL =
        function defaultImpl_getInboxURL() {
          svc._logger.debug("{flockIWebmailAccount}.getInboxURL()");
          var url = FlockSvcUtils.getWD(svc)
                                 .getString(svc._shortName, "inboxURL", "");
          svc._logger.debug("inbox URL is: " + url);
          return url;
        }
      }
      break;

    case "getUnreadMessages":
      if (!proto.getUnreadMessages) {
        proto.getUnreadMessages =
        function defaultImpl_getUnreadMessages(aCheckNow) {
          svc._logger.debug("{flockIWebmailAccount}.getUnreadMessages("
                            + aCheckNow + ")");
          if (aCheckNow) {
            var pollerSvc = CC["@flock.com/poller-service;1"]
                            .getService(CI.flockIPollerService);
            pollerSvc.forceRefresh(this.urn);
          }
          return this.getCustomParam("unreadMessages");
        }
      }
      break;

    case "getUpgradeAccount":
      if (!proto.getUpgradeAccount) {
        proto.getUpgradeAccount =
        function defaultImpl_getUpgradeAccount() {
          svc._logger.debug("{flockIWebmailAccount}.getUpgradeAccount()");
          // Currently only needed for the Yahoo! Mail Plus upgrade so return
          // an "empty" JSON string in most cases
          return '{"title":"","url":""}';
        }
      }
      break;

    case "refreshUnreadMessageList":
      if (!proto.refreshUnreadMessageList) {
        proto.refreshUnreadMessageList =
        function defaultImpl_refreshUnreadMessageList() {
          svc._logger
             .debug("{flockIWebmailAccount}.refreshUnreadMessageList()");
          var wd = FlockSvcUtils.getWD(svc);
          function forceMailServiceRefresh() {
            var timer = CC["@mozilla.org/timer;1"]
                        .createInstance(CI.nsITimer);
            var refresh = {
              notify: function refreshUnreadMessageList_Notifier(aTimer) {
                var pollerSvc = CC["@flock.com/poller-service;1"]
                                .getService(CI.flockIPollerService);
                pollerSvc.forceRefresh(svc.urn);
                timer.cancel();
              }
            };
            const FALLBACK_DELAY = "10000";
            var delay = wd.getString(svc._shortName,
                                     "refreshUnreadMessageListDelay",
                                     FALLBACK_DELAY);
            try {
              delay = parseInt(delay);
              if (delay < 1) {
                delay = parseInt(FALLBACK_DELAY);
              }
            } catch (ex) {
              svc._logger.error("Unparsable WebDetective INT for service "
                                + svc._shortName + ": " + ex);
              delay = parseInt(FALLBACK_DELAY);
            }
            timer.initWithCallback(refresh, delay, CI.nsITimer.TYPE_ONE_SHOT);
          }
          var obs = CC["@mozilla.org/observer-service;1"]
                    .getService(CI.nsIObserverService);
          var docReady = {
            observe: function docReady_obs(aSubject, aTopic, aData) {
              obs.removeObserver(docReady, 'FlockDocumentReady');
              forceMailServiceRefresh();
            }
          };
          obs.addObserver(docReady, 'FlockDocumentReady', false);
        }
      }
      break;

    case "isPrimary":
      if (!proto.isPrimary) {
        proto.isPrimary =
        function defaultImpl_isPrimary() {
          svc._logger.debug("{flockIWebmailAccount}.isPrimary()");
          return this.getCustomParam("isPrimary");
        }
      }
      break;

    case "isSessionExpired":
      if (!proto.isSessionExpired) {
        proto.isSessionExpired =
        function defaultImpl_isSessionExpired() {
          svc._logger.debug("{flockIWebmailAccount}.isSessionExpired()");
          return this.getCustomParam("isSessionExpired");
        }
      }
      break;

    case "setAsPrimary":
      if (!proto.setAsPrimary) {
        proto.setAsPrimary =
        function defaultImpl_setAsPrimary() {
          svc._logger.debug("{flockIWebmailAccount}.setAsPrimary()");
          var acUtils = FlockSvcUtils.getACUtils(svc);

          // Get configured webmail accounts
          var accts = acUtils.getAccountsByInterface("flockIMailWebService");

          // Look for current primary account
          var primaryAcct = null;
          while (accts.hasMoreElements()) {
            var acct = accts.getNext()
                            .QueryInterface(CI.flockIWebmailAccount);
            if (acct.getCustomParam("isPrimary")) {
              primaryAcct = acct;
              break;
            }
          }

          // If this account is already primary, then we're done
          if (primaryAcct != this) {
            // If we found a primary account, unset it
            if (primaryAcct) {
              primaryAcct.setCustomParam("isPrimary", false);
            }

            // Set this as primary
            this.setCustomParam("isPrimary", true);

            // Remember when this was set so that we may revert to the last
            // set primary if necessary
            this.setCustomParam("lastPrimaryDate", Date.now());
          }
        }
      }
      break;

    default:
      throw "FlockSvcUtils.flockIWebmailAccount.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)
};

/******************************************************************************
 * flockIWebServiceAPI Default Method Implementations
 ******************************************************************************/
FlockSvcUtils.flockIWebServiceAPI.addDefaultMethod =
function addDefaultMethod(aComponent, aMethod) {

  var svc = aComponent;
  var proto = aComponent.__proto__;

  switch (aMethod) {

    case "getRequestMethod":
      if (!proto.getRequestMethod) {
        proto.getRequestMethod =
        function defaultImpl_getRequestMethod(aRequestMethod) {
          svc._logger.debug("{flockIWebServiceAPI}.getRequestMethod("
                            + aRequestMethod + ")");

          var methodMap = {
            0: "GET",
            1: "PUT",
            2: "POST"
          };

          if (aRequestMethod) {
            return methodMap[aRequestMethod];
          }
          return methodMap[0];
        }
      }
      break;

    default:
      throw "FlockSvcUtils.flockIWebServiceAPI.addDefaultMethod: "
            + "no default for method " + aMethod;
  } // switch (aMethod)
};
