/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * Simple LNXALL binary logger interface
 *
 * 2024/12/23
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>

#include <endian.h>

#include "lnxbinlog.h"
#include "binlog_format.h"

#define BINLOG_BUFSIZ   0x80000

static int b_dump_uptime;

static void dump_utc_time(uint64_t nowt)
{
	char buf[128];
	time_t now = (time_t) (nowt / 1000);
	struct tm tim;

	if (localtime_r(&now, &tim) == NULL)
		memset(&tim, 0, sizeof(tim));
	snprintf(buf, sizeof(buf), "%d-%02d-%02d %02d:%02d:%02d.%03d",
		tim.tm_year + 1900,
		tim.tm_mon + 1,
		tim.tm_mday,
		tim.tm_hour,
		tim.tm_min,
		tim.tm_sec,
		(int) (nowt % 1000));

	fprintf(stdout, "[%s] ", buf);
}

static void dump_up_time(uint64_t nowt)
{
	char buf[128];
	time_t now = (time_t) (nowt / 1000);
	int days, hours, mins, secs;

	days = (int) (now / 86400);
	now %= 86400;
	hours = (int) (now / 3600);
	now %= 3600;
	mins = (int) (now / 60);
	secs = (int) (now % 60);

	snprintf(buf, sizeof(buf), "%03d days, %02d:%02d:%02d.%03d",
		days, hours, mins, secs, (int) (nowt % 1000));

	fprintf(stdout, "[%s] ", buf);
}

