/*
 *  Project   : tin - a Usenet reader
 *  Module    : makerc.c
 *  Original  : Thomas E. Dickey <dickey@invisible-island.net> (makecfg.c)
 *  Author    : Dennis Preiser
 *  Created   : 2026-04-30
 *  Updated   : 2026-07-28
 *  Notes     : structs for tinrc.c
 *
 * Copyright (c) 2026 Dennis Preiser <dennis@d--p.de>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/* replace tin.h {{ */

#ifdef HAVE_CONFIG_H
#include "autoconf.h"
#endif /* HAVE_CONFIG_H */

#ifdef HAVE_STDDEF_H
#include <stddef.h>
#endif /* HAVE_STDDEF_H */
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif /* HAVE_CONFIG_H */

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#else
extern void exit(int);
extern void free(void *);
#endif /* HAVE_STDLIB_H */

#ifdef HAVE_UNISTD_H
#	include <unistd.h>
#endif /* HAVE_UNISTD_H */

#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif /* !EXIT_SUCCESS */

#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif /* !EXIT_FAILURE */

#ifdef HAVE_STRING_H
#include <string.h>
#else
extern char *strcpy(char *, const char *);
extern int strcmp(const char *, const char *);
extern int strncmp(const char *, const char *, size_t);
extern size_t strlen(const char *);
#endif /* HAVE_STRING_H */

#include <stdio.h>
#include <ctype.h>
#ifdef HAVE_ERRNO_H
#	include <errno.h>
#else
#	ifdef HAVE_SYS_ERRNO_H
#		include <sys/errno.h>
/*
#	else
#		error "No errno.h or sys/errno.h found"
*/
#	endif /* HAVE_SYS_ERRNO_H */
#endif /* HAVE_ERRNO_H */
#if !defined(errno)
#	ifdef DECL_ERRNO
		extern int errno;
#	endif /* DECL_ERRNO */
#endif /* !errno */

#ifndef HAVE_UNLINK
#	define unlink(file)	remove(file)
#endif /* !HAVE_UNLINK */

#ifdef STDC_NORETURN
#ifdef HAVE_STDNORETURN_H
#include <stdnoreturn.h>
#endif /* HAVE_STDNORETURN_H */
#else
#if defined(_Noreturn)
#undef _Noreturn
#endif /* _Noreturn */
#define _Noreturn /**/
#endif /* STDC_NORETURN */

#if !defined(HAVE_ISBLANK) || !defined(isblank) || defined(DECL_ISBLANK)
	/* handrolled isblank() */
#	define ISBLANK(c) ((c) == ' ' || (c) == '\t')
#else
#	define ISBLANK(c) isblank(c)
#endif /* !HAVE_ISBLANK || !isblank || DECL_ISBLANK */

#ifndef ENOTSUP
#	define ENOTSUP EINVAL
#endif /* !ENOTSUP */

/* }} replace tin.h */

#define TINRC_TEMPLATE_FILE "tinrc.tbl"
#define RC_FILE "tinrc.h"
#define INIT_FILE "tinrc.c"


static _Noreturn void
failed(
	const char *message)
{
	perror(message);
	exit(EXIT_FAILURE);
}


static FILE *
open_it(
	const char *filename,
	const char *mode)
{
	FILE *fp = fopen(filename, mode);

	if (fp == NULL)
		failed(filename);
	return fp;
}


static void
write_it(
	FILE *dothfp,
	const char * const *table)
{
	int n;

	for (n = 0; table[n] != NULL; n++)
		fprintf(dothfp, "%s\n", table[n]);
}


_Noreturn static void
give_up(
	const char *rc_file,
	const char *init_file,
	FILE *ifp,
	FILE *dothfp,
	FILE *dotcfp)
{
	if (ifp)
		(void) fclose(ifp);
	if (dothfp)
		(void) fclose(dothfp);
	if (dotcfp)
		(void) fclose(dotcfp);
	(void) unlink(rc_file);
	(void) unlink(init_file);
	errno = ENOTSUP;
	failed("Giving up");
}


static int
count_var(
	FILE *ifp)
{
	char buffer[BUFSIZ];
	char *p;
	long pos;
	int max_var = 0;

	if ((pos = ftell(ifp)) < 0)
		return -1;

	while (fgets(buffer, sizeof(buffer) - 1, ifp)) {
		p = buffer;
		while (ISBLANK((unsigned char) *p))
			++p;

		/* end of struct, stop counting */
		if (*p == '}')
			break;

		switch (*p) {
			case '\n':
			case '#':
			case '/':
			case ';':
			case '!':
			case '+':
				break;

			default:
				++max_var;
				break;
		}
	}

	return ((fseek(ifp, pos, SEEK_SET) < 0) ? -1 : max_var);
}


