/* This file is part of GNU Pies.
   Copyright (C) 2009-2022 Sergey Poznyakoff

   GNU Pies is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option)
   any later version.

   GNU Pies is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GNU Pies.  If not, see <http://www.gnu.org/licenses/>. */

#include "pies.h"

typedef struct
{
  int open;
  FILE *file;
} LOGSTREAM;


static int
syslog_printer (LOGSTREAM *str, int prio, const char *fmt, va_list ap)
{
  pies_vsyslog (prio, fmt, ap);
  return 0;
}

static int
syslog_open (int logf, LOGSTREAM *str)
{
  if (logf & DIAG_REOPEN_LOG)
    {
      pies_syslog_open ();
      str->open = 1;
    }
  else
    str->open = 0;
  return 0;
}

static void
syslog_close (LOGSTREAM *str)
{
  if (str->open)
    pies_syslog_close ();
}

static int
stderr_printer (LOGSTREAM *str, int prio, const char *fmt, va_list ap)
{
  FILE *fp = str->file;
  va_list aq;
  
  fprintf (fp, "%s: ", program_name);
  va_copy (aq, ap);
  vfprintf (fp, fmt, aq);
  va_end (aq);
  fprintf (fp, "\n");
  return 0;
}

static int
stderr_open (int logf, LOGSTREAM *str)
{
  if (PIES_SYSVINIT_ENABLED && (logf & DIAG_REOPEN_LOG))
    {
      int fd = console_open (O_WRONLY|O_NOCTTY|O_NDELAY);
      if (fd == -1)
	return -1;
      str->file = fdopen (fd, "w");
      if (!str->file)
	{
          close (fd);
	  return -1;
        }
      str->open = 1;
    }
  else
    {
      str->file = stderr;
      str->open = 0;
    }
  return 0;
}

static void
stderr_close (LOGSTREAM *str)
{
  if (str->open)
    fclose (str->file);
}

struct logger
{
  int mask;
  int (*printer) (LOGSTREAM *, int prio, const char *fmt, va_list ap);
  int (*open) (int flags, LOGSTREAM *);
  void (*close) (LOGSTREAM *);
};

struct logger logger[] = {
  { DIAG_TO_STDERR, stderr_printer, stderr_open, stderr_close },
  { DIAG_TO_SYSLOG, syslog_printer, syslog_open, syslog_close }
};

void
vdiagmsg (int logf, int prio, const char *fmt, va_list ap)
{
  int i;
  for (i = 0; i < ARRAY_SIZE (logger); i++)
    {
      if (logger[i].mask & (logf & DIAG_TO_MASK))
	{
	  LOGSTREAM stream;
	  if (logger[i].open (logf, &stream) == 0)
	    {
	      logger[i].printer (&stream, prio, fmt, ap);
	      logger[i].close (&stream);
	    }
	}
    }
}

void
diagmsg (int logf, int prio, const char *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  vdiagmsg (logf, prio, fmt, ap);
  va_end (ap);
} 

unsigned debug_level;
int source_info_option;
int diag_output = DIAG_TO_STDERR;

void
diag_setup (int flags)
{
  if (flags)
    diag_output = flags;
  if (diag_output & DIAG_TO_SYSLOG)
    pies_syslog_open ();
}

void
vlogmsg (int prio, const char *fmt, va_list ap)
{
  vdiagmsg (diag_output, prio, fmt, ap);
}

void
logmsg (int prio, const char *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  vlogmsg (prio, fmt, ap);
  va_end (ap);
}

void
debug_msg (const char *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  vlogmsg (LOG_DEBUG, fmt, ap);
  va_end (ap);
}


void
logmsg_vprintf (int prio, const char *fmt, va_list ap)
{
  char *p, *t, *str;
  static char *buf = NULL;
  static size_t len = 0;
  static struct grecs_txtacc *log_acc;

  if (grecs_vasprintf (&buf, &len, fmt, ap))
    {
      logmsg (LOG_CRIT, _("out of memory trying to log a message"));
      return;
    }
  if (!log_acc)
    log_acc = grecs_txtacc_create ();
  
  str = buf;
  
  while (str)
    {
      p = strchr (str, '\n');
      if (p)
	{
	  *p++ = 0;
	  if (!*p)
	    p = NULL;
	  grecs_txtacc_grow_string (log_acc, str);
	  grecs_txtacc_grow_char (log_acc, 0);
	  t = grecs_txtacc_finish (log_acc, 0);
	  logmsg (prio, "%s", t);
	  grecs_txtacc_free_string (log_acc, t);
	}
      else
	grecs_txtacc_grow_string (log_acc, str);
      str = p;
    }
}

void
logmsg_printf (int prio, const char *fmt, ...)
{
  va_list ap;
  va_start (ap, fmt);
  logmsg_vprintf (prio, fmt, ap);
  va_end (ap);
}

void
pies_diag_printer (grecs_locus_t const *locus, int err, int errcode,
		   const char *msg)
{
  if (locus)
    {
      char *locstr = NULL;
      size_t locsize  = 0;

      grecs_asprint_locus (&locstr, &locsize, locus);

      if (errcode)
	logmsg (err ? LOG_ERR : LOG_WARNING, "%s: %s: %s",
		locstr, msg, strerror (errcode));
      else
	logmsg (err ? LOG_ERR : LOG_WARNING, "%s: %s",
		locstr, msg);
      free (locstr);
    }
  else
    {
      if (errcode)
	logmsg (err ? LOG_ERR : LOG_WARNING, "%s: %s", msg,
		strerror (errcode));
      else
	logmsg (err ? LOG_ERR : LOG_WARNING, "%s", msg);
    }
}
  
void
logfuncall (const char *fun, const char *arg, int err)
{
  if (arg)
    grecs_error (NULL, err, _("%s: %s failed"), arg, fun);
  else
    grecs_error (NULL, err, _("%s failed"), fun);
}
