/**
   r_log.c


   Copyright (C) 2001, RTFM, Inc.
   Copyright (C) 2006, Network Resonance, Inc.
   All Rights Reserved

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:

   1. Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
   2. Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
   3. Neither the name of Network Resonance, Inc. nor the name of any
      contributors to this software may be used to endorse or promote
      products derived from this software without specific prior written
      permission.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   POSSIBILITY OF SUCH DAMAGE.


   ekr@rtfm.com  Mon Dec  3 15:24:38 2001
 */

#ifdef LINUX
#define _BSD_SOURCE
#define _DEFAULT_SOURCE
#endif

#include "r_log.h"
#include "hex.h"

#include <string.h>
#include <errno.h>
#ifndef _MSC_VER
#include <strings.h>
#include <syslog.h>
#endif
#include <registry.h>
#include <time.h>

#include <csi_platform.h>


#define LOGGING_DEFAULT_LEVEL  5

int NR_LOG_LOGGING = 0;

static const char *log_level_strings[]={
     "EMERG",
     "ALERT",
     "CRIT",
     "ERR",
     "WARNING",
     "NOTICE",
     "INFO",
     "DEBUG"
};

static const char *log_level_reg_strings[]={
     "emergency",
     "alert",
     "critical",
     "error",
     "warning",
     "notice",
     "info",
     "debug"
};

#define LOGGING_REG_PREFIX "logging"

#define MAX_ERROR_STRING_SIZE   512

#define R_LOG_INITTED1   1
#define R_LOG_INITTED2   2

#define LOG_NUM_DESTINATIONS 3

typedef struct log_type_ {
     char *facility_name;
     int level[LOG_NUM_DESTINATIONS];
     NR_registry dest_facility_key[LOG_NUM_DESTINATIONS];
} log_type;

#define MAX_LOG_TYPES 16

static log_type log_types[MAX_LOG_TYPES];
static int log_type_ct;


typedef struct log_destination_ {
     const char *dest_name;
     int enabled;
     int default_level;
     r_dest_vlog *dest_vlog;
} log_destination;


#define LOG_LEVEL_UNDEFINED        -1
#define LOG_LEVEL_NONE             -2
#define LOG_LEVEL_USE_DEST_DEFAULT -3

static int stderr_vlog(int facility,int level,const char *format,va_list ap);
static int syslog_vlog(int facility,int level,const char *format,va_list ap);
static int noop_vlog(int facility,int level,const char *format,va_list ap);

static log_destination log_destinations[LOG_NUM_DESTINATIONS]={
     {
          "stderr",
          0,
          LOGGING_DEFAULT_LEVEL,
          stderr_vlog,
     },
     {
          "syslog",
#ifndef WIN32
          1,
#else
          0,
#endif
          LOGGING_DEFAULT_LEVEL,
          syslog_vlog,
     },
     {
          "extra",
          0,
          LOGGING_DEFAULT_LEVEL,
          noop_vlog,
     },
};

static int r_log_level=LOGGING_DEFAULT_LEVEL;
static int r_log_level_environment=0;
static int r_log_initted=0;
static int r_log_env_verbose=0;

static void r_log_facility_change_cb(void *cb_arg, char action, NR_registry_name name);
static void r_log_facility_delete_cb(void *cb_arg, char action, NR_registry_name name);
static void r_log_destination_change_cb(void *cb_arg, char action, NR_registry_name name);
static void r_log_default_level_change_cb(void *cb_arg, char action, NR_registry_name name);
static int r_log_get_default_level(void);
static int r_log_get_destinations(int usereg);
static int r_logging_dest(int dest_index, int facility, int level);
static int _r_log_init(int usereg);
static int r_log_get_reg_level(NR_registry_name name, int *level);

