/*---------------------------------------------------------------------------*
 *  plog.c  *
 *                                                                           *
 *  Copyright 2007, 2008 Nuance Communciations, Inc.                               *
 *                                                                           *
 *  Licensed under the Apache License, Version 2.0 (the 'License');          *
 *  you may not use this file except in compliance with the License.         *
 *                                                                           *
 *  You may obtain a copy of the License at                                  *
 *      http://www.apache.org/licenses/LICENSE-2.0                           *
 *                                                                           *
 *  Unless required by applicable law or agreed to in writing, software      *
 *  distributed under the License is distributed on an 'AS IS' BASIS,        *
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 
 *  See the License for the specific language governing permissions and      *
 *  limitations under the License.                                           *
 *                                                                           *
 *---------------------------------------------------------------------------*/


#include <stdio.h>
#include <stdarg.h>
#include "PFileSystem.h"
#include "ptypes.h"
#include "plog.h"
#include "pmemory.h"
#include "pstdio.h"
#include "ptimestamp.h"
#include "passert.h"
#ifdef USE_STACKTRACE
#include "PStackTrace.h"
#endif

#ifdef USE_THREAD
#include "ptrd.h"
#include "pmutex.h"
#endif


#if defined (ANDROID)
#if defined (HAVE_ANDROID_OS)
#define LOG_TAG "Srec" 
#include <utils/Log.h>
#endif
#endif

#include "phashtable.h"

#define MTAG __FILE__

#define FILTER_MSG_1		"ESR_BUFFER_OVERFLOW"
#define FILTER_MSG_1_SIZE	( sizeof ( FILTER_MSG_1 ) - 1 )

#define FILTER_MSG_2		"ESR_NO_MATCH_ERROR"
#define FILTER_MSG_2_SIZE	( sizeof ( FILTER_MSG_2 ) - 1 )

static unsigned int GlogLevel = 0;
static PLogger *Glogger = NULL;
static LOG_OUTPUT_FORMAT GlogFormat = LOG_OUTPUT_FORMAT_MODULE_NAME |
                                      LOG_OUTPUT_FORMAT_DATE_TIME;
/**
 * Used to detect endless recursion where the PLog module calls itself.
 */
static ESR_BOOL locked = ESR_FALSE;
#ifdef USE_THREAD

static PtrdMutex* Gmutex = NULL;
#endif

typedef struct FileLogger_t
{
  PLogger base;
  PFile* fp;
}
FileLogger;

/**
 * Prints and formats a message to the log.
 *
 * @param self the PLogger.
 *
 * @param format the format string specifying the next arguments (a la
 * printf).
 *
 * @param args variable argument list.
 *
 * @return The number of bytes written to the PLogger or -1 if an error
 * occurs.
 */
static ESR_ReturnCode FileLoggerPrintf(PLogger *self, const LCHAR *format, ...)
{
  FileLogger *p = STATIC_CAST(self, FileLogger, base);
  ESR_ReturnCode rc;
  va_list args;
  
  va_start(args, format);
  rc = pvfprintf(p->fp, format, args);
  va_end(args);
  return rc;
}

static ESR_ReturnCode FileLoggerFlush(PLogger *self)
{
  FileLogger *p = STATIC_CAST(self, FileLogger, base);
  return pfflush(p->fp) == 0 ? ESR_SUCCESS : ESR_FATAL_ERROR;
}


/**
 * Destroys the logger.  This function is responsible to deallocate any
 * resources used by the logger.  In particular, if buffering is internally
 * used, it needs to flush the buffer.
 */
static void FileLoggerDestroy(PLogger *self)
{
  FileLogger *p = STATIC_CAST(self, FileLogger, base);
  pfflush(p->fp);
  
  if (p->fp != PSTDERR && p->fp != PSTDOUT)
    pfclose(p->fp);
  FREE(p);
}

static ESR_ReturnCode createPFileLogger(PFile* fp, PLogger** logger)
{
  FileLogger* fileLogger;
  
  if (fp == NULL)
    return ESR_INVALID_ARGUMENT;
  fileLogger = NEW(FileLogger, MTAG);
  if (fileLogger == NULL)
    return ESR_OUT_OF_MEMORY;
    
  fileLogger->base.printf = FileLoggerPrintf;
  fileLogger->base.flush = FileLoggerFlush;
  fileLogger->base.destroy = FileLoggerDestroy;
  fileLogger->fp = fp;
  
  *logger = &fileLogger->base;
  return ESR_SUCCESS;
}

