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

#include "flockLogger.h"

#include "nsIPrefService.h"
#include "nsIPrefBranch2.h"
#include "nsServiceManagerUtils.h"
#include "nsICategoryManager.h"
#include "nsISupportsPrimitives.h"
#include "nsIObserverService.h"


#define PREF_LOGGER_ENABLED "flock.service.logger.enabled"
#define PREF_LOGGER_LEVEL "flock.service.logger.level"

#define LOGGING_OBSERVER_CATEGORY "flockILoggingObserver"

#ifdef DEBUG
#define DEFAULT_LEVEL flockILoggingService::LEVEL_ALL
#else
#define DEFAULT_LEVEL flockILoggingService::LEVEL_WARN
#endif

#define NO_OVERRIDE_LEVEL -1


static flockLoggingService *gLoggingService = nsnull;


flockLoggingService::flockLoggingService()
  : mLevel(DEFAULT_LEVEL), mEnabled(PR_TRUE)
{
}

flockLoggingService::~flockLoggingService()
{
  gLoggingService = nsnull;
}

flockLoggingService*
flockLoggingService::GetSingleton()
{
  if (!gLoggingService) {
    gLoggingService = new flockLoggingService();
    if (!gLoggingService) {
        return nsnull;
    }

    NS_ADDREF(gLoggingService);
    if (NS_FAILED(gLoggingService->Initialize())) {
      NS_RELEASE(gLoggingService);
      return nsnull;
    }

    return gLoggingService;
  }

  NS_ADDREF(gLoggingService);
  return gLoggingService;
}

NS_IMPL_ISUPPORTS2(flockLoggingService, flockILoggingService, nsIObserver)

nsresult flockLoggingService::Initialize()
{
  nsresult rv;

  nsCOMPtr<nsICategoryManager> categoryManager(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsISimpleEnumerator> enumerator;
  rv = categoryManager->EnumerateCategory(LOGGING_OBSERVER_CATEGORY,
                                          getter_AddRefs(enumerator));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsISupports> entry;
  while (NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(entry)))) {
    nsCOMPtr<nsISupportsCString> category(do_QueryInterface(entry, &rv));

    if (NS_SUCCEEDED(rv)) {
      nsCAutoString categoryEntry;
      rv = category->GetData(categoryEntry);

      nsCString contractId;
      rv |= categoryManager->GetCategoryEntry(LOGGING_OBSERVER_CATEGORY,
                                              categoryEntry.get(),
                                              getter_Copies(contractId));

      if (NS_SUCCEEDED(rv)) {
        nsCOMPtr<flockILoggingObserver> observer(do_GetService(contractId.get(), &rv));
        if (NS_SUCCEEDED(rv)) {
          AddObserver(observer);
        }
      }
    }
  }

  nsCOMPtr<nsIObserverService> observerService(do_GetService("@mozilla.org/observer-service;1"));
  if (observerService) {
    observerService->AddObserver(this, "profile-after-change", PR_FALSE);
    observerService->AddObserver(this, "profile-before-change", PR_FALSE);
  }

  return NS_OK;
}

nsresult flockLoggingService::Setup()
{
  nsCOMPtr<nsIPrefBranch2> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
  if (prefBranch) {
    prefBranch->AddObserver(PREF_LOGGER_ENABLED, this, PR_FALSE);
    prefBranch->AddObserver(PREF_LOGGER_LEVEL, this, PR_FALSE);

    return PrefsChanged(nsnull);
  }

  return NS_OK;
}

nsresult flockLoggingService::Shutdown()
{
  nsCOMPtr<nsIPrefBranch2> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
  if (prefBranch) {
    prefBranch->RemoveObserver(PREF_LOGGER_ENABLED, this);
    prefBranch->RemoveObserver(PREF_LOGGER_LEVEL, this);
  }

  return NS_OK;

}

nsresult flockLoggingService::PrefsChanged(const char *aPref)
{
  nsresult rv;

  nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  if (!aPref || !strcmp(aPref, PREF_LOGGER_ENABLED)) {
    rv = prefBranch->GetBoolPref(PREF_LOGGER_ENABLED, &mEnabled);
    if (NS_FAILED(rv)) {
      mEnabled = PR_TRUE;
    }
  }

  if (!aPref || !strcmp(aPref, PREF_LOGGER_LEVEL)) {
    rv = prefBranch->GetIntPref(PREF_LOGGER_LEVEL, &mLevel);
    if (NS_FAILED(rv)) {
      mLevel = DEFAULT_LEVEL;
    }
  }

  return NS_OK;
}

