/**
 * @file    myLog.c
 * @brief   异步日志实现 — 消息池 + 生产者/消费者 + 批量写入 + 文件轮转
 *
 * 架构概述：
 *   调用 log_prt() → 从消息池取空闲块 → 填充内容 → 入队(生产者)
 *   工作线程 → 批量出队(消费者) → 控制台彩色输出 + 文件写入
 *   首次调用 pthread_once 自动启动工作线程
 */

#include "myLog.h"
#include "myBase.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>

/* ========== 可配置参数 ========== */

/** 消息池预分配大小 */
#define POOL_SIZE       10000
/** 单次批量处理消息数上限 */
#define BATCH_MAX       100
/** 空闲等待间隔(毫秒) */
#define FLUSH_MS        10
/** 单日志文件最大 10MB */
#define FILE_MAX_SIZE   (10 * 1024 * 1024)
/** 保留历史文件数 */
#define FILE_MAX_KEEP   5
/** 单条日志内容上限 4KB */
#define CONTENT_MAX     4096

/* ========== 数据结构 ========== */

/**
 * @brief 单条日志消息（消息池节点 + 队列节点合一）
 */
typedef struct stru_log_msg
{
	uint32_t        level;           /* 日志级别 LOG_INFO/LOG_ERROR */
	char            time_str[64];    /* 时间戳 YYYY-MM-DD HH:MM:SS.mmm */
	char            src_info[128];   /* "文件名/函数:行号" */
	char            content[CONTENT_MAX]; /* 格式化后的日志内容 */
	struct stru_log_msg *next;       /* 空闲链表/队列链表指针 */
} stru_log_msg;

/**
 * @brief 线程安全消息队列（生产者-消费者模式）
 */
typedef struct
{
	stru_log_msg   *head;            /* 队头 */
	stru_log_msg   *tail;            /* 队尾 */
	int             count;           /* 当前消息数 */
	pthread_mutex_t lock;            /* 互斥锁 */
	pthread_cond_t  cond_pop;        /* 消费者等待条件 */
	pthread_cond_t  cond_push;       /* 生产者等待条件（队列满） */
} stru_msg_queue;

/**
 * @brief 日志系统全局状态
 */
typedef struct
{
	stru_msg_queue  q;               /* 消息队列 */
	pthread_t       worker;          /* 工作线程 */
	volatile int    running;         /* 线程运行标志 */
	FILE           *fp;              /* 当前日志文件句柄 */
	long            file_bytes;      /* 当前文件已写入字节数 */
	int             to_file;         /* 文件输出开关 */
	int             to_console;      /* 控制台输出开关 */
	char            dir[256];        /* 日志目录路径 */
} stru_log_state;

LOCAL stru_log_state g_log;

/* ========== 消息池(预分配，无运行时 malloc) ========== */

LOCAL stru_log_msg      *g_pool_free;                           /* 空闲链表头 */
LOCAL stru_log_msg       g_pool_buf[POOL_SIZE];                 /* 预分配数组 */
LOCAL pthread_mutex_t    g_pool_lock  = PTHREAD_MUTEX_INITIALIZER;
LOCAL pthread_once_t     g_init_once  = PTHREAD_ONCE_INIT;

/**
 * @brief  初始化消息池：把预分配数组链成空闲链表
 * @detail 只在首次调用时执行一次（由 global_init 调用）
 */
LOCAL void pool_init(void)
{
	pthread_mutex_lock(&g_pool_lock);

	if (g_pool_free != NULL)
	{
		pthread_mutex_unlock(&g_pool_lock);

		return;
	}

	/* 链成空闲链表 */
	for (int i = 0; i < POOL_SIZE - 1; i++)
	{
		g_pool_buf[i].next = &g_pool_buf[i + 1];
	}

	g_pool_buf[POOL_SIZE - 1].next = NULL;
	g_pool_free = &g_pool_buf[0];

	pthread_mutex_unlock(&g_pool_lock);
}

/**
 * @brief  从池中取一个空闲消息块
 * @return 成功返回指针，池耗尽返回 NULL
 */
LOCAL stru_log_msg *pool_alloc(void)
{
	pthread_mutex_lock(&g_pool_lock);

	stru_log_msg *m = g_pool_free;

	if (m)
	{
		g_pool_free = m->next;
	}

	pthread_mutex_unlock(&g_pool_lock);

	return m;
}

/**
 * @brief  归还消息块到池
 */
LOCAL void pool_free(stru_log_msg *m)
{
	if (!m)
	{
		return;
	}

	pthread_mutex_lock(&g_pool_lock);

	m->next = g_pool_free;
	g_pool_free = m;

	pthread_mutex_unlock(&g_pool_lock);
}