static void dump_binlog_head(const struct blog_head * blh)
{
	uint16_t logtype;
	char descbuf[128];
	const char * descp = NULL;
	const unsigned char * pdat;
	size_t dlen, idx;
	uint64_t logtime;

	dlen = (size_t) blh->desc_len;
	logtype = be16toh(blh->log_type);
	switch (logtype) {
	case BLTYPE_MBTCP_R:
	case BLTYPE_MBTCP_W: {
			const struct bldesc_modbus_tcp * modbus_tcp;
			modbus_tcp = (const struct bldesc_modbus_tcp *) (blh + 1);
			if (dlen != sizeof(*modbus_tcp)) {
				fprintf(stderr, "Error, invalid Modbus tcp descriptor: %zu\n", dlen);
				fflush(stderr);
				descp = "Unknown Modbus TCP";
			} else {
				char ipaddr[32] = { 0 };
				inet_ntop(AF_INET, &modbus_tcp->remote_ip, ipaddr, sizeof(ipaddr));
				snprintf(descbuf, sizeof(descbuf), "MODBUS_TCP %s %s:%u",
					logtype == BLTYPE_MBTCP_R ? "R" : "W",
					// ipaddr, (unsigned int) be16toh(modbus_tcp->port_no));
					ipaddr, (unsigned int) modbus_tcp->port_no);
				descp = descbuf;
			}
		}
		break;

	case BLTYPE_CAN_R:
		descp = "CAN R";
		break;

	case BLTYPE_CAN_W:
		descp = "CAN W";
		break;

	case BLTYPE_CAN_1_R:
		descp = "CAN 1 R";
		break;

	case BLTYPE_CAN_1_W:
		descp = "CAN 1 W";
		break;

	case BLTYPE_CAN_2_R:
		descp = "CAN 2 R";
		break;

	case BLTYPE_CAN_2_W:
		descp = "CAN 2 W";
		break;

	case BLTYPE_CAN_3_R:
		descp = "CAN 2 R";
		break;

	case BLTYPE_CAN_3_W:
		descp = "CAN 3 W";
		break;

	case BLTYPE_RS485_R:
		descp = "RS485 R";
		break;

	case BLTYPE_RS485_W:
		descp = "RS485 W";
		break;

	case BLTYPE_RS485_1_R:
		descp = "RS485 1 R";
		break;

	case BLTYPE_RS485_1_W:
		descp = "RS485 1 W";
		break;

	case BLTYPE_RS485_2_R:
		descp = "RS485 2 R";
		break;

	case BLTYPE_RS485_2_W:
		descp = "RS485 2 W";
		break;

	case BLTYPE_RS485_3_R:
		descp = "RS485 3 R";
		break;

	case BLTYPE_RS485_3_W:
		descp = "RS485 3 W";
		break;

	case BLTYPE_RS485_4_R:
		descp = "RS485 4 R";
		break;

	case BLTYPE_RS485_4_W:
		descp = "RS485 4 W";
		break;

	case BLTYPE_RS485_5_R:
		descp = "RS485 5 R";
		break;

	case BLTYPE_RS485_5_W:
		descp = "RS485 5 W";
		break;

	case BLTYPE_RS485_6_R:
		descp = "RS485 6 R";
		break;

	case BLTYPE_RS485_6_W:
		descp = "RS485 6 W";
		break;

	case BLTYPE_RS485_7_R:
		descp = "RS485 7 R";
		break;

	case BLTYPE_RS485_7_W:
		descp = "RS485 7 W";
		break;

	case BLTYPE_RS485_8_R:
		descp = "RS485 8 R";
		break;

	case BLTYPE_RS485_8_W:
		descp = "RS485 8 W";
		break;

	case BLTYPE_RS485_9_R:
		descp = "RS485 9 R";
		break;

	case BLTYPE_RS485_9_W:
		descp = "RS485 9 W";
		break;

	case BLTYPE_RS485_10_R:
		descp = "RS485 10 R";
		break;

	case BLTYPE_RS485_10_W:
		descp = "RS485 10 W";
		break;

	case BLTYPE_RS485_11_R:
		descp = "RS485 11 R";
		break;

	case BLTYPE_RS485_11_W:
		descp = "RS485 11 W";
		break;

	case BLTYPE_RS485_12_R:
		descp = "RS485 12 R";
		break;

	case BLTYPE_RS485_12_W:
		descp = "RS485 12 W";
		break;

	case BLTYPE_RS485_13_R:
		descp = "RS485 13 R";
		break;

	case BLTYPE_RS485_13_W:
		descp = "RS485 13 W";
		break;

	case BLTYPE_RS485_14_R:
		descp = "RS485 14 R";
		break;

	case BLTYPE_RS485_14_W:
		descp = "RS485 14 W";
		break;

	case BLTYPE_RS485_15_R:
		descp = "RS485 15 R";
		break;

	case BLTYPE_RS485_15_W:
		descp = "RS485 15 W";
		break;

	case BLTYPE_RS485_16_R:
		descp = "RS485 16 R";
		break;

	case BLTYPE_RS485_16_W:
		descp = "RS485 16 W";
		break;

	default:
		snprintf(descbuf, sizeof(descbuf), "Unknown type: %#x", logtype);
		descp = descbuf;
		break;
	}

	logtime = (uint64_t) be16toh(blh->utcmsec_h);
	logtime = (logtime << 32) + (uint64_t) be32toh(blh->utcmsec_l);
	dump_utc_time(logtime);

	if (b_dump_uptime != 0) {
		logtime = (uint64_t) be16toh(blh->upmsec_h);
		logtime = (logtime << 32) + (uint64_t) be32toh(blh->upmsec_l);
		dump_up_time(logtime);
	}

	fprintf(stdout, "%s =>", descp);

	pdat = (const unsigned char *) blh;
	pdat += sizeof(*blh) + dlen;
	dlen = (size_t) be16toh(blh->data_len);
	for (idx = 0; idx < dlen; ++idx) {
		fprintf(stdout, " %02x", (unsigned int) *pdat++);
	}

	fputc('\n', stdout);
	fflush(stdout);
}

