/* Synchronet message base (SMB) index re-generator */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * This program 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 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include <stdio.h>
#include <stdlib.h> /* atoi, qsort */
#include <string.h> /* strnicmp */
#include <ctype.h>  /* toupper */

#include "smblib.h"
#include "genwrap.h"    /* PLATFORM_DESC */
#include "str_list.h"   /* strList API */
#include "crc16.h"
#include "git_branch.h"
#include "git_hash.h"

smb_t smb;
BOOL  renumber = FALSE;
BOOL  rehash = FALSE;
BOOL  fixnums = FALSE;
BOOL  smb_undelete = FALSE;
char* usage = "usage: fixsmb [-renumber] [-undelete] [-fixnums] [-rehash] <smb_file> [[smb_file] [...]]";
bool  terminated = false;

#if defined _WIN32
BOOL WINAPI ControlHandler(unsigned long CtrlType)
{
	terminated = true;
	return true;
}
#elif defined __unix__
#include <signal.h>
void sighandler_quit(int sig)
{
	terminated = true;
}
#endif


int compare_index(const idxrec_t* idx1, const idxrec_t* idx2)
{
	return idx1->number - idx2->number;
}

void sort_index(smb_t* smb)
{
	ulong    l;
	uint8_t* idxbuf;
	size_t   idxreclen = smb_idxreclen(smb);

	printf("Sorting index... ");
	if ((idxbuf = malloc(idxreclen * smb->status.total_msgs)) == NULL) {
		perror("malloc");
		exit(1);
	}

	rewind(smb->sid_fp);
	for (l = 0; l < smb->status.total_msgs; l++)
		if (smb_fread(smb, idxbuf + (l * idxreclen), idxreclen, smb->sid_fp) != idxreclen) {
			perror("reading index");
			exit(1);
		}

	qsort(idxbuf, l, idxreclen
	      , (int (*)(const void*, const void*)) compare_index);

	rewind(smb->sid_fp);
	if (chsize(fileno(smb->sid_fp), 0L) != 0) {        /* Truncate the index */
		perror("truncating index");
		exit(1);
	}

	printf("\nRe-writing index... \n");
	smb->status.total_msgs = l;
	for (l = 0; l < smb->status.total_msgs; l++) {
		if (smb_fwrite(smb, idxbuf + (l * idxreclen), idxreclen, smb->sid_fp) != idxreclen) {
			perror("writing index");
			exit(1);
		}
	}
	free(idxbuf);
	printf("\n");
}

void close_msgbase(void)
{
	smb_close(&smb);
}