/* ========== 队列操作 ========== */

/**
 * @brief  入队（生产者端）
 * @detail 队列满时阻塞等待消费者腾出空间
 */
LOCAL void q_push(stru_log_msg *m)
{
	pthread_mutex_lock(&g_log.q.lock);

	/* 队列满则等待 */
	while (g_log.q.count >= POOL_SIZE)
	{
		pthread_cond_wait(&g_log.q.cond_push, &g_log.q.lock);
	}

	m->next = NULL;

	/* 尾插 */
	if (!g_log.q.head)
	{
		g_log.q.head = m;
	}
	else
	{
		g_log.q.tail->next = m;
	}

	g_log.q.tail = m;
	g_log.q.count++;

	pthread_cond_signal(&g_log.q.cond_pop);
	pthread_mutex_unlock(&g_log.q.lock);
}
/* ========== 文件管理 ========== */

/**
 * @brief  拼接日志文件路径
 * @param  idx 0=当前文件(dir/app.log), N>=1=历史文件(dir/app.log.N)
 */
LOCAL void file_path(char *buf, size_t sz, int idx)
{
	if (idx == 0)
	{
		snprintf(buf, sz, "%s/app.log", g_log.dir);
	}
	else
	{
		snprintf(buf, sz, "%s/app.log.%d", g_log.dir, idx);
	}
}

/**
 * @brief  确保日志目录存在，不存在则创建
 * @return 0 成功
 */
LOCAL int ensure_dir(void)
{
	if (!g_log.dir[0])
	{
		/* 默认放到 /tmp/logs */
		snprintf(g_log.dir, sizeof(g_log.dir), "/tmp/logs");
	}

	struct stat st;

	if (stat(g_log.dir, &st) != 0)
	{
		return mkdir(g_log.dir, 0755);
	}

	return 0;
}

/**
 * @brief  轮转日志文件
 * @detail app.log→app.log.1→app.log.2→...→app.log.N(max)→删除
 */
LOCAL void rotate_files(void)
{
	char old[512];
	char neo[512];

	/* 关闭当前文件 */
	if (g_log.fp)
	{
		fclose(g_log.fp);
		g_log.fp = NULL;
	}

	/* 删除最旧的历史文件 */
	file_path(old, sizeof(old), FILE_MAX_KEEP);
	remove(old);

	/* 历史文件依次后移一位 */
	for (int i = FILE_MAX_KEEP - 1; i >= 1; i--)
	{
		file_path(old, sizeof(old), i);
		file_path(neo, sizeof(neo), i + 1);
		rename(old, neo);
	}

	/* 当前文件重命名为 .1 */
	file_path(old, sizeof(old), 0);
	file_path(neo, sizeof(neo), 1);
	rename(old, neo);

	g_log.file_bytes = 0;
}

/**
 * @brief  打开当前日志文件（追加模式，行缓冲）
 * @return 成功返回 FILE*, 失败返回 NULL
 */
LOCAL FILE *open_file(void)
{
	if (ensure_dir() != 0)
	{
		return NULL;
	}

	char path[512];

	file_path(path, sizeof(path), 0);

	/* 检查已有文件大小，超出则先轮转 */
	struct stat st;

	if (stat(path, &st) == 0)
	{
		if (st.st_size > FILE_MAX_SIZE)
		{
			rotate_files();
		}
		else
		{
			g_log.file_bytes = st.st_size;
		}
	}

	FILE *fp = fopen(path, "a");

	if (fp)
	{
		setlinebuf(fp); /* 行缓冲模式 */
	}

	return fp;
}

/* ========== 格式化 ========== */

/**
 * @brief  当前时间戳字符串 "YYYY-MM-DD HH:MM:SS.mmm"
 */
LOCAL void fmt_time(char *buf, size_t sz)
{
	struct timeval tv;

	gettimeofday(&tv, NULL);

	struct tm *t = localtime(&tv.tv_sec);

	if (t)
	{
		snprintf(buf, sz, "%04d-%02d-%02d %02d:%02d:%02d.%03d",
		         t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
		         t->tm_hour, t->tm_min, t->tm_sec,
		         (int)(tv.tv_usec / 1000));
	}
	else
	{
		snprintf(buf, sz, "----.--.-- --:--:--.---");
	}
}

/**
 * @brief  格式化文件来源信息 "文件名/函数名:行号"
 * @detail 自动去除文件路径前缀，仅保留文件名
 */