int r_log_register(const char *facility_name,int *log_facility)
  {
    int i,j;
    int level;
    int r,_status;
    char *buf=0;
    NR_registry dest_prefix, dest_facility_prefix;

    for(i=0;i<log_type_ct;i++){
      if(!strcmp(facility_name,log_types[i].facility_name)){
        *log_facility=i;
        return(0);
      }
    }

    if(log_type_ct==MAX_LOG_TYPES){
      ABORT(R_INTERNAL);
    }

    i=log_type_ct;

    /* Initial registration completed, increment log_type_ct */
    log_types[i].facility_name=strdup(facility_name);
    *log_facility=log_type_ct;
    log_type_ct++;

    for(j=0; j<LOG_NUM_DESTINATIONS; j++){
      log_types[i].level[j]=LOG_LEVEL_UNDEFINED;

      if(NR_reg_initted()){

        if((size_t)snprintf(dest_prefix,sizeof(NR_registry),
          "logging.%s.facility",log_destinations[j].dest_name)>=sizeof(NR_registry))
          ABORT(R_INTERNAL);

        if (r=NR_reg_make_registry(dest_prefix,facility_name,dest_facility_prefix))
          ABORT(r);

        if((size_t)snprintf(log_types[i].dest_facility_key[j],sizeof(NR_registry),
          "%s.level",dest_facility_prefix)>=sizeof(NR_registry))
          ABORT(R_INTERNAL);

        if(!r_log_get_reg_level(log_types[i].dest_facility_key[j],&level)){
          log_types[i].level[j]=level;
        }

        /* Set a callback for the facility's level */
        if(r=NR_reg_register_callback(log_types[i].dest_facility_key[j],
          NR_REG_CB_ACTION_ADD|NR_REG_CB_ACTION_CHANGE,
          r_log_facility_change_cb,(void *)&(log_types[i].level[j])))
          ABORT(r);
        if(r=NR_reg_register_callback(log_types[i].dest_facility_key[j],
          NR_REG_CB_ACTION_DELETE,
          r_log_facility_delete_cb,(void *)&(log_types[i].level[j])))
          ABORT(r);

      }
    }

    _status=0;
  abort:
    if(_status)
      free(buf);
    return(_status);
  }

static int r_log_get_reg_level(NR_registry_name name, int *out)
  {
    char level[32];
    int r,_status;
    int i;

    if(r=NR_reg_get_string(name,level,sizeof(level)))
      ABORT(r);

    if(!strcasecmp(level,"none")){
      *out=LOG_LEVEL_NONE;
      return(0);
    }

    for(i=0;i<=LOG_DEBUG;i++){
      if(!strcasecmp(level,log_level_reg_strings[i])){
        *out=(int)i;
        return(0);
      }
    }

    if(i>LOG_DEBUG){
      *out=LOG_LEVEL_UNDEFINED;
    }

    _status=0;
  abort:
    return(_status);
  }

/* Handle the case where a value changes */
static void r_log_facility_change_cb(void *cb_arg, char action, NR_registry_name name)
  {
    int *lt_level=(int *)cb_arg;
    int level;
    int r,_status;

    if(r=r_log_get_reg_level(name,&level))
      ABORT(r);

    *lt_level=level;

    _status=0;
  abort:
    (void)_status; // to avoid unused variable warning and still conform to
                   // pattern of using ABORT
    return;
  }

/* Handle the case where a value is deleted */
static void r_log_facility_delete_cb(void *cb_arg, char action, NR_registry_name name)
  {
    int *lt_level=(int *)cb_arg;

    *lt_level=LOG_LEVEL_UNDEFINED;
  }

int r_log(int facility,int level,const char *format,...)
  {
    va_list ap;

    va_start(ap,format);

    r_vlog(facility,level,format,ap);
    va_end(ap);

    return(0);
  }

int r_dump(int facility,int level,const char *name,const char *data,int len)
  {
    char *hex = 0;
    size_t unused;

    if(!r_logging(facility,level))
      return(0);

    hex=(char*)malloc((len*2)+1);
    if (!hex)
      return(R_FAILED);

    if (nr_nbin2hex((const UCHAR*)data, len, hex, len*2+1, &unused))
      strcpy(hex, "?");

    if(name)
      r_log(facility,level,"%s[%d]=%s",name,len,hex);
    else
      r_log(facility,level,"%s",hex);

    free(hex);
    return(0);
  }