int fixsmb(char* sub)
{
	char*     p;
	char*     text;
	char      c;
	int       i, w;
	ulong     l, size, n;
	off_t     length;
	smbmsg_t  msg;
	uint32_t* numbers = NULL;
	long      total = 0;
	BOOL      dupe_msgnum;
	uint32_t  highest = 0;

	memset(&smb, 0, sizeof(smb));

	SAFECOPY(smb.file, sub);

	if ((p = getfext(smb.file)) != NULL && stricmp(p, ".shd") == 0)
		*p = 0; /* Chop off .shd extension, if supplied on command-line */

	char path[MAX_PATH + 1];
	SAFEPRINTF(path, "%s.shd", smb.file);
	if (!fexistcase(path)) {
		printf("%s does not exist\n", path);
		exit(1);
	}

	printf("Opening %s\n", smb.file);

	if ((i = smb_open(&smb)) != 0) {
		printf("smb_open returned %d: %s\n", i, smb.last_error);
		exit(1);
	}

	if ((i = smb_lock(&smb)) != 0) {
		printf("smb_lock returned %d: %s\n", i, smb.last_error);
		exit(1);
	}

	if ((i = smb_locksmbhdr(&smb)) != 0) {
		printf("smb_locksmbhdr returned %d: %s\n", i, smb.last_error);
		exit(1);
	}

	if ((i = smb_getstatus(&smb)) != 0) {
		printf("smb_getstatus returned %d: %s\n", i, smb.last_error);
		smb_unlocksmbhdr(&smb);
		exit(1);
	}

	uint32_t last_msg = smb.status.last_msg;

	if (!(smb.status.attr & SMB_HYPERALLOC)) {

		if ((i = smb_open_ha(&smb)) != 0) {
			printf("smb_open_ha returned %d: %s\n", i, smb.last_error);
			exit(1);
		}

		if ((i = smb_open_da(&smb)) != 0) {
			printf("smb_open_da returned %d: %s\n", i, smb.last_error);
			exit(1);
		}

		rewind(smb.sha_fp);
		if (chsize(fileno(smb.sha_fp), 0L) != 0) {    /* Truncate the header allocation file */
			perror("truncating sha file");
			exit(1);
		}
		rewind(smb.sda_fp);
		if (chsize(fileno(smb.sda_fp), 0L) != 0) {    /* Truncate the data allocation file */
			perror("truncating sda file");
			exit(1);
		}
	}

	rewind(smb.sid_fp);
	if (chsize(fileno(smb.sid_fp), 0L) != 0) {     /* Truncate the index */
		perror("truncating sid file");
		exit(1);
	}

	if (renumber || rehash) {
		printf("Truncating hash file (due to renumbering/rehashing)\n");
		if ((i = smb_open_hash(&smb)) != SMB_SUCCESS) {
			printf("smb_open_hash returned %d: %s\n", i, smb.last_error);
			exit(1);
		}
		if (chsize(fileno(smb.hash_fp), 0L) != 0) {
			perror("truncating hash file");
			exit(1);
		}
	}

	if (!(smb.status.attr & SMB_HYPERALLOC)) {
		length = filelength(fileno(smb.sdt_fp));
		/* TODO: LE Only */
		w = 0;
		for (l = 0; l < length; l += SDT_BLOCK_LEN)  /* Init .SDA file to NULL */
			fwrite(&w, 2, 1, smb.sda_fp);

		length = filelength(fileno(smb.shd_fp));
		c = 0;
		for (l = smb.status.header_offset; l < length; l += SHD_BLOCK_LEN)   /* Init .SHD file to NULL */
			fwrite(&c, 1, 1, smb.sha_fp);
	} else
		length = filelength(fileno(smb.shd_fp));

	n = 0;    /* message offset */
	for (l = smb.status.header_offset; l < length; l += size) {
		size = SHD_BLOCK_LEN;
		printf("\r%2lu%%  ", (long)(100.0 / ((float)length / l)));
		fflush(stdout);
		ZERO_VAR(msg);
		msg.idx.offset = l;
		if ((i = smb_lockmsghdr(&smb, &msg)) != 0) {
			printf("\n(%06lX) smb_lockmsghdr returned %d:\n%s\n", l, i, smb.last_error);
			continue;
		}
		i = smb_getmsghdr(&smb, &msg);
		smb_unlockmsghdr(&smb, &msg);
		if (i != SMB_SUCCESS) {
			if ((l == smb.status.header_offset) || (smb.status.attr & SMB_HYPERALLOC) || (i != SMB_ERR_HDR_ID))
				printf("\n(%06lX) smb_getmsghdr returned %d:\n%s\n", l, i, smb.last_error);
			continue;
		}
		size = smb_hdrblocks(smb_getmsghdrlen(&msg)) * SHD_BLOCK_LEN;
		printf("#%-5" PRIu32 " (%06lX) %-25.25s ", msg.hdr.number, l
		       , msg.hdr.type == SMB_MSG_TYPE_FILE ? msg.subj : msg.from);

		dupe_msgnum = FALSE;
		for (i = 0; i < total && !dupe_msgnum; i++)
			if (msg.hdr.number == numbers[i])
				dupe_msgnum = TRUE;

		if (dupe_msgnum && fixnums && msg.hdr.number >= last_msg) {
			printf("Fixed message number (%lu -> %lu)\n", (ulong)msg.hdr.number, (ulong)highest + 1);
			msg.hdr.number = highest + 1;
			dupe_msgnum = FALSE;
		}
		if (!dupe_msgnum) {
			total++;
			if ((numbers = realloc_or_free(numbers, total * sizeof(*numbers))) == NULL) {
				fprintf(stderr, "realloc failure: %lu\n", total * sizeof(*numbers));
				exit(EXIT_FAILURE);
			}
			numbers[total - 1] = msg.hdr.number;
		}

		if (dupe_msgnum)
			msg.hdr.attr |= MSG_DELETE;
		else if (smb_undelete)
			msg.hdr.attr &= ~MSG_DELETE;

		if (renumber)
			msg.hdr.number = n + 1;

		if (renumber || msg.hdr.number <= last_msg) {
			/* Create hash record */
			if (msg.hdr.attr & MSG_DELETE)
				text = NULL;
			else
				text = smb_getmsgtxt(&smb, &msg, GETMSGTXT_BODY_ONLY);
			i = smb_hashmsg(&smb, &msg, (uchar*)text, TRUE /* update */);
			if (i != SMB_SUCCESS)
				printf("!ERROR %d hashing message\n", i);
			if (text != NULL)
				free(text);
		}

		/* Index the header */
		if (dupe_msgnum)
			printf("Not indexing duplicate message number (%u)\n", msg.hdr.number);
		else if (msg.hdr.attr & MSG_DELETE)
			printf("Not indexing deleted message\n");
		else if (msg.hdr.number == 0)
			printf("Not indexing invalid message number (0)!\n");
		else {
			msg.idx_offset = n;
			if (msg.hdr.number > highest)
				highest = msg.hdr.number;
			if (msg.hdr.netattr & NETMSG_INTRANSIT) {
				printf("Removing 'in transit' attribute\n");
				msg.hdr.netattr &= ~NETMSG_INTRANSIT;
			}
			if ((i = smb_putmsg(&smb, &msg)) != 0) {
				printf("\nsmb_putmsg returned %d: %s\n", i, smb.last_error);
				continue;
			}
			n++;
		}

		if (!(smb.status.attr & SMB_HYPERALLOC)) {
			/**************************/
			/* Allocate header blocks */
			/**************************/
			fseek(smb.sha_fp, (l - smb.status.header_offset) / SHD_BLOCK_LEN, SEEK_SET);
			if (msg.hdr.attr & MSG_DELETE)
				c = 0;                              /* mark as free */
			else
				c = 1;                              /* or allocated */

			for (i = 0; i < (int)(size / SHD_BLOCK_LEN); i++)
				fputc(c, smb.sha_fp);

			/************************/
			/* Allocate data blocks */
			/************************/

			if (!(msg.hdr.attr & MSG_DELETE))
				smb_incmsg_dfields(&smb, &msg, 1);
		}

		smb_freemsgmem(&msg);
	}
	printf("\r%79s\r100%%\n", "");
	smb.status.total_msgs = n;
	if (renumber)
		smb.status.last_msg = highest;
	else {
		if (highest > smb.status.last_msg)
			smb.status.last_msg = highest;
		sort_index(&smb);
	}
	printf("Saving message base status (%lu total messages).\n", n);
	if ((i = smb_putstatus(&smb)) != 0)
		printf("\nsmb_putstatus returned %d: %s\n", i, smb.last_error);
	smb_unlocksmbhdr(&smb);
	printf("Closing message base.\n");
	smb_close(&smb);
	printf("Done.\n");
	FREE_AND_NULL(numbers);
	return 0;
}