static size_t dump_binlog(const unsigned char * buffer, size_t blen)
{
	size_t offlen, offlast, bsize;
	const unsigned char * p;
	const struct blog_head * blh;
	int unalign = 0;

	blh = NULL;
	offlen = 0;
	offlast = 0;
	p = buffer;

retry:
	while (*p != BLOG_MAGIC_0) {
		p++;
		offlen++;
		if (offlen >= blen)
			return offlast ? offlast : blen;
	}

	if (blen < (offlen + sizeof(*blh)))
		return offlast ? offlast : blen;
	blh = (const struct blog_head *) p;
	if (blh->magic1 != BLOG_MAGIC_1 || blh->magic2 != BLOG_MAGIC_2) {
		p++;
		offlen++;
		offlast = offlen;
		goto retry;
	}

	if (unalign == 0 && (0x3ul & (unsigned long) p)) {
		unalign = 1;
		fprintf(stderr, "Error, unaligned pointer at %p\n", p);
		fflush(stderr);
	}

	bsize = sizeof(*blh);
	bsize += (size_t) blh->desc_len;
	bsize += (size_t) be16toh(blh->data_len);
	if (blen < (bsize + offlen))
		return offlast ? offlast : blen;
	dump_binlog_head(blh);

	p += bsize;
	offlen += bsize;
	offlast = offlen;
	if (offlen < blen)
		goto retry;
	return blen;
}

static void trans_binlog(const char * filp, uint64_t fsize)
{
	uint64_t curoff;
	int fd, error;
	unsigned char * binbuf;

	curoff = 0;
	binbuf = NULL;
	fd = open(filp, O_RDONLY | O_CLOEXEC);
	if (fd < 0) {
		error = errno;
		fprintf(stderr, "Error, failed to open '%s': %s\n", filp, strerror(error));
		fflush(stderr);
		return;
	}

	binbuf = (unsigned char *) malloc(BINLOG_BUFSIZ);
	if (binbuf == NULL) {
		fprintf(stderr, "Error, system out of memory!\n");
		fflush(stderr);
		close(fd);
		return;
	}

	while (curoff < fsize) {
		size_t rlen;
		ssize_t len;

		rlen = (size_t) (fsize - curoff);
		if (rlen > BINLOG_BUFSIZ)
			rlen = BINLOG_BUFSIZ;

		if (lseek(fd, (off_t) curoff, SEEK_SET) != (off_t) curoff) {
			error = errno;
			fprintf(stderr, "Error, failed to seek file '%s': %s\n", filp, strerror(error));
			fflush(stderr);
			break;
		}

		len = read(fd, binbuf, rlen);
		if (len <= 0)
			break;
		curoff += (uint64_t) dump_binlog(binbuf, len);
	}

	if (fd != -1)
		close(fd);
	if (binbuf != NULL)
		free(binbuf);
}

int main(int argc, char *argv[])
{
	int i, ret, error;
	struct stat fst;
	char * outbuf;
	const char * envp;

	b_dump_uptime = 0;
	envp = getenv("BINLOG_UPTIME");
	if (envp && envp[0] == '1')
		b_dump_uptime = -1;

	outbuf = (char *) calloc(0x1, 0x80001);
	if (outbuf != NULL) {
		fflush(stdout);
		setvbuf(stdout, outbuf, _IOFBF, 0x80000);
	}

	for (i = 1; i < argc; ++i) {
		const char * arg1 = argv[i];

		memset(&fst, 0, sizeof(fst));
		ret = stat(arg1, &fst);
		if (ret == -1) {
			error = errno;
			fprintf(stderr, "Error, %s: %s\n", strerror(error), arg1);
			fflush(stderr);
			continue;
		}

		if (!S_ISREG(fst.st_mode)) {
			fprintf(stderr, "Error, not regular file: %s\n", arg1);
			fflush(stderr);
			continue;
		}

		fprintf(stdout, "Trying to dump from binary-log: %s\n", arg1);
		fflush(stdout);
		trans_binlog(arg1, (uint64_t) fst.st_size);
	}

	if (outbuf != NULL) {
		fflush(stdout);
		setvbuf(stdout, NULL, _IOLBF, 0x20000);
		free(outbuf);
	}

	return 0;
}