// Some platforms (notably WIN32) do not have this
#ifndef va_copy
  #ifdef WIN32
    #define va_copy(dest, src) ( (dest) = (src) )
  #else  // WIN32
    #error va_copy undefined, and semantics of assignment on va_list unknown
  #endif //WIN32
#endif //va_copy

int r_vlog(int facility,int level,const char *format,va_list ap)
  {
    char log_fmt_buf[MAX_ERROR_STRING_SIZE];
    const char *level_str="unknown";
    const char *facility_str="unknown";
    const char *fmt_str=format;
    int i;

    if(r_log_env_verbose){
      if((level>=LOG_EMERG) && (level<=LOG_DEBUG))
        level_str=log_level_strings[level];

      if(facility >= 0 && facility < log_type_ct)
        facility_str=log_types[facility].facility_name;

      snprintf(log_fmt_buf, MAX_ERROR_STRING_SIZE, "(%s/%s) %s",
        facility_str,level_str,format);

      log_fmt_buf[MAX_ERROR_STRING_SIZE-1]=0;
      fmt_str=log_fmt_buf;
    }

    for(i=0; i<LOG_NUM_DESTINATIONS; i++){
      if(r_logging_dest(i,facility,level)){
        // Some platforms do not react well when you use a va_list more than
        // once
        va_list copy;
        va_copy(copy, ap);
        log_destinations[i].dest_vlog(facility,level,fmt_str,copy);
        va_end(copy);
      }
    }
    return(0);
  }

int stderr_vlog(int facility,int level,const char *format,va_list ap)
  {
    vfprintf(stderr,format,ap);
      fprintf(stderr,"\n");
    return(0);
  }

int syslog_vlog(int facility,int level,const char *format,va_list ap)
  {
#ifndef WIN32
    vsyslog(level|LOG_LOCAL0,format,ap);
#endif
    return(0);
  }

int noop_vlog(int facility,int level,const char *format,va_list ap)
  {
    return(0);
  }

static int r_logging_dest(int dest_index, int facility, int level)
  {
    int thresh;

    _r_log_init(0);

    if(!log_destinations[dest_index].enabled)
      return(0);

    if(level <= r_log_level_environment)
      return(1);

    if(r_log_initted<R_LOG_INITTED2)
      return(level<=r_log_level);

    if(facility < 0 || facility > log_type_ct)
      thresh=r_log_level;
    else{
      if(log_types[facility].level[dest_index]==LOG_LEVEL_NONE)
        return(0);

      if(log_types[facility].level[dest_index]>=0)
        thresh=log_types[facility].level[dest_index];
      else if(log_destinations[dest_index].default_level!=LOG_LEVEL_UNDEFINED)
        thresh=log_destinations[dest_index].default_level;
      else
        thresh=r_log_level;
    }

    if(level<=thresh)
      return(1);

    return(0);
  }

int r_logging(int facility, int level)
  {
    int i;

    _r_log_init(0);

    /* return 1 if logging is on for any dest */

    for(i=0; i<LOG_NUM_DESTINATIONS; i++){
      if(r_logging_dest(i,facility,level))
        return(1);
    }

    return(0);
  }


static int r_log_get_default_level(void)
  {
    char *log = 0;
    int _status;

    log=getenv("R_LOG_LEVEL");

    if(log){
      r_log_level=atoi(log);
      r_log_level_environment=atoi(log);
    }
    else{
      r_log_level=LOGGING_DEFAULT_LEVEL;
    }

    _status=0;
  //abort:
    return(_status);
  }


