/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "primpl.h"
#include "prenv.h"
#include "prprf.h"
#include <string.h>
#ifdef ANDROID
#  include <android/log.h>
#endif

/*
 * Lock used to lock the log.
 *
 * We can't define _PR_LOCK_LOG simply as PR_Lock because PR_Lock may
 * contain assertions.  We have to avoid assertions in _PR_LOCK_LOG
 * because PR_ASSERT calls PR_LogPrint, which in turn calls _PR_LOCK_LOG.
 * This can lead to infinite recursion.
 */
static PRLock* _pr_logLock;
#if defined(_PR_PTHREADS) || defined(_PR_BTHREADS)
#  define _PR_LOCK_LOG() PR_Lock(_pr_logLock);
#  define _PR_UNLOCK_LOG() PR_Unlock(_pr_logLock);
#elif defined(_PR_GLOBAL_THREADS_ONLY)
#  define _PR_LOCK_LOG() \
    {                    \
      _PR_LOCK_LOCK(_pr_logLock)
#  define _PR_UNLOCK_LOG()        \
    _PR_LOCK_UNLOCK(_pr_logLock); \
    }
#else

#  define _PR_LOCK_LOG()                                \
    {                                                   \
      PRIntn _is;                                       \
      PRThread* _me = _PR_MD_CURRENT_THREAD();          \
      if (!_PR_IS_NATIVE_THREAD(_me)) _PR_INTSOFF(_is); \
      _PR_LOCK_LOCK(_pr_logLock)

#  define _PR_UNLOCK_LOG()                           \
    _PR_LOCK_UNLOCK(_pr_logLock);                    \
    PR_ASSERT(_me == _PR_MD_CURRENT_THREAD());       \
    if (!_PR_IS_NATIVE_THREAD(_me)) _PR_INTSON(_is); \
    }

#endif

#if defined(XP_PC)
#  define strcasecmp stricmp
#endif

/*
 * On NT, we can't define _PUT_LOG as PR_Write or _PR_MD_WRITE,
 * because every asynchronous file io operation leads to a fiber context
 * switch.  So we define _PUT_LOG as fputs (from stdio.h).  A side
 * benefit is that fputs handles the LF->CRLF translation.  This
 * code can also be used on other platforms with file stream io.
 */
#if defined(WIN32)
#  define _PR_USE_STDIO_FOR_LOGGING
#endif

/*
** Coerce Win32 log output to use OutputDebugString() when
** NSPR_LOG_FILE is set to "WinDebug".
*/
#if defined(XP_PC)
#  define WIN32_DEBUG_FILE (FILE*)-2
#endif

#ifdef WINCE
static void OutputDebugStringA(const char* msg) {
  int len = MultiByteToWideChar(CP_ACP, 0, msg, -1, 0, 0);
  WCHAR* wMsg = (WCHAR*)PR_Malloc(len * sizeof(WCHAR));
  MultiByteToWideChar(CP_ACP, 0, msg, -1, wMsg, len);
  OutputDebugStringW(wMsg);
  PR_Free(wMsg);
}
#endif

/* Macros used to reduce #ifdef pollution */

#if defined(_PR_USE_STDIO_FOR_LOGGING) && defined(XP_PC)
#  define _PUT_LOG(fd, buf, nb)        \
    PR_BEGIN_MACRO                     \
    if (logFile == WIN32_DEBUG_FILE) { \
      char savebyte = buf[nb];         \
      buf[nb] = '\0';                  \
      OutputDebugStringA(buf);         \
      buf[nb] = savebyte;              \
    } else {                           \
      fwrite(buf, 1, nb, fd);          \
      fflush(fd);                      \
    }                                  \
    PR_END_MACRO
#elif defined(_PR_USE_STDIO_FOR_LOGGING)
#  define _PUT_LOG(fd, buf, nb) \
    {                           \
      fwrite(buf, 1, nb, fd);   \
      fflush(fd);               \
    }
#elif defined(ANDROID)
#  define _PUT_LOG(fd, buf, nb)                            \
    PR_BEGIN_MACRO                                         \
    if (fd == _pr_stderr) {                                \
      char savebyte = buf[nb];                             \
      buf[nb] = '\0';                                      \
      __android_log_write(ANDROID_LOG_INFO, "PRLog", buf); \
      buf[nb] = savebyte;                                  \
    } else {                                               \
      PR_Write(fd, buf, nb);                               \
    }                                                      \
    PR_END_MACRO
#elif defined(_PR_PTHREADS)
#  define _PUT_LOG(fd, buf, nb) PR_Write(fd, buf, nb)
#else
#  define _PUT_LOG(fd, buf, nb) _PR_MD_WRITE(fd, buf, nb)
#endif

/************************************************************************/