static void
do_makerc(
	const char *tmpl,
	const char *rc_file,
	const char *init_file,
	FILE *dothfp,
	FILE *dotcfp)
{
	FILE *ifp = open_it(tmpl, "r");
	char buffer[BUFSIZ];
	char indent[BUFSIZ];
	char *p;
	const char *type, *var, *init, *cmt;
	int ln = 0, curr_var = 0, max_var = 0;
	size_t ilen;

	while (fgets(buffer, sizeof(buffer) - 1, ifp)) {
		if (max_var == 0) {
			if ((max_var = count_var(ifp)) < 0) {
				fprintf(stderr, "%s: %s\n", tmpl, strerror(errno));
				give_up(rc_file, init_file, ifp, dothfp, dotcfp);
			}
			fprintf(dothfp, "%s\n", "");
			fprintf(dotcfp, "%s\n", "");
		}
		++ln;
		p = buffer;
		while (ISBLANK((unsigned char) *p))
			++p;
		if (*p == ';' || *p == '\n')
			continue;

		/* copy preprocessor directives or comments as they are */
		if (*p == '#' || (*p == '/' && (p[1] == '/' || p[1] == '*'))) {
			fprintf(dothfp, "%s", buffer);
			fprintf(dotcfp, "%s", buffer);
			continue;
		}

		if (*p == '!') {
			fprintf(dothfp, "%s", buffer + 1);
			continue;
		}

		if (*p == '+') {
			fprintf(dotcfp, "%s", buffer + 1);
			continue;
		}

		if (*p == '}') {
			fprintf(dothfp, "%s", buffer);
			fprintf(dotcfp, "%s", buffer);
			curr_var = max_var = 0;
			continue;
		}

		++curr_var;

		/* remove newline; it may appear after initial value or optional comment */
		if ((p = strrchr(buffer, '\n')))
			*p = '\0';

		cmt = NULL;
		ilen = 0;
		indent[ilen] = '\0';
		p = buffer;

		/* save indentation, get type of variable, mandatory */
		while (*p == '\t' && ilen + 1 < sizeof(indent)) {
			indent[ilen++] = '\t';
			++p;
		}
		indent[ilen] = '\0';
		type = p;

		switch (*type) {
			case 'c':
			/* case 'f': */
			case 'i':
			case 'l':
			/* case 's': */
			case 't':
			/* case 'u': */
			case 'w':
				break;

			default:
				fprintf(stderr, "Unknown type in %s, line %d:\n%s\n", tmpl, ln, buffer);
				give_up(rc_file, init_file, ifp, dothfp, dotcfp);
					break;
		}

		/* name of variable, mandatory */
		if ((p = strchr(p, '\t'))) {
			*p++ = '\0';
			while (*p == '\t')
				++p;
		}
		if (!p || !*p) {
			fprintf(stderr, "Variable name and initial value are missing in %s, line %d:\n\t%s\n", tmpl, ln, type);
			give_up(rc_file, init_file, ifp, dothfp, dotcfp);
		}
		var = p;

		/* initial value, mandatory */
		if ((p = strchr(p, '\t'))) {
			*p++ = '\0';
			while (*p == '\t')
				++p;
		}
		if (!p || !*p) {
			fprintf(stderr, "Initial value is missing in %s, line %d:\n\t%s\t%s\n", tmpl, ln, type, var);
			give_up(rc_file, init_file, ifp, dothfp, dotcfp);
		}
		init = p;

		/* comment, optional */
		if ((p = strchr(p, '\t'))) {
			*p++ = '\0';
			while (*p == '\t')
				++p;

			cmt = p;
		}

		if (cmt && *cmt)
			fprintf(dothfp, "%s%s\t%s;\t%s\n", indent, type, var, cmt);
		else
			fprintf(dothfp, "%s%s\t%s;\n", indent, type, var);
		fprintf(dotcfp, "%s%s%s\t/* %s */\n", indent, init, curr_var == max_var ? "" : ",", var);
	}

	if (fclose(ifp))
		give_up(rc_file, init_file, NULL, dothfp, dotcfp);
}


static void
makerc(
	const char *rc_file,
	const char *init_file,
	FILE *dothfp,
	FILE *dotcfp)
{
	static const char *const tinrc_h_open[] =
	{
		"/* This file is generated by MAKERC */",
		"",
		"#ifndef TINRC_H",
		"#	define TINRC_H 1",
		NULL
	};
	static const char *const tinrc_h_close[] =
	{
		"",
		"#endif /* !TINRC_H */",
		NULL
	};
	static const char *const init_c_open[] =
	{
		"/* This file is generated by MAKERC */",
		"",
		"#ifndef TIN_H",
		"#	include \"tin.h\"",
		"#endif /* !TIN_H */",
		NULL
	};

	write_it(dothfp, tinrc_h_open);
	write_it(dotcfp, init_c_open);

	do_makerc(TINRC_TEMPLATE_FILE, rc_file, init_file, dothfp, dotcfp);

	write_it(dothfp, tinrc_h_close);

	if (fclose(dothfp))
		give_up(rc_file, init_file, NULL, NULL, dotcfp);
	if (fclose(dotcfp))
		give_up(rc_file, init_file, NULL, NULL, NULL);
}


int
main(
	int argc,
	char *argv[])
{
	FILE *dothfp;
	FILE *dotcfp;
	const char *rc_file;
	const char *init_file;

	if (argc > 1)
		rc_file = argv[1];
	else
		rc_file = RC_FILE;

	if (argc > 2)
		init_file = argv[2];
	else
		init_file = INIT_FILE;

	dothfp = open_it(rc_file, "w");
	dotcfp = open_it(init_file, "w");

	makerc(rc_file, init_file, dothfp, dotcfp);

	return (0);
}
