#include "compat.h"
#include "transliteration.h"
#include "czech.h"
#include "hindi.h"
#include "velthuis.h"
#include "harvard-kyoto.h"
#include "config.h"

#define FLAG_REVERSE    1 << 0
#define FLAG_VELTHUIS   1 << 1
#define FLAG_CZECH      1 << 2
#define FLAG_HINDI      1 << 3
#define FLAG_ASCII      1 << 4
#define FLAG_DEVANAGARI 1 << 5
#define FLAG_HARVARD    1 << 6

static const char *usage_str =
	PROGNAME ", a helper for Sanskrit transliteration.\n"
	"\n"
	"Usage:\n"
	"  " PROGNAME " [flags and text arguments in any order]\n"
	"\n"
	"Options:\n"
	"  -f, --file        the input file for transliteration\n"
	"  -o, --output      the output file (instead of standard input)\n"
	"  -r, --reverse     reverse transliteration (from Latin to Devanagari)\n"
	"  -e, --encode      convert an ASCII text to IAST using the Velthuis scheme\n"
	"  -k, --harvard     convert an ASCII text to IAST using the Harvard-Kyoto scheme\n"
	"  -a, --ascii       convert a Devanagari text to Velthuis text rather than to IAST\n"
	"  -d, --devanagari  when encoding, output a Devanagari text rather than IAST\n"
	"  -c, --czech       transcript Devanagari to Czech language (experimental)\n"
	"  -H, --hindi       transcript Hindi from Devanagari to Latin\n"
	"  -h, --help        show this help and exit\n"
	"  -v, --version     show version number and exit\n"
	"\n"
	"Information:\n"
	"  By default, the program takes all non-option text arguments written in\n"
	"  Devanagari and transliterates them into the IAST version. When the FILE\n"
	"  is set to '-', the standard input shall be read for input. The input data\n"
	"  are expected to be a valid Unicode string.\n"
	"\n"
	"  Since the program outputs Unicode characters, you need to ensure that your\n"
	"  terminal emulator is able to display the characters correctly if you are\n"
	"  printing those into the console instead of a file.\n"
	"\n"
	"  For more information see the iast(1) manual page.\n";

static const char *short_opts = "f:o:rekadcHhv";

static const struct option long_opts[] = {
	{"file",       required_argument,  0, 'f'},
	{"output",     required_argument,  0, 'o'},
	{"reverse",    no_argument,        0, 'r'},
	{"encode",     no_argument,        0, 'e'},
	{"velthuis",   no_argument,        0, 'e'},
	{"harvard",    no_argument,        0, 'k'},
	{"kyoto",      no_argument,        0, 'k'},
	{"ascii",      no_argument,        0, 'a'},
	{"devanagari", no_argument,        0, 'd'},
	{"czech",      no_argument,        0, 'c'},
	{"hindi",      no_argument,        0, 'H'},
	{"help",       no_argument,        0, 'h'},
	{"version",    no_argument,        0, 'v'},
	{0, 0, 0, 0}
};

static void print_usage()
{
	fprintf(stdout, "%s\n", usage_str);
}

static void print_version()
{
	fprintf(stdout, PROGNAME " v" PACKAGE_VERSION "\n");
}

static void error(const char *msg, ...)
{
	va_list params;
	char buf[256];

	va_start(params, msg);
	vsnprintf(buf, sizeof(buf), msg, params);
	fprintf(stderr, "[" PROGNAME "] error: %s\n", buf);
	va_end(params);
}

static int velthuis_encode(const char *in, char **out, unsigned int flags)
{
	char *tmp = NULL;
	int ret;

	if (flags & FLAG_REVERSE)
		return encode_iast_to_velthuis(in, out);

	ret = encode_velthuis_to_iast(in, out);
	if (flags & FLAG_DEVANAGARI) {
		ret = transliterate_latin_to_devanagari(*out, &tmp);
		free(*out);
		*out = tmp;
	}

	return ret;
}

static int harvard_encode(const char *in, char **out, unsigned int flags)
{
	char *tmp = NULL;
	int ret;

	ret = encode_harvard_kyoto_to_iast(in, out);
	if (flags & FLAG_DEVANAGARI) {
		ret = transliterate_latin_to_devanagari(*out, &tmp);
		free(*out);
		*out = tmp;
	}

	return ret;
}

static int iast_transliterate(const char *in, char **out, unsigned int flags)
{
	char *tmp = NULL;
	int ret;

	if (flags & FLAG_REVERSE)
		return transliterate_latin_to_devanagari(in, out);

	ret = transliterate_devanagari_to_latin(in, out);
	if (flags & FLAG_ASCII) {
		ret = encode_iast_to_velthuis(*out, &tmp);
		free(*out);
		*out = tmp;
	}

	return ret;
}