static PRLogModuleInfo* logModules;

static char* logBuf = NULL;
static char* logp;
static char* logEndp;
#ifdef _PR_USE_STDIO_FOR_LOGGING
static FILE* logFile = NULL;
#else
static PRFileDesc* logFile = 0;
#endif
static PRBool outputTimeStamp = PR_FALSE;
static PRBool appendToLog = PR_FALSE;

#define LINE_BUF_SIZE 512
#define DEFAULT_BUF_SIZE 16384

#ifdef _PR_NEED_STRCASECMP

/*
 * strcasecmp is defined in /usr/ucblib/libucb.a on some platforms
 * such as NCR.  Linking with both libc and libucb
 * may cause some problem, so I just provide our own implementation
 * of strcasecmp here.
 */

static const unsigned char uc[] = {
    '\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007', '\010',
    '\011', '\012', '\013', '\014', '\015', '\016', '\017', '\020', '\021',
    '\022', '\023', '\024', '\025', '\026', '\027', '\030', '\031', '\032',
    '\033', '\034', '\035', '\036', '\037', ' ',    '!',    '"',    '#',
    '$',    '%',    '&',    '\'',   '(',    ')',    '*',    '+',    ',',
    '-',    '.',    '/',    '0',    '1',    '2',    '3',    '4',    '5',
    '6',    '7',    '8',    '9',    ':',    ';',    '<',    '=',    '>',
    '?',    '@',    'A',    'B',    'C',    'D',    'E',    'F',    'G',
    'H',    'I',    'J',    'K',    'L',    'M',    'N',    'O',    'P',
    'Q',    'R',    'S',    'T',    'U',    'V',    'W',    'X',    'Y',
    'Z',    '[',    '\\',   ']',    '^',    '_',    '`',    'A',    'B',
    'C',    'D',    'E',    'F',    'G',    'H',    'I',    'J',    'K',
    'L',    'M',    'N',    'O',    'P',    'Q',    'R',    'S',    'T',
    'U',    'V',    'W',    'X',    'Y',    'Z',    '{',    '|',    '}',
    '~',    '\177'};

PRIntn strcasecmp(const char* a, const char* b) {
  const unsigned char* ua = (const unsigned char*)a;
  const unsigned char* ub = (const unsigned char*)b;

  if (((const char*)0 == a) || (const char*)0 == b) {
    return (PRIntn)(a - b);
  }

  while ((uc[*ua] == uc[*ub]) && ('\0' != *a)) {
    a++;
    ua++;
    ub++;
  }

  return (PRIntn)(uc[*ua] - uc[*ub]);
}

#endif /* _PR_NEED_STRCASECMP */

void _PR_InitLog(void) {
  char* ev;

  _pr_logLock = PR_NewLock();

  ev = PR_GetEnv("NSPR_LOG_MODULES");
  if (ev && ev[0]) {
    char module[64]; /* Security-Critical: If you change this
                      * size, you must also change the sscanf
                      * format string to be size-1.
                      */
    PRBool isSync = PR_FALSE;
    PRIntn evlen = strlen(ev), pos = 0;
    PRInt32 bufSize = DEFAULT_BUF_SIZE;
    while (pos < evlen) {
      PRIntn level = 1, count = 0, delta = 0;
      count = sscanf(
          &ev[pos],
          "%63[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
          "]%n:%d%n",
          module, &delta, &level, &delta);
      pos += delta;
      if (count == 0) {
        break;
      }

      /*
      ** If count == 2, then we got module and level. If count
      ** == 1, then level defaults to 1 (module enabled).
      */
      if (strcasecmp(module, "sync") == 0) {
        isSync = PR_TRUE;
      } else if (strcasecmp(module, "bufsize") == 0) {
        if (level >= LINE_BUF_SIZE) {
          bufSize = level;
        }
      } else if (strcasecmp(module, "timestamp") == 0) {
        outputTimeStamp = PR_TRUE;
      } else if (strcasecmp(module, "append") == 0) {
        appendToLog = PR_TRUE;
      } else {
        PRLogModuleInfo* lm = logModules;
        PRBool skip_modcheck =
            (0 == strcasecmp(module, "all")) ? PR_TRUE : PR_FALSE;

        while (lm != NULL) {
          if (skip_modcheck) {
            lm->level = (PRLogModuleLevel)level;
          } else if (strcasecmp(module, lm->name) == 0) {
            lm->level = (PRLogModuleLevel)level;
            break;
          }
          lm = lm->next;
        }
      }
      /*found:*/
      count = sscanf(&ev[pos], " , %n", &delta);
      pos += delta;
      if (count == EOF) {
        break;
      }
    }
    PR_SetLogBuffering(isSync ? 0 : bufSize);

    ev = PR_GetEnvSecure("NSPR_LOG_FILE");
    if (ev && ev[0]) {
      if (!PR_SetLogFile(ev)) {
#ifdef XP_PC
        char* str = PR_smprintf("Unable to create nspr log file '%s'\n", ev);
        if (str) {
          OutputDebugStringA(str);
          PR_smprintf_free(str);
        }
#else
        fprintf(stderr, "Unable to create nspr log file '%s'\n", ev);
#endif
      }
    } else {
#ifdef _PR_USE_STDIO_FOR_LOGGING
      logFile = stderr;
#else
      logFile = _pr_stderr;
#endif
    }
  }
}