/**
 * Initializes the LOG library.  This function must be called before any
 * logging can take place.
 *
 * @param logger The logger to be used to output the messages.  If NULL, then
 * logging goes to PSTDERR.  @param logLevel The level of logging requested.
 *
 * @return ESR_SUCCESS if success, anything else if an error occurs.
 *
 */
ESR_ReturnCode PLogInit(PLogger *logger, unsigned int logLevel)
{
  ESR_ReturnCode rc = ESR_SUCCESS;
  
  if (Glogger != NULL)
    return ESR_INVALID_STATE;
    
  GlogLevel = logLevel;
  
#ifdef USE_THREAD
  if ((rc = PtrdMutexCreate(&Gmutex)) != ESR_SUCCESS)
    return rc;
#endif
    
  if (logger != NULL)
    Glogger = logger;
  else
  {
    rc = createPFileLogger(PSTDERR, &Glogger);
    if (rc != ESR_SUCCESS)
      goto CLEANUP;
  }
  
  return rc;
CLEANUP:
#ifdef USE_THREAD
  if (Gmutex != NULL)
  {
    PtrdMutexDestroy(Gmutex);
    Gmutex = NULL;
  }
#endif
  return rc;
}

ESR_ReturnCode PLogIsInitialized(ESR_BOOL* isInit)
{
  if (isInit == NULL)
    return ESR_INVALID_STATE;
  *isInit = Glogger != NULL;
  return ESR_SUCCESS;
}

ESR_ReturnCode PLogIsLocked(ESR_BOOL* isLocked)
{
  if (isLocked == NULL)
    return ESR_INVALID_STATE;
  *isLocked = locked;
  return ESR_SUCCESS;
}

/**
 * Shutdowns the LOG library.  Once this function is called, no logging activity can be performed.
 * Also, the logger that was given to pLogInit is destroyed.
 *
 * @return ESR_SUCCESS if success, anything else if an error occurs.
 *
 */
ESR_ReturnCode PLogShutdown()
{
  ESR_ReturnCode rc = ESR_SUCCESS;
  
  if (Glogger == NULL)
    return ESR_INVALID_STATE;
    
#ifdef USE_THREAD
  if ((rc = PtrdMutexDestroy(Gmutex)) != ESR_SUCCESS)
    return rc;
  Gmutex = NULL;
#endif
  
  if (Glogger->flush != NULL)
    Glogger->flush(Glogger);
  Glogger->destroy(Glogger);
  Glogger = NULL;
  return rc;
}

ESR_ReturnCode PLogGetLevel(unsigned int *logLevel)
{
  if (Glogger == NULL)
    return ESR_INVALID_STATE;
  if (logLevel == NULL)
    return ESR_INVALID_ARGUMENT;
    
  *logLevel = GlogLevel;
  return ESR_SUCCESS;
}

ESR_ReturnCode PLogSetLevel(unsigned int logLevel)
{
  if (Glogger == NULL)
    return ESR_INVALID_STATE;
    
  GlogLevel = logLevel;
  return ESR_SUCCESS;
}

#define TIME_BUF_SIZE 24
#define TIME_FORMAT L("%Y/%m/%d %H:%M:%S")
#define PLOG_PANIC(x, rc) \
  do \
  { \
    { \
      pfprintf(PSTDERR, L("[%s:%d] %s failed with %s\n"), __FILE__, __LINE__, x, ESR_rc2str(rc)); \
      pfflush(PSTDERR); \
    } \
  } while (0)