int main(int argc, char **argv)
{
	int        i;
	str_list_t list;
	int        retval = EXIT_SUCCESS;

	printf("\nFIXSMB v3.21-%s %s/%s SMBLIB %s - Rebuild Synchronet Message Base\n\n"
	       , PLATFORM_DESC, GIT_BRANCH, GIT_HASH, smb_lib_ver());

	list = strListInit();

	for (i = 1; i < argc; i++) {
		if (argv[i][0] == '-') {
			if (!stricmp(argv[i], "-renumber"))
				renumber = TRUE;
			else if (!stricmp(argv[i], "-rehash"))
				rehash = TRUE;
			else if (!stricmp(argv[i], "-undelete"))
				smb_undelete = TRUE;
			else if (!stricmp(argv[i], "-fixnums"))
				fixnums = TRUE;
		} else
			strListPush(&list, argv[i]);
	}

	if (!strListCount(list)) {
		puts(usage);
		exit(1);
	}

	/* Install Ctrl-C/Break signal handler here */
#if defined _WIN32
	SetConsoleCtrlHandler(ControlHandler, /* Add */ true);
#elif defined __unix__
	signal(SIGINT, sighandler_quit);
#endif

	atexit(close_msgbase);

	for (i = 0; list[i] != NULL && retval == EXIT_SUCCESS && !terminated; i++)
		retval = fixsmb(list[i]);

	return retval;
}
