#pragma once

/*
 * Odyssey.
 *
 * Scalable PostgreSQL connection pooler.
 */

#include <machinarium/ds/queue.h>
#include <machinarium/wait_list.h>

#include <types.h>
#include <list.h>
#include <pid.h>

#define OD_LOGLINE_MAXLEN 1024

#define OD_LOGGER_GLOBAL NULL

typedef struct od_logger od_logger_t;

typedef enum { OD_LOG, OD_ERROR, OD_DEBUG, OD_FATAL } od_logger_level_t;

typedef enum {
	OD_LOGGER_FORMAT_TEXT,
	OD_LOGGER_FORMAT_JSON
} od_logger_format_type_t;

typedef struct {
	od_logger_level_t level;
	od_list_t link;
	size_t len;
	char text[OD_LOGLINE_MAXLEN];
} od_logger_slot_t;

struct od_logger {
	od_pid_t *pid;
	int log_debug;
	int log_stdout;
	int log_syslog;
	int async;
	int queue_depth;
	char *format;
	int format_len;
	od_logger_format_type_t format_type;

	int fd;

	atomic_uint_fast64_t loaded;
	int64_t machine;

	od_logger_slot_t *slots;
	mm_queue_t tasks;

	od_list_t free_slots;
	size_t free_slots_count;
	pthread_spinlock_t free_slots_lock;

	mm_wait_list_t notifier;

	atomic_uint_fast64_t dropped_lines;
};

extern od_retcode_t od_logger_init(od_logger_t *, od_pid_t *);
extern od_retcode_t od_logger_load(od_logger_t *logger);

void od_logger_stat(od_logger_t *logger);

static inline void od_logger_set_debug(od_logger_t *logger, int enable)
{
	logger->log_debug = enable;
}

static inline void od_logger_set_stdout(od_logger_t *logger, int enable)
{
	logger->log_stdout = enable;
}

static inline void od_logger_set_format(od_logger_t *logger, char *format)
{
	logger->format = format;
	logger->format_len = strlen(format);

	/* Detect JSON format */
	if (strcasestr(format, "json") != NULL) {
		logger->format_type = OD_LOGGER_FORMAT_JSON;
	} else {
		logger->format_type = OD_LOGGER_FORMAT_TEXT;
	}
}

static inline void od_logger_set_async(od_logger_t *logger, int async)
{
	logger->async = async;
}

static inline void od_logger_set_queue_depth(od_logger_t *logger,
					     int queue_depth)
{
	if (queue_depth <= 0) {
		queue_depth = 30000;
	}

	logger->queue_depth = queue_depth;
}

extern int od_logger_open(od_logger_t *, char *);
extern int od_logger_reopen(od_logger_t *, char *);
extern int od_logger_open_syslog(od_logger_t *, char *, char *);
extern void od_logger_shutdown(od_logger_t *);
extern void od_logger_close(od_logger_t *);
extern void od_logger_write(od_logger_t *, od_logger_level_t, char *, void *,
			    void *, char *, va_list);

void od_logger_wait_finish(od_logger_t *);

static inline void od_log(od_logger_t *logger, char *context, void *client,
			  void *server, char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	od_logger_write(logger, OD_LOG, context, client, server, fmt, args);
	va_end(args);
}

static inline void od_glog(char *context, void *client, void *server, char *fmt,
			   ...)
{
	va_list args;
	va_start(args, fmt);
	od_logger_write(OD_LOGGER_GLOBAL, OD_LOG, context, client, server, fmt,
			args);
	va_end(args);
}

static inline void od_debug(od_logger_t *logger, char *context, void *client,
			    void *server, char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	od_logger_write(logger, OD_DEBUG, context, client, server, fmt, args);
	va_end(args);
}

static inline void od_gdebug(char *context, void *client, void *server,
			     char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	od_logger_write(OD_LOGGER_GLOBAL, OD_DEBUG, context, client, server,
			fmt, args);
	va_end(args);
}

static inline void od_error(od_logger_t *logger, char *context, void *client,
			    void *server, char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	od_logger_write(logger, OD_ERROR, context, client, server, fmt, args);
	va_end(args);
}

static inline void od_gerror(char *context, void *client, void *server,
			     char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	od_logger_write(OD_LOGGER_GLOBAL, OD_ERROR, context, client, server,
			fmt, args);
	va_end(args);
}

static inline void od_fatal(od_logger_t *logger, char *context, void *client,
			    void *server, char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	od_logger_write(logger, OD_FATAL, context, client, server, fmt, args);
	va_end(args);
	exit(1);
}

static inline void od_gfatal(char *context, void *client, void *server,
			     char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	od_logger_write(OD_LOGGER_GLOBAL, OD_FATAL, context, client, server,
			fmt, args);
	va_end(args);
	exit(1);
}

/* log functions to create module-owned loggers with predefined values */
static inline void od_vglog(char *context, void *client, void *server,
			    char *fmt, va_list args)
{
	od_logger_write(OD_LOGGER_GLOBAL, OD_LOG, context, client, server, fmt,
			args);
}

static inline void od_vgdebug(char *context, void *client, void *server,
			      char *fmt, va_list args)
{
	od_logger_write(OD_LOGGER_GLOBAL, OD_DEBUG, context, client, server,
			fmt, args);
}

static inline void od_vgerror(char *context, void *client, void *server,
			      char *fmt, va_list args)
{
	od_logger_write(OD_LOGGER_GLOBAL, OD_ERROR, context, client, server,
			fmt, args);
}

static inline void od_vgfatal(char *context, void *client, void *server,
			      char *fmt, va_list args)
{
	od_logger_write(OD_LOGGER_GLOBAL, OD_FATAL, context, client, server,
			fmt, args);
	exit(1);
}

#define DEFINE_SIMPLE_MODULE_LOGGER(mod, ctx)                                  \
	__attribute__((unused)) static inline void mod##_log(char *fmt, ...)   \
	{                                                                      \
		va_list args;                                                  \
		va_start(args, fmt);                                           \
		od_vglog(ctx, NULL, NULL, fmt, args);                          \
		va_end(args);                                                  \
	}                                                                      \
	__attribute__((unused)) static inline void mod##_debug(char *fmt, ...) \
	{                                                                      \
		va_list args;                                                  \
		va_start(args, fmt);                                           \
		od_vgdebug(ctx, NULL, NULL, fmt, args);                        \
		va_end(args);                                                  \
	}                                                                      \
	__attribute__((unused)) static inline void mod##_error(char *fmt, ...) \
	{                                                                      \
		va_list args;                                                  \
		va_start(args, fmt);                                           \
		od_vgerror(ctx, NULL, NULL, fmt, args);                        \
		va_end(args);                                                  \
	}                                                                      \
	__attribute__((unused)) static inline void mod##_fatal(char *fmt, ...) \
	{                                                                      \
		va_list args;                                                  \
		va_start(args, fmt);                                           \
		od_vgfatal(ctx, NULL, NULL, fmt, args);                        \
		va_end(args);                                                  \
	}