static ESR_ReturnCode logIt(const LCHAR *format, va_list args, ESR_BOOL showStackTrace)
{
  ESR_ReturnCode rc = ESR_SUCCESS;
  ESR_ReturnCode flushRC = ESR_SUCCESS;
#ifdef USE_STACKTRACE
#define BUFFER_SIZE P_MAX_STACKTRACE + 2000
#else
#define BUFFER_SIZE 2000
#endif
  LCHAR buffer[BUFFER_SIZE] = L("");

  // TODO: Remove once logging subsystem supports "warn" level
  if (strstr(format, "ESR_BUFFER_OVERFLOW")==format)
    return ESR_SUCCESS;
  
#ifdef USE_STACKTRACE
  if (Glogger == NULL)
  {
    /*
     * There are three possible scenerios for why logging would occur although the PLog module
     * is uninitialized:
     *
     * 1) The code fails before PLog is initialized (perhaps in other portable components)
     * 2) The user forgets to initialize the PLog module
     * 3) The code fails after PLog is uninitialized (on shutdown)
     *
     * We do our best by logging any errors but this might result in the memory leak of
     * the PStackTrace module in case 3.
     */
    rc = PStackTraceCreate();
    if (rc != ESR_SUCCESS)
    {
      PLOG_PANIC(L("PStackTraceCreate"), rc);
      goto CLEANUP;
    }
  }
  else
  {
#ifdef USE_THREAD
    rc = PtrdMutexLock(Gmutex);
    if (rc != ESR_SUCCESS)
      return rc;
#endif
  }
  if (locked)
    return ESR_INVALID_STATE;
  locked = ESR_TRUE;
  
  if (GlogFormat & LOG_OUTPUT_FORMAT_DATE_TIME)
  {
    PTimeStamp now;
    struct tm* loctime;
    LCHAR timeStr[TIME_BUF_SIZE];
    size_t timeStrSize;
    
    PTimeStampSet(&now);
    loctime = localtime(&now.secs);
    timeStrSize = LSTRFTIME(timeStr, TIME_BUF_SIZE, TIME_FORMAT, loctime);
    passert(timeStrSize == (TIME_BUF_SIZE - 5));
    psprintf(timeStr + (TIME_BUF_SIZE - 5), ".%03hu", now.msecs);
    
    psprintf(buffer + LSTRLEN(buffer), L("%s|"), timeStr);
    passert(LSTRLEN(buffer) < BUFFER_SIZE);
  }
  
  if (GlogFormat & LOG_OUTPUT_FORMAT_THREAD_ID)
  {
    rc = psprintf(buffer + LSTRLEN(buffer), L("trd=%u|"), PtrdGetCurrentThreadId());
    passert(LSTRLEN(buffer) < BUFFER_SIZE);
  }
  
  if (GlogFormat & LOG_OUTPUT_FORMAT_MODULE_NAME && showStackTrace)
  {
    size_t len = P_MAX_STACKTRACE;
    LCHAR text[P_MAX_STACKTRACE];
    LCHAR* index;
    size_t i;
    
    rc = PStackTraceGetValue((LCHAR*) & text, &len);
    if (rc == ESR_SUCCESS)
    {
      for (i = 0; i < 2; ++i)
      {
        rc = PStackTracePopLevel((LCHAR*) & text);
        if (rc != ESR_SUCCESS)
        {
          PLOG_PANIC(L("PStackTracePopLevel"), rc);
          goto CLEANUP;
        }
      }
      index = text;
      while (index)
      {
        index = LSTRSTR(index, L(" at\n"));
        if (index != NULL)
        {
          *(index + 1) = L('<');
          *(index + 2) = L('-');
          *(index + 3) = L(' ');
        }
      }
    }
    else if (rc == ESR_NOT_SUPPORTED)
      LSTRCPY(text, L(""));
    else if (rc != ESR_SUCCESS)
    {
      PLOG_PANIC(L("PStackTraceGetValue"), rc);
      goto CLEANUP;
    }
    rc = psprintf(buffer + LSTRLEN(buffer), L("Module=%s|"), text);
    passert(LSTRLEN(buffer) < BUFFER_SIZE);
  }
  
  pvsprintf(buffer + LSTRLEN(buffer), format, args);
#else
  pvsprintf(buffer + LSTRLEN(buffer), format, args);
#endif
  passert(LSTRLEN(buffer) < BUFFER_SIZE);
  
  psprintf(buffer + LSTRLEN(buffer), L("\n"));
  passert(LSTRLEN(buffer) < BUFFER_SIZE);
  
  if (Glogger != NULL)
  {
    rc = Glogger->printf(Glogger, L("%s"), buffer);
    if (rc != ESR_SUCCESS)
      goto CLEANUP;
    flushRC = Glogger->flush(Glogger);
  }
  else
  {
    /* We need to log but the logging module is disabled or is locked so we output to stderr instead */
    {
      pfprintf(PSTDERR, L("%s"), buffer);
      pfflush(PSTDERR);
    }
  }
  locked = ESR_FALSE;
#ifdef USE_THREAD
  PtrdMutexUnlock(Gmutex);
#endif
  return flushRC;
CLEANUP:
  if (Glogger != NULL && Glogger->flush != NULL)
    flushRC = Glogger->flush(Glogger);
  locked = ESR_FALSE;
#ifdef USE_THREAD
  PtrdMutexUnlock(Gmutex);
#endif
  return rc != ESR_SUCCESS ? rc : flushRC;
}

