/*
 * 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 "flockLoggingWriters.h"

#include "nsNetUtil.h"
#include "nsIObserverService.h"
#include "nsServiceManagerUtils.h"
#include "nsStringAPI.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsDirectoryServiceUtils.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch2.h"

#include "prtime.h"


#define PREF_LOGGER_FILE_ENABLED "flock.service.logger.file.enabled"

#define LOG_FILE_NAME "log.txt"

#ifdef DEBUG
#define LOG_FILE_ENABLED_BY_DEFAULT PR_TRUE
#else
#define LOG_FILE_ENABLED_BY_DEFAULT PR_FALSE
#endif

static nsresult GenerateLogMessage(PRTime aTime, PRInt32 aLevel,
                                   const nsAString & aContext,
                                   const nsAString & aMessage,
                                   PRBool aLogFullDate,
                                   nsCString& aLogMessage)
{
  static const char *levels[] = {
    "all", "debug", "info", "warn", "error", "fatal"
  };

  if (aLevel < 0 || aLevel > PRInt32(NS_ARRAY_LENGTH(levels))) {
    return NS_ERROR_INVALID_ARG;
  }

  nsCAutoString date;

  PRExplodedTime t;
  PR_ExplodeTime(aTime, PR_LocalTimeParameters, &t);

  const char *format = aLogFullDate ? "%Y-%m-%d %H:%M:%S." : "%H:%M:%S.";

  char buf[256];
  PR_FormatTimeUSEnglish(buf, sizeof(buf), format, &t);
  date.Append(buf);

  PRInt32 msec = t.tm_usec / 1000;
  for (PRInt32 digit = 100; digit > 1; digit /= 10) {
    date.Append(char('0' + (msec / digit)));
    msec %= digit;
  }
  date.Append(char('0' + msec));

  aLogMessage = "[";
  aLogMessage += date;
  aLogMessage += " flock:";
  aLogMessage += NS_ConvertUTF16toUTF8(aContext);
  aLogMessage += ":";
  aLogMessage += levels[aLevel];
  aLogMessage += "] ";
  aLogMessage += NS_ConvertUTF16toUTF8(aMessage);
  aLogMessage += "\n";

  return NS_OK;
}

NS_IMPL_ISUPPORTS1(flockLoggingConsoleWriter, flockILoggingObserver)

flockLoggingConsoleWriter::flockLoggingConsoleWriter()
{
}

flockLoggingConsoleWriter::~flockLoggingConsoleWriter()
{
}

NS_IMETHODIMP flockLoggingConsoleWriter::Emit(PRTime aTime, PRInt32 aLevel,
                                              const nsAString & aContext,
                                              const nsAString & aMessage)
{
  nsCAutoString msg;

  nsresult rv;
  rv = GenerateLogMessage(aTime, aLevel, aContext, aMessage, PR_FALSE, msg);
  NS_ENSURE_SUCCESS(rv, rv);
 
  fputs(msg.get(), stderr);

  return NS_OK;
}

NS_IMPL_ISUPPORTS2(flockLoggingFileWriter, flockILoggingObserver, nsIObserver)

flockLoggingFileWriter::flockLoggingFileWriter()
  : mInitialized(PR_FALSE)
{
}

flockLoggingFileWriter::~flockLoggingFileWriter()
{
}

nsresult flockLoggingFileWriter::Init()
{
  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 flockLoggingFileWriter::Setup()
{
  nsCOMPtr<nsIPrefBranch2> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
  if (prefBranch) {
    prefBranch->AddObserver(PREF_LOGGER_FILE_ENABLED, this, PR_FALSE);

    return PrefsChanged(nsnull);
  }

  return NS_OK;
}

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

  return CloseFile();
}

nsresult flockLoggingFileWriter::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_FILE_ENABLED)) {
    PRBool enabled;
    rv = prefBranch->GetBoolPref(PREF_LOGGER_FILE_ENABLED, &enabled);
    if (NS_FAILED(rv)) {
      enabled = LOG_FILE_ENABLED_BY_DEFAULT;
    }

    if (enabled) {
      return InitFile();
    } else {
      return CloseFile();
    }
  }

  return NS_OK;
}

nsresult flockLoggingFileWriter::InitFile()
{
  nsresult rv;

  if (mInitialized) {
    return NS_OK;
  }

  nsCOMPtr<nsIFile> logFile;
  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(logFile));
  if (NS_FAILED(rv)) {
    return rv;
  }

  logFile->AppendNative(NS_LITERAL_CSTRING(LOG_FILE_NAME));

  PRBool exists;
  rv = logFile->Exists(&exists);
  if (NS_FAILED(rv) || !exists) {
    rv = logFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  rv = NS_NewLocalFileOutputStream(getter_AddRefs(mLogStream), logFile,
                                   PR_WRONLY | PR_CREATE_FILE | PR_APPEND,
                                   0600, 0);
  NS_ENSURE_SUCCESS(rv, rv);

  mInitialized = PR_TRUE;

  return NS_OK;
}

nsresult flockLoggingFileWriter::CloseFile()
{
  if (!mInitialized) {
    return NS_OK;
  }

  mLogStream->Close();
  mLogStream = nsnull;

  mInitialized = PR_FALSE;

  return NS_OK;
}

NS_IMETHODIMP flockLoggingFileWriter::Emit(PRTime aTime, PRInt32 aLevel,
                                           const nsAString & aContext,
                                           const nsAString & aMessage)
{
  if (!mInitialized) {
    return NS_OK;
  }

  nsCAutoString msg;

  nsresult rv;
  rv = GenerateLogMessage(aTime, aLevel, aContext, aMessage, PR_TRUE, msg);
  NS_ENSURE_SUCCESS(rv, rv);

  PRUint32 dummy;
  return mLogStream->Write(msg.get(), msg.Length(), &dummy);
}

NS_IMETHODIMP flockLoggingFileWriter::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());
  }

  return NS_OK;
}