LOCAL void fmt_src(char *buf, size_t sz,
                   const char *file, const char *func, uint32_t line)
{
	if (!file || !func)
	{
		snprintf(buf, sz, "?:?:0");

		return;
	}

	/* 提取纯文件名（去掉路径前缀） */
	const char *p = strrchr(file, '/');

	if (!p)
	{
		p = strrchr(file, '\\');
	}

	const char *name = p ? p + 1 : file;

	snprintf(buf, sz, "%s/%s:%u", name, func, line);
}

/* ========== 输出 ========== */

/**
 * @brief  批量写入控制台 + 文件
 * @detail 控制台带颜色输出，文件带级别标记
 */
LOCAL void output_batch(stru_log_msg **batch, int cnt)
{
	if (cnt == 0)
	{
		return;
	}

	static const char *titles[] = { "INFO", "ERROR" };
	static const char *colors[] = { COLOR_WHITE, COLOR_RED };

	/* 控制台彩色输出 */
	if (g_log.to_console)
	{
		for (int i = 0; i < cnt; i++)
		{
			stru_log_msg *m = batch[i];

			printf("%s[%s] [%s%s%s] %s\n",
			       colors[m->level], m->time_str,
			       colors[m->level], titles[m->level],
			       COLOR_RESET, m->content);
		}

		fflush(stdout);
	}

	/* 文件输出 */
	if (g_log.to_file)
	{
		if (!g_log.fp)
		{
			g_log.fp = open_file();
		}

		if (g_log.fp)
		{
			for (int i = 0; i < cnt; i++)
			{
				stru_log_msg *m = batch[i];
				int n = fprintf(g_log.fp, "[%s] [%s] %s\n",
				                titles[m->level],
				                m->time_str, m->content);

				if (n > 0)
				{
					g_log.file_bytes += n;
				}
			}

			fflush(g_log.fp);

			/* 超出限制则轮转 */
			if (g_log.file_bytes > FILE_MAX_SIZE)
			{
				rotate_files();
			}
		}
	}
}

/* ========== 工作线程 ========== */

/**
 * @brief  日志消费线程主循环
 * @detail 批量从队列取消息 → 输出 → 归还消息池
 */
/**
 * @brief  日志消费线程主循环
 * @detail 一次加锁取完队列中所有消息 → 立即输出 → 归还消息池。
 *         队列空时短暂休眠，有消息到达时由 q_push 的 cond_signal 唤醒。
 *         不等待凑批——有多少输出多少，确保日志及时可见。
 */
LOCAL void *worker_thread(void *arg)
{
	(void)arg;

	{
		char ts[64];
		fmt_time(ts, sizeof(ts));
		printf("[%s] [INFO] [myLog.c, %u] liblog: worker_thread started\n", ts, __LINE__);
	}

	while (g_log.running)
	{
		stru_log_msg *batch[BATCH_MAX];
		int cnt = 0;

		/* 一次加锁，取完队列中所有消息（最多 BATCH_MAX 条防止饿死） */
		pthread_mutex_lock(&g_log.q.lock);

		while (cnt < BATCH_MAX && g_log.q.count > 0)
		{
			stru_log_msg *m = g_log.q.head;

			g_log.q.head = m->next;

			if (!g_log.q.head)
			{
				g_log.q.tail = NULL;
			}

			g_log.q.count--;
			batch[cnt++] = m;
		}

		pthread_cond_broadcast(&g_log.q.cond_push);
		pthread_mutex_unlock(&g_log.q.lock);

		if (cnt > 0)
		{
			output_batch(batch, cnt);

			/* 归还消息块到池 */
			for (int i = 0; i < cnt; i++)
			{
				pool_free(batch[i]);
			}
		}
		else
		{
			/* 队列空，等待新消息（带超时避免死等） */
			pthread_mutex_lock(&g_log.q.lock);

			if (g_log.q.count == 0 && g_log.running)
			{
				struct timespec ts;

				clock_gettime(CLOCK_REALTIME, &ts);
				ts.tv_nsec += FLUSH_MS * 1000000;

				if (ts.tv_nsec >= 1000000000)
				{
					ts.tv_sec += 1;
					ts.tv_nsec -= 1000000000;
				}

				pthread_cond_timedwait(&g_log.q.cond_pop, &g_log.q.lock, &ts);
			}

			pthread_mutex_unlock(&g_log.q.lock);
		}
	}

	return NULL;
}


/* ========== 全局初始化(pthread_once) ========== */

/**
 * @brief  全局初始化（由 pthread_once 确保只执行一次）
 * @detail 初始化消息池 + 启动工作线程
 */