NS_IMETHODIMP flockLoggingService::Log(PRInt32 aLevel,
                                       PRInt32 aOverrideLevel,
                                       const nsAString & aContext,
                                       const nsAString & aMessage)
{
  PRInt32 effectiveLevel = mLevel;
  if (aOverrideLevel > NO_OVERRIDE_LEVEL) {
    effectiveLevel = aOverrideLevel;
  }

  if (mEnabled) {
    if (aLevel >= effectiveLevel) {
      PRTime now = PR_Now();
      for (PRInt32 i = 0; i < mObservers.Count(); i++) {
        mObservers[i]->Emit(now, aLevel, aContext, aMessage);
      }
    }
  }

  return NS_OK;
}

NS_IMETHODIMP flockLoggingService::SetLevel(PRInt32 aLevel, PRInt32 *_retval)
{
  *_retval = mLevel;
  mLevel = aLevel;
  return NS_OK;
}

NS_IMETHODIMP flockLoggingService::AddObserver(flockILoggingObserver *aObserver)
{
  NS_ENSURE_ARG_POINTER(aObserver);
  mObservers.AppendObject(aObserver);
  return NS_OK;
}

NS_IMETHODIMP flockLoggingService::RemoveObserver(flockILoggingObserver *aObserver)
{
  NS_ENSURE_ARG_POINTER(aObserver);
  mObservers.RemoveObject(aObserver);
  return NS_OK;
}

NS_IMETHODIMP flockLoggingService::Observe(nsISupports *aSubject,
                                           const char *aTopic,
                                           const PRUnichar *data)
{
  if (!strcmp(aTopic, "profile-after-change")) {
    Setup();
  } else if (!strcmp(aTopic, "profile-before-change")) {
    Shutdown();
  } else if (!strcmp(aTopic, "nsPref:changed")) {
    PrefsChanged(NS_ConvertUTF16toUTF8(data).get());
  }

  nsAutoString message;
  message.AssignLiteral("observed notification: ");
  message += NS_ConvertUTF8toUTF16(aTopic);

  Log(LEVEL_DEBUG, NO_OVERRIDE_LEVEL, NS_LITERAL_STRING("logger"), message);
  return NS_OK;
}

NS_IMPL_ISUPPORTS1(flockLogger, flockILogger)

flockLogger::flockLogger()
  : mOverrideLevel(NO_OVERRIDE_LEVEL)
{
}

flockLogger::~flockLogger()
{
}

nsresult flockLogger::Init()
{
  if (!gLoggingService) {
    flockLoggingService* service = flockLoggingService::GetSingleton();
    if (!service) {
      return NS_ERROR_UNEXPECTED;
    }
  }

  return NS_OK;
}

NS_IMETHODIMP flockLogger::Init(const nsAString & aContext)
{
  mContext = aContext;

  nsCAutoString prefLoggerLevel(PREF_LOGGER_LEVEL ".");
  prefLoggerLevel += NS_ConvertUTF16toUTF8(aContext);

  nsresult rv = NS_OK;
  nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
  if (NS_SUCCEEDED(rv)) {
    rv = prefBranch->GetIntPref(prefLoggerLevel.get(), &mOverrideLevel);
  }

  if (NS_FAILED(rv)) {
    mOverrideLevel = NO_OVERRIDE_LEVEL;
  }

  return NS_OK;
}

NS_IMETHODIMP flockLogger::Debug(const nsAString & aMessage)
{
  return gLoggingService->Log(flockILoggingService::LEVEL_DEBUG,
                              mOverrideLevel, mContext, aMessage);
}

NS_IMETHODIMP flockLogger::Info(const nsAString & aMessage)
{
  return gLoggingService->Log(flockILoggingService::LEVEL_INFO,
                              mOverrideLevel, mContext, aMessage);
}

NS_IMETHODIMP flockLogger::Warn(const nsAString & aMessage)
{
  return gLoggingService->Log(flockILoggingService::LEVEL_WARN,
                              mOverrideLevel, mContext, aMessage);
}

NS_IMETHODIMP flockLogger::Error(const nsAString & aMessage)
{
  return gLoggingService->Log(flockILoggingService::LEVEL_ERROR,
                              mOverrideLevel, mContext, aMessage);
}

NS_IMETHODIMP flockLogger::Fatal(const nsAString & aMessage)
{
  return gLoggingService->Log(flockILoggingService::LEVEL_FATAL,
                              mOverrideLevel, mContext, aMessage);
}