/**
 * Conditionally PLogs a message.  The message is logged only if module is enabled.
 *
 * @param msg The message format specification (ala printf).
 * @return ESR_SUCCESS if success, anything else if an error occurs.
 */
ESR_ReturnCode PLogMessage(const char* msg, ...)
{
  va_list args;
  ESR_ReturnCode rc;
#if USE_STACKTRACE
  size_t depth;
#endif
  
#if defined (ANDROID)
#if defined (HAVE_ANDROID_OS)
  return ( ESR_SUCCESS );/* Get rid of this for phone device */
#endif
#endif

  if (Glogger == NULL)
    return ESR_INVALID_STATE;
#ifdef USE_STACKTRACE
  return ESR_SUCCESS;
  rc = PStackTraceGetDepth(&depth);
  
  if (rc == ESR_NOT_SUPPORTED)
  {
    /* Debugging symbols are missing */
    return ESR_SUCCESS;
  }
  else if (rc != ESR_SUCCESS)
  {
    pfprintf(PSTDERR, L("PStackTraceGetDepth"), ESR_rc2str(rc));
    goto CLEANUP;
  }
  
  /* Remove PLogMessage() from depth */
  --depth;
  if (GlogLevel < depth)
    return ESR_SUCCESS;
#endif
    
  va_start(args, msg);
  rc = logIt(msg, args, ESR_FALSE);
  va_end(args);
  return rc;
#if USE_STACKTRACE
CLEANUP:
  return rc;
#endif
}


/**
 * Unconditionally logs an error message.
 *
 * @param msg The message format specification (ala printf).
 * @return ESR_SUCCESS if success, anything else if an error occurs.
 */
ESR_ReturnCode PLogError(const char* msg, ...)
{
  va_list args;
  ESR_ReturnCode rc;
#if defined (ANDROID)
#if defined (HAVE_ANDROID_OS)
  char log_text [2048];
#endif
#endif

  va_start(args, msg);
#if defined (ANDROID)
#if defined (HAVE_ANDROID_OS)
  pvsprintf ( log_text, msg, args);
/* We need to disable some error messages because they are frequently not
 * errors but due to sloppy use of some functions. This prevents us from
 * getting flooded with bad error messages. SteveR
 */
  if ( ( strncmp ( log_text, FILTER_MSG_1, FILTER_MSG_1_SIZE ) != 0 ) &&
    ( strncmp ( log_text, FILTER_MSG_2, FILTER_MSG_2_SIZE ) != 0 ) )
  {
    LOGE ("%s", log_text );
  }
  rc = 0;
#else
  rc = logIt(msg, args, ESR_TRUE);
#endif
#else
  rc = logIt(msg, args, ESR_TRUE);
#endif
  va_end(args);
  
  return rc;
}



ESR_ReturnCode PLogCreateFileLogger(PFile* file, PLogger **logger)
{
  if (logger == NULL || file == NULL)
    return ESR_INVALID_ARGUMENT;
    
  return createPFileLogger(file, logger);
}

/**
 * Creates a logger that logs to a circular file.
 *
 * @param filename The name of the file to be created.
 * @param maxsize The maximum number of bytes that the file may have.
 * @param logger logger handle receiving the created logger.
 */
ESR_ReturnCode PLogCreateCircularFileLogger(const LCHAR *filename,
    unsigned int maxsize,
    PLogger **logger)
{
  return ESR_NOT_SUPPORTED;
}


ESR_ReturnCode PLogSetFormat(LOG_OUTPUT_FORMAT format)
{
  GlogFormat = format;
  return ESR_SUCCESS;
}