LOCAL void global_init(void)
{
	pool_init();

	/* 显式初始化同步原语（避免依赖零初始化的不可移植行为） */
	pthread_mutex_init(&g_log.q.lock, NULL);
	pthread_cond_init(&g_log.q.cond_pop, NULL);
	pthread_cond_init(&g_log.q.cond_push, NULL);

	g_log.q.head  = NULL;
	g_log.q.tail  = NULL;
	g_log.q.count = 0;
	g_log.to_console = 1;
	g_log.to_file    = 0;
	g_log.running    = 1;

	int ret = pthread_create(&g_log.worker, NULL, worker_thread, NULL);

	if (ret != 0)
	{
		{
			char ts[64];
			fmt_time(ts, sizeof(ts));
			printf("[%s] [ERROR] [myLog.c, %u] liblog: pthread_create failed (err=%d)\n", ts, __LINE__, ret);
		}
		g_log.running = 0;
	}
	else
	{
		{
			char ts[64];
			fmt_time(ts, sizeof(ts));
			printf("[%s] [INFO] [myLog.c, %u] liblog: async worker thread started, to_console=%d, to_file=%d\n",
			       ts, __LINE__, g_log.to_console, g_log.to_file);
		}
	}
}

/* ========== 公开 API ========== */

/**
 * @brief  初始化日志系统
 * @param  dir 日志文件目录（NULL 则默认 /tmp/logs）
 * @return 0 成功
 */
int log_init(const char *dir)
{
	if (dir && dir[0])
	{
		snprintf(g_log.dir, sizeof(g_log.dir), "%s", dir);
	}

	pool_init();
	/* g_log.to_file = 1; *//* 默认不开启文件输出，由运行时 log_file_on() 显式开启 */

	return 0;
}

/**
 * @brief  核心日志打印（非阻塞）
 * @detail 首次调用 pthread_once 自动触发全局初始化。
 *         从消息池获取空闲块 → 填充时间/来源/内容 → 入队。
 *         消息池耗尽时静默丢弃本条日志。
 */
void log_prt(uint32_t level, const char *file, const char *func,
             uint32_t line, const char *fmt, ...)
{
	/* 懒初始化：仅首次调用执行 */
	pthread_once(&g_init_once, global_init);

	if (level > LOG_ERROR || !fmt || !g_log.running)
	{
		return;
	}

	/* 从消息池取空闲块 */
	stru_log_msg *m = pool_alloc();

	if (!m)
	{
		return; /* 池耗尽，静默丢弃 */
	}

	/* 填充消息字段 */
	m->level = level;
	fmt_time(m->time_str, sizeof(m->time_str));
	fmt_src(m->src_info, sizeof(m->src_info), file, func, line);

	/* 格式化日志内容 */
	va_list ap;

	va_start(ap, fmt);

	int pos = snprintf(m->content, sizeof(m->content),
	                   "[%s] ", m->src_info);

	if (pos < (int)sizeof(m->content))
	{
		vsnprintf(m->content + pos, sizeof(m->content) - pos, fmt, ap);
	}

	va_end(ap);

	/* 入队（生产者端） */
	q_push(m);
}

void log_file_on(void)
{
	g_log.to_file = 1;
}

void log_file_off(void)
{
	g_log.to_file = 0;
}

void log_console_on(void)
{
	g_log.to_console = 1;
}

void log_console_off(void)
{
	g_log.to_console = 0;
}

/**
 * @brief  等待消息队列清空（阻塞直到所有日志写入完成）
 */
void log_flush(void)
{
	for (;;)
	{
		pthread_mutex_lock(&g_log.q.lock);
		int c = g_log.q.count;
		pthread_mutex_unlock(&g_log.q.lock);

		if (c == 0)
		{
			break;
		}

		usleep(10000); /* 10ms 轮询 */
	}
}

/**
 * @brief  停止日志系统并释放所有资源
 * @detail 停止工作线程 → 刷新文件 → 释放队列残留消息
 */
void log_cleanup(void)
{
	if (!g_log.running)
	{
		return;
	}

	/* 通知线程停止 */
	g_log.running = 0;
	pthread_cond_signal(&g_log.q.cond_pop);

	/* 等待线程退出 */
	pthread_join(g_log.worker, NULL);

	/* 关闭文件 */
	if (g_log.fp)
	{
		fclose(g_log.fp);
		g_log.fp = NULL;
	}

	/* 释放队列中残留消息 */
	pthread_mutex_lock(&g_log.q.lock);

	stru_log_msg *cur = g_log.q.head;

	while (cur)
	{
		stru_log_msg *tmp = cur;

		cur = cur->next;
		pool_free(tmp);
	}

	g_log.q.head  = NULL;
	g_log.q.tail  = NULL;
	g_log.q.count = 0;

	pthread_mutex_unlock(&g_log.q.lock);
}