static int r_log_get_destinations(int usereg)
  {
    char *log = 0;
    int i;
    int r,_status;

    log=getenv("R_LOG_DESTINATION");
    if(log){
      for(i=0; i<LOG_NUM_DESTINATIONS; i++)
        log_destinations[i].enabled=!strcmp(log,log_destinations[i].dest_name);
    }
    else if(usereg){
      NR_registry reg_key;
      int i;
      int value;
      char c;

      /* Get the data out of the registry */
      for(i=0; i<LOG_NUM_DESTINATIONS; i++){
        /* set callback for default level */
        if((size_t)snprintf(reg_key,sizeof(reg_key),"%s.%s.level",LOGGING_REG_PREFIX,
          log_destinations[i].dest_name)>=sizeof(reg_key))
          ABORT(R_INTERNAL);

        NR_reg_register_callback(reg_key,
          NR_REG_CB_ACTION_ADD|NR_REG_CB_ACTION_CHANGE|NR_REG_CB_ACTION_DELETE,
          r_log_default_level_change_cb,0);

        if(r=r_log_get_reg_level(reg_key,&value)){
          if(r==R_NOT_FOUND)
            log_destinations[i].default_level=LOG_LEVEL_UNDEFINED;
          else
            ABORT(R_INTERNAL);
        }
        else
          log_destinations[i].default_level=value;

        /* set callback for the enabled key for this logging dest */
        if((size_t)snprintf(reg_key,sizeof(reg_key),"%s.%s.enabled",LOGGING_REG_PREFIX,
          log_destinations[i].dest_name)>=sizeof(reg_key))
          ABORT(R_INTERNAL);

        NR_reg_register_callback(reg_key,
          NR_REG_CB_ACTION_ADD|NR_REG_CB_ACTION_CHANGE|NR_REG_CB_ACTION_DELETE,
          r_log_destination_change_cb,0);

        if(r=NR_reg_get_char(reg_key,&c)){
          if(r==R_NOT_FOUND)
            log_destinations[i].enabled=0;
          else
            ABORT(r);
        }
        else
          log_destinations[i].enabled=c;
      }
    }

    _status=0;
  abort:
    return(_status);
  }

static void r_log_destination_change_cb(void *cb_arg, char action, NR_registry_name name)
  {
    r_log_get_destinations(1);
  }

static void r_log_default_level_change_cb(void *cb_arg, char action, NR_registry_name name)
  {
    r_log_get_destinations(1);
  }


int r_log_init()
  {
    _r_log_init(1);

    return 0;
  }

int _r_log_init(int use_reg)
  {
#ifndef WIN32
    char *log = 0;
#endif

    if(r_log_initted==0) {
#ifdef WIN32
      r_log_env_verbose=1;
#else
      log=getenv("R_LOG_VERBOSE");
      if(log)
        r_log_env_verbose=atoi(log);
#endif

    }

    if(!use_reg){
      if(r_log_initted<R_LOG_INITTED1){
        r_log_get_default_level();
        r_log_get_destinations(0);

        r_log_initted=R_LOG_INITTED1;
      }
    }
    else{
      if(r_log_initted<R_LOG_INITTED2){
        int facility;

        r_log_get_default_level();
        r_log_get_destinations(1);

        r_log_register("generic",&facility);
        r_log_register("logging",&NR_LOG_LOGGING);

        r_log_initted=R_LOG_INITTED2;
      }
    }

    return(0);
  }

int r_log_set_extra_destination(int default_level, r_dest_vlog *dest_vlog)
  {
    int i;
    log_destination *dest = 0;

    for(i=0; i<LOG_NUM_DESTINATIONS; i++){
      if(!strcmp("extra",log_destinations[i].dest_name)){
        dest=&log_destinations[i];
        break;
      }
    }

    if(!dest)
      return(R_INTERNAL);

    if (dest_vlog==0){
      dest->enabled=0;
      dest->dest_vlog=noop_vlog;
    }
    else{
      dest->enabled=1;
      dest->default_level=default_level;
      dest->dest_vlog=dest_vlog;
    }

    return(0);
  }

