/* This file is part of Mailfromd.
   Copyright (C) 2005-2025 Sergey Poznyakoff

   Mailfromd 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 3, or (at your option)
   any later version.

   Mailfromd is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with mailfromd.  If not, see <http://www.gnu.org/licenses/>. */

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/utsname.h>
#include <sysexits.h>
#include <ctype.h>
/* FIXME: This is only for _() and N_(): */
#include "libmf.h"

char *progname;
char *template_dir = TEMPLATE_DIR;
char *output_dir;

static void
testdir(char *dirname)
{
	DIR *d;
	
	if ((d = opendir(dirname)) != NULL) {
		struct dirent *ent;
		while ((ent = readdir(d)) != NULL) {
			if (!(ent->d_name[0] == '.' &&
			      (ent->d_name[1] == 0 ||
			       (ent->d_name[1] == '.' && ent->d_name[2] == 0)))) {
				    fprintf(stderr, "%s: directory %s exists and is not empty\n",
					    progname, dirname);
				    exit(EX_UNAVAILABLE);
			    }
		}
		closedir(d);
	} else if (errno == ENOENT) {
		if (mkdir(dirname, 0755)) {
			fprintf(stderr, "%s: can't create %s: %s\n",
				progname, template_dir, strerror(errno));
			exit(EX_OSERR);
		}
	} else if (errno == ENOTDIR) {
		fprintf(stderr, "%s: %s is not a directory\n",
			progname, template_dir);
		exit(EX_USAGE);
	} else {
		fprintf(stderr, "%s: can't open %s: %s\n",
			progname, template_dir, strerror(errno));
		exit(EX_OSERR);
	}
}	

FILE *
fopenat(int dirfd, char *name, int create)
{
	int fd;
	
	fd = openat(dirfd, name,
		    create ? O_WRONLY|O_CREAT|O_TRUNC : O_RDONLY, 0644);
	if (fd == -1)
		return NULL;
	return fdopen(fd, create ? "w" : "r");
}

struct expansion {
	char *token;
	size_t len;
	char *text;
};

enum {
	EXP_MODNAME,
	EXP_ATLOCALDIR,
	EXP_INCDIR,
	EXP_EMAIL
};

static struct expansion expansions[] = {
#define S(s) s, sizeof(s)-1
	{ S("MODNAME"), NULL },
	{ S("ATLOCALDIR"), NULL },
	{ S("INCDIR"), NULL },
	{ S("EMAIL"), NULL },
	{ NULL }
};

void
expand_line(char *buf, size_t len, FILE *fp)
{
	char *p;
	size_t n;
	int i;
	
	while (len) {
		p = memchr(buf, '<', len);
		if (p == NULL) {
			fwrite(buf, len, 1, fp);
			break;
		}
		fwrite(buf, p - buf, 1, fp);
		p++;
		n = len - (p - buf);
		for (i = 0; expansions[i].token; i++) {
			if (expansions[i].len + 1 < n &&
			    memcmp(p, expansions[i].token, expansions[i].len) == 0 &&
			    p[expansions[i].len] == '>') {
				fputs(expansions[i].text, fp);
				p += expansions[i].len + 1;
				goto next;
			}
		}
		fputc(p[-1], fp);
	next:
		len -= p - buf;
		buf = p;
	}
}

void
expand_template(int tdir, char *tname, int odir, char *oname)
{
	FILE *tfp, *ofp;
	char buf[BUFSIZ];
	
	if ((tfp = fopenat(tdir, tname, 0)) == NULL) {
		fprintf(stderr, "%s: can't open %s/%s for reading: %s\n",
			progname, template_dir, tname, strerror(errno));
		exit(EX_OSERR);
	}
	if ((ofp = fopenat(odir, oname, 1)) == NULL) {
		fprintf(stderr, "%s: can't open %s/%s for writing: %s\n",
			progname, output_dir, oname, strerror(errno));
		exit(EX_OSERR);
	}

	while (fgets(buf, sizeof(buf), tfp)) {
		if (strncmp(buf, "##", 2) == 0)
			continue;
		expand_line(buf, strlen(buf), ofp);
	}
	
	if (ferror(tfp)) {
		fprintf(stderr, "%s: error reading %s/%s\n",
			progname, template_dir, tname);
		exit(EX_OSERR);
	}

	if (ferror(ofp)) {
		fprintf(stderr, "%s: error writing %s/%s\n",
			progname, output_dir, oname);
		exit(EX_OSERR);
	}
	
	fclose(tfp);
	fclose(ofp);
}

void
verify_modname(char const *p)
{
	for (; *p; p++) {
		if (!(isalnum(*p) || *p == '_')) {
			fprintf(stderr, "%s: module name contains forbidden characters (%c)\n",
				progname, *p);
			exit(EX_USAGE);
		}
	}
}