void _PR_LogCleanup(void) {
  PRLogModuleInfo* lm = logModules;

  PR_LogFlush();

#ifdef _PR_USE_STDIO_FOR_LOGGING
  if (logFile && logFile != stdout && logFile != stderr
#  ifdef XP_PC
      && logFile != WIN32_DEBUG_FILE
#  endif
  ) {
    fclose(logFile);
  }
#else
  if (logFile && logFile != _pr_stdout && logFile != _pr_stderr) {
    PR_Close(logFile);
  }
#endif
  logFile = NULL;

  if (logBuf) {
    PR_DELETE(logBuf);
  }

  while (lm != NULL) {
    PRLogModuleInfo* next = lm->next;
    free((/*const*/ char*)lm->name);
    PR_Free(lm);
    lm = next;
  }
  logModules = NULL;

  if (_pr_logLock) {
    PR_DestroyLock(_pr_logLock);
    _pr_logLock = NULL;
  }
}

static void _PR_SetLogModuleLevel(PRLogModuleInfo* lm) {
  char* ev;

  ev = PR_GetEnv("NSPR_LOG_MODULES");
  if (ev && ev[0]) {
    char module[64]; /* Security-Critical: If you change this
                      * size, you must also change the sscanf
                      * format string to be size-1.
                      */
    PRIntn evlen = strlen(ev), pos = 0;
    while (pos < evlen) {
      PRIntn level = 1, count = 0, delta = 0;

      count = sscanf(
          &ev[pos],
          "%63[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"
          "]%n:%d%n",
          module, &delta, &level, &delta);
      pos += delta;
      if (count == 0) {
        break;
      }

      /*
      ** If count == 2, then we got module and level. If count
      ** == 1, then level defaults to 1 (module enabled).
      */
      if (lm != NULL) {
        if ((strcasecmp(module, "all") == 0) ||
            (strcasecmp(module, lm->name) == 0)) {
          lm->level = (PRLogModuleLevel)level;
        }
      }
      count = sscanf(&ev[pos], " , %n", &delta);
      pos += delta;
      if (count == EOF) {
        break;
      }
    }
  }
} /* end _PR_SetLogModuleLevel() */

PR_IMPLEMENT(PRLogModuleInfo*) PR_NewLogModule(const char* name) {
  PRLogModuleInfo* lm;

  if (!_pr_initialized) {
    _PR_ImplicitInitialization();
  }

  lm = PR_NEWZAP(PRLogModuleInfo);
  if (lm) {
    lm->name = strdup(name);
    lm->level = PR_LOG_NONE;
    lm->next = logModules;
    logModules = lm;
    _PR_SetLogModuleLevel(lm);
  }
  return lm;
}

PR_IMPLEMENT(PRBool) PR_SetLogFile(const char* file) {
#ifdef _PR_USE_STDIO_FOR_LOGGING
  FILE* newLogFile;

#  ifdef XP_PC
  if (strcmp(file, "WinDebug") == 0) {
    newLogFile = WIN32_DEBUG_FILE;
  } else
#  endif
  {
    const char* mode = appendToLog ? "a" : "w";
    newLogFile = fopen(file, mode);
    if (!newLogFile) {
      return PR_FALSE;
    }

#  ifndef WINCE /* _IONBF does not exist in the Windows Mobile 6 SDK. */
    /* We do buffering ourselves. */
    setvbuf(newLogFile, NULL, _IONBF, 0);
#  endif
  }
  if (logFile && logFile != stdout && logFile != stderr
#  ifdef XP_PC
      && logFile != WIN32_DEBUG_FILE
#  endif
  ) {
    fclose(logFile);
  }
  logFile = newLogFile;
  return PR_TRUE;
#else
  PRFileDesc* newLogFile;
  PRIntn flags = PR_WRONLY | PR_CREATE_FILE;
  if (appendToLog) {
    flags |= PR_APPEND;
  } else {
    flags |= PR_TRUNCATE;
  }

  newLogFile = PR_Open(file, flags, 0666);
  if (newLogFile) {
    if (logFile && logFile != _pr_stdout && logFile != _pr_stderr) {
      PR_Close(logFile);
    }
    logFile = newLogFile;
  }
  return (PRBool)(newLogFile != 0);
#endif /* _PR_USE_STDIO_FOR_LOGGING */
}