static int process_input(const char *input, char **out, unsigned int flags)
{
	if (flags & FLAG_HINDI)
		return transcript_devanagari_to_hindi(input, out);

	if (flags & FLAG_CZECH)
		return transcript_devanagari_to_czech(input, out);

	if (flags & FLAG_VELTHUIS)
		return velthuis_encode(input, out, flags);

	if (flags & FLAG_HARVARD)
		return harvard_encode(input, out, flags);

	return iast_transliterate(input, out, flags);
}

static int process_string(int fd, const char *input, unsigned int flags)
{
	char *output = NULL;
	int ret;

	ret = process_input(input, &output, flags);

	switch (ret) {
	case 0:
		write(fd, output, strlen(output));
		break;
	default:
		error("unexpected error.");
		break;
	}

	free(output);

	return ret;
}

static int validate_flags(unsigned int flags)
{
	if (flags & FLAG_HINDI && flags & FLAG_REVERSE) {
		error("invalid combination of '-H' and '-r'.");
		return -EINVAL;
	}

	if (flags & FLAG_CZECH && flags & FLAG_REVERSE) {
		error("invalid combination of '-c' and '-r'.");
		return -EINVAL;
	}

	return 0;
}

#define CHUNKSIZE 1024
static int read_fd(char **out, int fd)
{
	int n, alloc = 0, space, len = 0;
	char *buf = NULL;

	while (1) {
		space = alloc - len;
		if (space == 0) {
			space = CHUNKSIZE;
			alloc += space;
			buf = realloc(buf, alloc + 1); /* + 1 is '\0' */
		}
		n = read(fd, buf + len, space);
		if (n > 0) {
			len += n;
			continue;
		} else if (n == 0) {
			break;
		}

		free(buf);
		return -errno;
	}

	if (buf)
		buf[len] = '\0';

	*out = buf;

	return 0;
}

static int read_file(char **out, const char *path)
{
	int fd, retval;

	fd = open(path, O_RDONLY);
	if (fd == -1)
		return -errno;

	retval = read_fd(out, fd);
	close(fd);

	return retval;
}

int main(int argc, const char **argv)
{
	int i, retval, c = 0, opt_index = 0, out = STDOUT_FILENO;
	const char *files[argc];
	unsigned int nfiles = 0;
	unsigned int flags = 0;
	char *input, *output = NULL;

	if (argc == 1) {
		print_usage();
		return -1;
	}

	opterr = 0; /* disable the auto error message */
	while (c != -1) {
		c = getopt_long(argc, (char * const *) argv, short_opts,
				long_opts, &opt_index);

		switch (c) {
		case 'f':
			files[nfiles++] = optarg;
			break;
		case 'o':
			output = optarg;
			break;
		case 'r':
			flags |= FLAG_REVERSE;
			break;
		case 'e':
			flags |= FLAG_VELTHUIS;
			break;
		case 'k':
			flags |= FLAG_HARVARD;
			break;
		case 'a':
			flags |= FLAG_ASCII;
			break;
		case 'd':
			flags |= FLAG_DEVANAGARI;
			break;
		case 'c':
			flags |= FLAG_CZECH;
			break;
		case 'H':
			flags |= FLAG_HINDI;
			break;
		case 'h':
			print_usage();
			return 0;
		case 'v':
			print_version();
			return 0;
		case '?':
			error("unrecognised option '-%s'.", optopt ?
			      (char *) &(optopt) : argv[optind - 1] + 1);
			break;
		default:
			break;
		}
	}

	retval = validate_flags(flags);
	if (retval != 0)
		return retval;

	if (output) {
		out = open(output, O_CREAT | O_TRUNC | O_WRONLY, 0644);
		if (out == -1) {
			error("failed to open '%s' [%d].", output, errno);
			return errno;
		}
	}

	while (optind < argc) {
		const char *arg = argv[optind++];

		retval = process_string(out, arg, flags);
		if (retval != 0)
			return retval;
		write(out, "\n", 1);
 	}

	for (i = 0; i < nfiles; i++) {
		if (strcmp(files[i], "-") == 0) {
			retval = read_fd(&input, STDIN_FILENO);
		} else {
			retval = read_file(&input, files[i]);
		}

		if (retval != 0) {
			error("failed to read file '%s'.", files[i]);
			return retval;
		}
		retval = process_string(out, input, flags);
		free(input);
		if (retval != 0)
			return retval;
	}

	return 0;
}