/*****************************************************************************
 * asa: portable digital subtitle renderer
 *****************************************************************************
 * Copyright (C) 2007  David Lamparter
 *
 * 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.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
#include "acconf.h"
#endif

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <math.h>

#include <csri/csri.h>
#include <csri/logging.h>
#include <csri/openerr.h>
#include <csri/stream.h>

#include "render.h"

csri_rend *r;

static int do_usage(FILE *fd)
{
	fprintf(fd, "usage: csri [COMMON-OPTIONS] COMMAND [COMMAND-OPTIONS]\n"
		"\n"
		"common options: [-r renderer [-s specific]]\n"
		"\t-r\tselect a renderer, by name\n"
		"\t-s\tselect a renderer version, by specific name\n"
		"\n"
		"commands:\n"
		"\tlist\tshow installed renderers\n"
		"\tinfo\tshow detailed renderer information\n"
		"\trender\trender subtitle output\n"
#ifdef HAVE_LIBPNG
		"\t\t-i FILE\t\tread background from PNG file\n"
		"\t\t-o PREFIX\twrite output to PREFIX_nnnn.png\n"
#endif
		"\t\t-A\t\tkeep alpha\n"
		"\t\t-t [start][:[end][:[step]]]\tspecify timestamps to be rendered\n"
		"\t\tSUBFILE\t\tsubtitle file to load\n"
		"\n");
	return 2;
}

static int do_list(int argc, char **argv)
{
	unsigned num = 0;

	if (argc)
		return do_usage(stderr);

	while (r) {
		struct csri_info *info = csri_renderer_info(r);
		if (!info)
			continue;
		printf("%s:%s %s, %s, %s\n", info->name, info->specific,
			info->longname, info->author, info->copyright);
		r = csri_renderer_next(r);
		num++;
	}
	fprintf(stderr, "%u renderers found\n", num);
	return num > 0 ? 0 : 1;
}

static csri_ext_id known_exts[] = {
	CSRI_EXT_OPENERR,
	CSRI_EXT_LOGGING,
	CSRI_EXT_STREAM,
	CSRI_EXT_STREAM_ASS,
	CSRI_EXT_STREAM_TEXT,
	CSRI_EXT_STREAM_DISCARD,
	NULL
};

static const char *dummy_script = "[Script Info]\r\n"
	"ScriptType: v4.00\r\n"
	"[V4 Styles]\r\n"
	"Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, "
		"TertiaryColour, BackColour, Bold, Italic, BorderStyle, "
		"Outline, Shadow, Alignment, MarginL, MarginR, MarginV, "
		"AlphaLevel, Encoding\r\n"
	"Style: Default,Arial,20,&HFFFFFF&,&H00FFFF&,&H000000&,&H000000&,"
		"0,0,1,2,2,2,10,10,10,0,0\r\n"
	"[Events]\r\n"
	"Format: Marked, Start, End, Style, Name, "
		"MarginL, MarginR, MarginV, Effect, Text\r\n"
	"Dialogue: Marked=0,0:00:01.00,0:00:02.00,Default,,0000,0000,0000,,"
		"test\r\n";

static const char *dummy_stream = "1,0,Default,,0000,0000,0000,,stream\r\n";

#define e(x) { #x, CSRI_F_ ## x }
#define ree(x) e(RGB ## x), e(x ## RGB), e(BGR ## x), e(x ## BGR)
static struct csri_fmtlistent {
	const char *label;
	enum csri_pixfmt fmt;
} csri_fmts[] = {
	ree(A), ree(_),
	e(RGB), e(BGR),
	e(AYUV), e(YUVA), e(YVUA), e(YUY2), e(YV12A), e(YV12),
	{ NULL, 0 }
};

static void listfmts()
{
	csri_inst *i;
	struct csri_fmtlistent *fmt;
	struct csri_fmt f;

	printf("\ntrying to get list of supported colorspaces:\n");
	fflush(stdout);
	i = csri_open_mem(r, dummy_script, strlen(dummy_script), NULL);

	f.width = f.height = 256;
	for (fmt = csri_fmts; fmt->label; fmt++) {
		f.pixfmt = fmt->fmt;
		if (!csri_request_fmt(i, &f))
			printf("\t[%04x] %s\n", fmt->fmt, fmt->label);
	}
	csri_close(i);
}

static int do_info(int argc, char **argv)
{
	struct csri_info *info;
	csri_ext_id *id;
	if (argc)
		return do_usage(stderr);

	info = csri_renderer_info(r);
	if (!info)
		return 1;
	printf("%s:%s\n\t%s\n\t%s\n\t%s\n", info->name, info->specific,
		info->longname, info->author, info->copyright);
	printf("supported CSRI extensions:\n");
	for (id = known_exts; *id; id++) {
		void *rext = csri_query_ext(r, *id);
		void *lext = csri_query_ext(NULL, *id);
		if (lext || rext) {
			printf("\t%s ", *id);
			if (!lext)
				printf("\n");
			else if (!rext)
				printf("[library only]\n");
			else if (rext == lext)
				printf("[emulated by library]\n");
			else
				printf("\n");
		}
	}
	listfmts();
	return 0;
}

static void logfunc(void *appdata, enum csri_logging_severity sev,
	const char *message)
{
	char severity[32];
	switch (sev) {
	case CSRI_LOG_DEBUG:	strcpy(severity, "[debug]"); break;
	case CSRI_LOG_INFO:	strcpy(severity, "[info]"); break;
	case CSRI_LOG_NOTICE:	strcpy(severity, "[notice]"); break;
	case CSRI_LOG_WARNING:	strcpy(severity, "[warning]"); break;
	case CSRI_LOG_ERROR:	strcpy(severity, "[error]"); break;
	default:	snprintf(severity, 32, "[%d?]", (int)sev);
	}
	fprintf(stderr, "%-10s %s\n", severity, message);
}

static int real_render(double *times, const char *infile, const char *outfile,
	const char *script, enum csri_pixfmt pfmt)
{
	struct csri_frame *bg, *a;
	csri_inst *inst;
	struct csri_fmt fmt;
	double now;
	int idx;
	uint32_t width = 640, height = 480;

	bg = infile ? png_load(infile, &width, &height, pfmt)
		: frame_alloc(width, height, pfmt);
	a = frame_alloc(width, height, pfmt);
	if (!bg || !a) {
		fprintf(stderr, "failed to allocate frame\n");
		if (!bg)
			fprintf(stderr, "\t- problem with background.\n");
		return 2;
	}
	inst = csri_open_file(r, script, NULL);
	if (!inst) {
		fprintf(stderr, "failed to open script \"%s\"\n", script);
		return 2;
	}
	fmt.pixfmt = pfmt;
	fmt.width = width;
	fmt.height = height;
	if (csri_request_fmt(inst, &fmt)) {
		fprintf(stderr, "format not supported by renderer\n");
		return 2;
	}

	idx = 0;
	for (now = times[0]; now <= times[1]; now += times[2]) {
		frame_copy(a, bg, width, height);
		csri_render(inst, a, now);
		if (outfile) {
			char buffer[256];
			snprintf(buffer, sizeof(buffer),
				 "%s_%04d.png", outfile, idx);
			printf("%s\n", buffer);
			png_store(a, buffer, width, height);
		}
		idx++;
	}
	csri_close(inst);
	inst = NULL;
	frame_free(bg);
	frame_free(a);
	return 0;
}

static int do_render(int argc, char **argv)
{
	double times[3] = {0.0, 0.0, 1.0};
	const char *outfile = NULL, *infile = NULL;
	struct csri_fmtlistent *fmte;
	int keepalpha = 0;
	enum csri_pixfmt fmt = ~0U;
	argv--, argc++;
	while (1) {
		int c, i;
		const char *short_options = "t:o:i:F:A";
		char *arg, *end, *err;
		struct option long_options[] = {
			{"time", 1, 0, 't'},
			{"output", 1, 0, 'o'},
			{"input", 1, 0, 'i'},
			{"format", 0, 0, 'F'},
			{"alpha", 0, 0, 'A'},
			{0, 0, 0, 0}
		};
		c = getopt_long(argc, argv, short_options, long_options, NULL);
		if (c == -1)
			break;
		switch (c) {
		case 't':
			arg = optarg;
			for (i = 0; i < 3; i++) {
				end = strchr(arg, ':');
				if (end)
					*end = '\0';
				if (*arg) {
					times[i] = strtod(arg, &err);
					if (*err) {
						fprintf(stderr,
							"invalid time: %s\n",
							arg);
						return do_usage(stderr);
					}
				}
				if (!end)
					break;
				arg = end + 1;
			}
			break;
		case 'i':
			infile = optarg;
			break;
		case 'o':
			outfile = optarg;
			break;
		case 'A':
			if (fmt != ~0U)
				return do_usage(stderr);
			keepalpha = 1;
			break;
		case 'F':
			if (keepalpha || fmt != ~0U)
				return do_usage(stderr);
			for (fmte = csri_fmts; fmte->label; fmte++)
				if (!strcmp(fmte->label, optarg))
					break;
			if (!fmte->label)
				return do_usage(stderr);
			fmt = fmte->fmt;
			break;
		default:
			return do_usage(stderr);
		};
	}
	if (fmt == ~0U)
		fmt = keepalpha ? CSRI_F_RGBA : CSRI_F_RGB_;
	if (!isfinite(times[0])) {
		fprintf(stderr, "invalid start time\n");
		return do_usage(stderr);
	}
	if (!isfinite(times[1]) || times[1] < times[0]) {
		fprintf(stderr, "invalid end time\n");
		return do_usage(stderr);
	}
	if (!isnormal(times[2]) || times[2] < 0.0) {
		fprintf(stderr, "invalid end time\n");
		return do_usage(stderr);
	}
	if (argc - optind != 1) {
		fprintf(stderr, "script name missing\n");
		return do_usage(stderr);
	}
	return real_render(times, infile, outfile, argv[optind], fmt);
}

static int do_streamtest(int argc, char **argv)
{
	const char *outfile = NULL;
	struct csri_fmtlistent *fmte;
	enum csri_pixfmt pfmt = ~0U;
	struct csri_frame *bg, *a;
	csri_inst *inst;
	struct csri_fmt fmt;
	uint32_t width = 640, height = 480;
	struct csri_stream_ext *sext;

	argv--, argc++;
	while (1) {
		int c;
		const char *short_options = "o:F:";
		struct option long_options[] = {
			{"output", 1, 0, 'o'},
			{"format", 0, 0, 'F'},
			{0, 0, 0, 0}
		};
		c = getopt_long(argc, argv, short_options, long_options, NULL);
		if (c == -1)
			break;
		switch (c) {
		case 'o':
			outfile = optarg;
			break;
		case 'F':
			if (pfmt != ~0U)
				return do_usage(stderr);
			for (fmte = csri_fmts; fmte->label; fmte++)
				if (!strcmp(fmte->label, optarg))
					break;
			if (!fmte->label)
				return do_usage(stderr);
			pfmt = fmte->fmt;
			break;
		default:
			return do_usage(stderr);
		};
	}
	if (pfmt == ~0U)
		pfmt = CSRI_F_RGB_;

	sext = (struct csri_stream_ext *)csri_query_ext(r,
		CSRI_EXT_STREAM_ASS);
	if (!sext) {
		fprintf(stderr, "renderer does not support ASS streaming\n");
		return 2;
	}

	bg = frame_alloc(width, height, pfmt);
	a = frame_alloc(width, height, pfmt);
	if (!bg || !a) {
		fprintf(stderr, "failed to allocate frame\n");
		return 2;
	}
	inst = sext->init_stream(r, dummy_script, strlen(dummy_script), NULL);
	if (!inst) {
		fprintf(stderr, "failed to initialize stream\n");
		return 2;
	}
	fmt.pixfmt = pfmt;
	fmt.width = width;
	fmt.height = height;
	if (csri_request_fmt(inst, &fmt)) {
		fprintf(stderr, "format not supported by renderer\n");
		return 2;
	}

	frame_copy(a, bg, width, height);
	csri_render(inst, a, 1.75);
	if (outfile) {
		char buffer[256];
		snprintf(buffer, sizeof(buffer),
			 "%s_nstream.png", outfile);
		printf("%s\n", buffer);
		png_store(a, buffer, width, height);
	}

	frame_copy(a, bg, width, height);
	sext->push_packet(inst, dummy_stream, strlen(dummy_stream),
		1.5, 2.0);
	csri_render(inst, a, 1.75);
	if (outfile) {
		char buffer[256];
		snprintf(buffer, sizeof(buffer),
			 "%s_stream.png", outfile);
		printf("%s\n", buffer);
		png_store(a, buffer, width, height);
	}

	csri_close(inst);
	inst = NULL;
	frame_free(bg);
	frame_free(a);
	return 0;
}

int main(int argc, char **argv)
{
	struct csri_logging_ext *logext;

	if (argc < 2)
		return do_usage(stderr);

	logext = (struct csri_logging_ext *)csri_query_ext(NULL,
		CSRI_EXT_LOGGING);
	if (logext && logext->set_logcallback)
		logext->set_logcallback(logfunc, NULL);
	else
		fprintf(stderr, "warning: unable to set log callback\n");

	r = csri_renderer_default();
	argc--, argv++;
	if (!strcmp(argv[0], "list"))
		return do_list(argc - 1, argv + 1);
	if (!strcmp(argv[0], "-r")) {
		const char *name = NULL, *spec = NULL;
		if (argc < 2)
			return do_usage(stderr);
		name = argv[1];
		argc -= 2, argv += 2;
		if (!strcmp(argv[0], "-s")) {
			if (argc < 2)
				return do_usage(stderr);
			spec = argv[1];
		}
		r = csri_renderer_byname(name, spec);
		if (!r) {
			fprintf(stderr, "renderer %s:%s not found.\n",
				name, spec ? spec : "*");
			return 2;
		}
	}
	if (!strcmp(argv[0], "info"))
		return do_info(argc - 1, argv + 1);
	if (!strcmp(argv[0], "render"))
		return do_render(argc - 1, argv + 1);
	if (!strcmp(argv[0], "streamtest"))
		return do_streamtest(argc - 1, argv + 1);
	return do_usage(stderr);
}