PR_IMPLEMENT(void) PR_SetLogBuffering(PRIntn buffer_size) {
  PR_LogFlush();

  if (logBuf) {
    PR_DELETE(logBuf);
  }

  if (buffer_size >= LINE_BUF_SIZE) {
    logp = logBuf = (char*)PR_MALLOC(buffer_size);
    logEndp = logp + buffer_size;
  }
}

PR_IMPLEMENT(void) PR_LogPrint(const char* fmt, ...) {
  va_list ap;
  char line[LINE_BUF_SIZE];
  char* line_long = NULL;
  PRUint32 nb_tid = 0, nb;
  PRThread* me;
  PRExplodedTime now;

  if (!_pr_initialized) {
    _PR_ImplicitInitialization();
  }

  if (!logFile) {
    return;
  }

  if (outputTimeStamp) {
    PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now);
    nb_tid = PR_snprintf(line, sizeof(line) - 1,
                         "%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - ",
                         now.tm_year, now.tm_month + 1, now.tm_mday,
                         now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec);
  }

  me = PR_GetCurrentThread();
  nb_tid += PR_snprintf(line + nb_tid, sizeof(line) - nb_tid - 1, "%ld[%p]: ",
#if defined(_PR_BTHREADS)
                        me, me);
#else
                        me ? me->id : 0L, me);
#endif

  va_start(ap, fmt);
  nb = nb_tid + PR_vsnprintf(line + nb_tid, sizeof(line) - nb_tid - 1, fmt, ap);
  va_end(ap);

  /*
   * Check if we might have run out of buffer space (in case we have a
   * long line), and malloc a buffer just this once.
   */
  if (nb == sizeof(line) - 2) {
    va_start(ap, fmt);
    line_long = PR_vsmprintf(fmt, ap);
    va_end(ap);
    /* If this failed, we'll fall back to writing the truncated line. */
  }

  if (line_long) {
    nb = strlen(line_long);
    _PR_LOCK_LOG();
    if (logBuf != 0) {
      _PUT_LOG(logFile, logBuf, logp - logBuf);
      logp = logBuf;
    }
    /*
     * Write out the thread id (with an optional timestamp) and the
     * malloc'ed buffer.
     */
    _PUT_LOG(logFile, line, nb_tid);
    _PUT_LOG(logFile, line_long, nb);
    /* Ensure there is a trailing newline. */
    if (!nb || (line_long[nb - 1] != '\n')) {
      char eol[2];
      eol[0] = '\n';
      eol[1] = '\0';
      _PUT_LOG(logFile, eol, 1);
    }
    _PR_UNLOCK_LOG();
    PR_smprintf_free(line_long);
  } else {
    /* Ensure there is a trailing newline. */
    if (nb && (line[nb - 1] != '\n')) {
      line[nb++] = '\n';
      line[nb] = '\0';
    }
    _PR_LOCK_LOG();
    if (logBuf == 0) {
      _PUT_LOG(logFile, line, nb);
    } else {
      /* If nb can't fit into logBuf, write out logBuf first. */
      if (logp + nb > logEndp) {
        _PUT_LOG(logFile, logBuf, logp - logBuf);
        logp = logBuf;
      }
      /* nb is guaranteed to fit into logBuf. */
      memcpy(logp, line, nb);
      logp += nb;
    }
    _PR_UNLOCK_LOG();
  }
  PR_LogFlush();
}

PR_IMPLEMENT(void) PR_LogFlush(void) {
  if (logBuf && logFile) {
    _PR_LOCK_LOG();
    if (logp > logBuf) {
      _PUT_LOG(logFile, logBuf, logp - logBuf);
      logp = logBuf;
    }
    _PR_UNLOCK_LOG();
  }
}

PR_IMPLEMENT(void) PR_Abort(void) {
  PR_LogPrint("Aborting");
#ifdef ANDROID
  __android_log_write(ANDROID_LOG_ERROR, "PRLog", "Aborting");
#endif
  abort();
}

PR_IMPLEMENT(void) PR_Assert(const char* s, const char* file, PRIntn ln) {
  PR_LogPrint("Assertion failure: %s, at %s:%d\n", s, file, ln);
  fprintf(stderr, "Assertion failure: %s, at %s:%d\n", s, file, ln);
  fflush(stderr);
#ifdef WIN32
  DebugBreak();
#elif defined(ANDROID)
  __android_log_assert(NULL, "PRLog", "Assertion failure: %s, at %s:%d\n", s,
                       file, ln);
#endif
  abort();
}
