/*
 * Copyright (c) Kristaps Dzonsons <kristaps@bsd.lv>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include "config.h"

#if HAVE_SYS_QUEUE
# include <sys/queue.h>
#endif

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include "lowdown.h"
#include "extern.h"

/*
 * Starting size for input and output buffers.
 */
#define HBUF_START_BIG 4096

/*
 * Starting size for metadata buffers.
 */
#define HBUF_START_SMALL 128

/*
 * Return FALSE on failure, TRUE on success.
 */
static int
lowdown_render(const struct lowdown_opts *opts,
	struct lowdown_buf *ob, const struct lowdown_node *n)
{
	void	*rndr;
	int	 c = 0;

	switch (opts == NULL ? LOWDOWN_HTML : opts->type) {
	case LOWDOWN_GEMINI:
		if ((rndr = lowdown_gemini_new(opts)) == NULL)
			return 0;
		c = lowdown_gemini_rndr(ob, rndr, n);
		lowdown_gemini_free(rndr);
		break;
	case LOWDOWN_HTML:
		if ((rndr = lowdown_html_new(opts)) == NULL)
			return 0;
		c = lowdown_html_rndr(ob, rndr, n);
		lowdown_html_free(rndr);
		break;
	case LOWDOWN_LATEX:
		if ((rndr = lowdown_latex_new(opts)) == NULL)
			return 0;
		c = lowdown_latex_rndr(ob, rndr, n);
		lowdown_latex_free(rndr);
		break;
	case LOWDOWN_MAN:
	case LOWDOWN_MDOC:
	case LOWDOWN_MS:
	case LOWDOWN_NROFF:
		if ((rndr = lowdown_roff_new(opts)) == NULL)
			return 0;
		c = lowdown_roff_rndr(ob, rndr, n);
		lowdown_roff_free(rndr);
		break;
	case LOWDOWN_FODT:
		if ((rndr = lowdown_odt_new(opts)) == NULL)
			return 0;
		c = lowdown_odt_rndr(ob, rndr, n);
		lowdown_odt_free(rndr);
		break;
	case LOWDOWN_TERM:
		if ((rndr = lowdown_term_new(opts)) == NULL)
			return 0;
		c = lowdown_term_rndr(ob, rndr, n);
		lowdown_term_free(rndr);
		break;
	case LOWDOWN_TREE:
		c = lowdown_tree_rndr(ob, n, opts);
		break;
	default:
		c = 1;
		break;
	}

	return c;
}

int
lowdown_buf(const struct lowdown_opts *opts,
	const char *data, size_t datasz,
	char **res, size_t *rsz,
	struct lowdown_metaq *metaq)
{
	struct lowdown_buf	*ob = NULL;
	struct lowdown_doc	*doc;
	size_t			 maxn;
	enum lowdown_type	 t;
	struct lowdown_node	*n = NULL;
	int			 rc = 0;

	t = opts == NULL ? LOWDOWN_HTML : opts->type;

	if ((doc = lowdown_doc_new(opts)) == NULL)
		goto err;

	n = lowdown_doc_parse(doc, &maxn, data, datasz, metaq);
	if (n == NULL)
		goto err;
	assert(n->type == LOWDOWN_ROOT);

    	if (opts != NULL && (opts->oflags & LOWDOWN_SMARTY)) 
		if (!smarty(n, maxn, t))
			goto err;

	if ((ob = lowdown_buf_new(HBUF_START_BIG)) == NULL)
		goto err;

	if (!lowdown_render(opts, ob, n))
		goto err;

	*res = ob->data;
	*rsz = ob->size;
	ob->data = NULL;
	rc = 1;
err:
	lowdown_buf_free(ob);
	lowdown_node_free(n);
	lowdown_doc_free(doc);
	return rc;
}

int
lowdown_buf_diff(const struct lowdown_opts *opts,
	const char *new, size_t newsz,
	const char *old, size_t oldsz,
	char **res, size_t *rsz)
{
	struct lowdown_buf 	*ob = NULL;
	struct lowdown_doc 	*doc = NULL;
	enum lowdown_type 	 t;
	struct lowdown_node 	*nnew = NULL, *nold = NULL, 
				*ndiff = NULL;
	size_t			 maxn;
	int			 rc = 0;

	t = opts == NULL ? LOWDOWN_HTML : opts->type;

	if ((doc = lowdown_doc_new(opts)) == NULL)
		goto err;

	nnew = lowdown_doc_parse(doc, NULL, new, newsz, NULL);
	if (nnew == NULL)
		goto err;
	nold = lowdown_doc_parse(doc, NULL, old, oldsz, NULL);
	if (nold == NULL)
		goto err;

	ndiff = lowdown_diff(nold, nnew, &maxn);

    	if (opts != NULL && (opts->oflags & LOWDOWN_SMARTY)) 
		if (!smarty(ndiff, maxn, t))
			goto err;

	if ((ob = lowdown_buf_new(HBUF_START_BIG)) == NULL)
		goto err;

	if (!lowdown_render(opts, ob, ndiff))
		goto err;

	*res = ob->data;
	*rsz = ob->size;
	ob->data = NULL;
	rc = 1;
err:
	lowdown_buf_free(ob);
	lowdown_node_free(ndiff);
	lowdown_node_free(nnew);
	lowdown_node_free(nold);
	lowdown_doc_free(doc);
	return rc;
}

int
lowdown_file(const struct lowdown_opts *opts, FILE *fin,
	char **res, size_t *rsz, struct lowdown_metaq *metaq)
{
	struct lowdown_buf	*bin = NULL;
	int	 		 rc = 0;

	if ((bin = lowdown_buf_new(HBUF_START_BIG)) == NULL)
		goto out;
	if (!hbuf_putf(bin, fin))
		goto out;

	if (!lowdown_buf(opts,
	    bin->data, bin->size, res, rsz, metaq))
		goto out;
	rc = 1;
out:
	lowdown_buf_free(bin);
	return rc;
}

int
lowdown_file_diff(const struct lowdown_opts *opts,
	FILE *fnew, FILE *fold, char **res, size_t *rsz)
{
	struct lowdown_buf	*bnew = NULL, *bold = NULL;
	int	 		 rc = 0;

	if ((bnew = lowdown_buf_new(HBUF_START_BIG)) == NULL)
		goto out;
	if ((bold = lowdown_buf_new(HBUF_START_BIG)) == NULL)
		goto out;
	if (!hbuf_putf(bold, fold))
		goto out;
	if (!hbuf_putf(bnew, fnew))
		goto out;

	if (!lowdown_buf_diff(opts, 
	    bnew->data, bnew->size, 
	    bold->data, bold->size, 
	    res, rsz))
		goto out;
	rc = 1;
out:
	lowdown_buf_free(bnew);
	lowdown_buf_free(bold);
	return rc;
}