void
usage(FILE *fp)
{
	fprintf(fp, "Usage: %s [OPTIONS] MODNAME [DIR]\n", progname);
	fprintf(fp, "%s creates and bootstraps a new dynamically loaded module for mailfromd\n", progname);
	fprintf(fp, "\nOPTIONS are:\n\n");
	fprintf(fp, "  -C DIR         Search for template files in DIR\n");
	fprintf(fp, "                 (default: %s)\n", TEMPLATE_DIR);
	fprintf(fp, "  -e EMAIL       Set author email address\n");
	fprintf(fp, "  -q             Suppress informative messages.\n");
	fprintf(fp, "  -h             Print this help text.\n");
	fprintf(fp, "\n");
	fprintf(fp, "Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
	fprintf(fp, "mailfromd home page: <%s>\n", PACKAGE_URL);
}

/*
 * FIXME: The following is duplicated from lib/version.c with minor
 * edits (using stdio instead of mailutils streams).  This is because
 * I'm reluctant to make such a simple tool depend on such a large
 * library as mailutils.
 */
static const char mailfromd_version_etc_copyright[] =
  /* Do *not* mark this string for translation.  %s is a copyright
     symbol suitable for this locale, and %d is the copyright
     year.  */
  "Copyright %s 2005-%d Sergey Poznyakoff";
static int mailfromd_copyright_year = 2023;

void
version(void)
{
#ifdef GIT_DESCRIBE
	printf("%s (%s) %s [%s]\n",
	       progname, PACKAGE, PACKAGE_VERSION, GIT_DESCRIBE);
#else
	printf("%s (%s) %s\n",
	       progname, PACKAGE, PACKAGE_VERSION);
#endif
	/* TRANSLATORS: Translate "(C)" to the copyright symbol
	   (C-in-a-circle), if this symbol is available in the user's
	   locale.  Otherwise, do not translate "(C)"; leave it as-is.  */
	printf(mailfromd_version_etc_copyright, 
	       _("(C)"), mailfromd_copyright_year);

	printf("%s", _("\
\n\
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
This is free software: you are free to change and redistribute it.\n\
There is NO WARRANTY, to the extent permitted by law.\n\
\n\
"));
	
	/* TRANSLATORS: %s denotes an author name.  */
	printf(_("Written by %s.\n"), "Sergey Poznyakoff");
}

static void *
xmalloc(size_t size)
{
	char *p;
	
	if ((p = malloc(size)) == NULL) {
		fprintf(stderr, "%s: out of memory\n", progname);
		exit(EX_UNAVAILABLE);
	}
	return p;
}

static char *
mkfilename(char *name, char *suffix)
{
	char *p = xmalloc(strlen(name) + strlen(suffix) + 1);
	return strcat(strcpy(p, name), suffix);
}

// setup MODNAME DIR
int
main(int argc, char **argv)
{
	int c;
	char *module_name;
	char *module_prefix;
	char *email = NULL;
	int quiet = 0;
	int tdir, odir;
	static char mfmod_prefix[] = "mfmod_";
	char *name;
	
	if ((progname = strrchr(argv[0], '/')) == NULL)
		progname = argv[0];
	else
		progname++;

	if (argc > 1) {
		if (strcmp(argv[1], "--help") == 0) {
			usage(stdout);
			return 0;
		}
		if (strcmp(argv[1], "--version") == 0) {
			version();
			return 0;
		}
	}
	
	while ((c = getopt(argc, argv, "C:e:hVq")) != EOF) {
		switch (c) {
		case 'C':
			template_dir = optarg;
			break;

		case 'e':
			email = optarg;
			break;

		case 'h':
			usage(stdout);
			return 0;

		case 'V':
			version();
			break;
			
		case 'q':
			quiet = 1;
			break;
			
		default:
			usage(stderr);
			return EX_USAGE;
		}
	}

	argc -= optind;
	argv += optind;
	switch (argc) {
	case 2:
		output_dir = argv[1];
	case 1:
		module_name = argv[0];
		verify_modname(module_name);
		break;
	default:
		usage(stderr);
		return EX_USAGE;
	}

	module_prefix = xmalloc(strlen(mfmod_prefix) + strlen(module_name) + 1);
	strcat(strcpy(module_prefix, mfmod_prefix), module_name);

	if (!output_dir)
		output_dir = module_prefix;
	if (!quiet)
		printf("%s: setting up new module in %s\n", progname, output_dir);
	
	if (!email) {
		struct utsname u;
		char *user = getenv("USER");
		
		uname(&u);
		email = xmalloc(strlen(user) + strlen(u.nodename) + 3);
		strcpy(email, user);
		strcat(email, "@");
		strcat(email, u.nodename);
	}
	
	testdir(output_dir);

	expansions[EXP_MODNAME].text = module_name;
	expansions[EXP_ATLOCALDIR].text = ATLOCAL_DIR;
	expansions[EXP_INCDIR].text = DEFAULT_INCLUDE_DIR;
	expansions[EXP_EMAIL].text = email;
		
	if ((tdir = open(template_dir, O_NONBLOCK | O_DIRECTORY)) == -1) {
		fprintf(stderr, "%s: can't open directory %s: %s\n",
			progname, template_dir, strerror(errno));
		return EX_OSERR;
	}
	if ((odir = open(output_dir, O_NONBLOCK | O_DIRECTORY)) == -1) {
		fprintf(stderr, "%s: can't open directory %s: %s\n",
			progname, output_dir, strerror(errno));
		return EX_OSERR;
	}

	expand_template(tdir, "template.am", odir, "Makefile.am");
	expand_template(tdir, "template.ac", odir, "configure.ac");

	name = mkfilename(module_prefix, ".c");
	expand_template(tdir, "template.c", odir, name);
	free(name);

	name = mkfilename(module_name, ".mfl");	
	expand_template(tdir, "template.mfl", odir, name);
	free(name);

	close(tdir);
	close(odir);

	return 0;
}

		
	
