/* RCS delta import/export implementation */
/* $Id: delta.c,v 1.6 2003/07/29 21:12:02 pasky Exp $ */

/*
 * Copyright (c) 2003  Petr Baudis.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * 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.
 *   * Neither the name of the author nor the names of the product's
 *     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 "AS IS" 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 OWNER 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.
 */

#define _GNU_SOURCE /* for the asprintf() hack */

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

#include <librcs/librcs.h>
#include <librev/data_text.h>
#include <librev/delta.h>


static int fetch_fragment (struct rev_delta_text *txtdelta, char **str);

struct rev_delta *
rev_delta_import_rcs (char *rcsdelta)
{
	struct rev_delta *delta = rev_delta_init ();
	struct rev_delta_text *txtdelta;

	assert (rcsdelta);

	if (! delta) return NULL;

	txtdelta = rev_delta_text_init (delta);
	if (! txtdelta) {
		rev_delta_done (delta);
		return NULL;
	}

	while (*rcsdelta) {
		if (fetch_fragment (txtdelta, &rcsdelta) < 0) {
			/* rev_delta_text is bound to rev_delta */
			rev_delta_done (delta);
			return NULL;
		}
	}

	return delta;
}

char *
rev_delta_export_rcs (struct rev_delta *delta)
{
	char *rcsdelta = NULL;
	struct rev_delta_text *txtdelta;
	struct rev_delta_text_fragment *fragment;

	assert (delta);

	txtdelta = delta->data;
	assert (txtdelta);

	/* TODO: Group fragments of same type. --pasky */

	rev_list_foreach (txtdelta->fragments, fragment) {
		enum rev_delta_text_fragment_type action;
		int pos;
		char *line, *str = NULL;

		action = rev_delta_text_fragment_get_type (fragment);
		pos = rev_delta_text_fragment_get_lineno (fragment) + 1;
		line = *rev_delta_text_fragment_get_line (fragment);

		if (action != DFT_ADD && action != DFT_REMOVE)
			continue;

		assert (pos > 0);

		if (action == DFT_ADD) assert (line);

		/* FIXME: Portability bottleneck! */
		if (asprintf (&str, "%s%c%d 1\n%s%s",
				rcsdelta ? rcsdelta : "",
				action == DFT_ADD ? 'a' : 'd',
				pos,
				action == DFT_ADD ? line : "",
				action == DFT_ADD ? "\n" : "")
		    < 0)
			continue;

		if (rcsdelta) free (rcsdelta);
		rcsdelta = str;
	} rev_list_foreach_end;

	return rcsdelta;
}



/* RCS diff format:
 * <action><pos> <count>
 * <action> can be 'd' (delete line) or 'a' (add line). In case of 'a', the
 * <count> lines follow.
 */

/* Load a fragment (in RCS sense, not librev sense). Returns zero if ok,
 * negative number if an error occured. */
static int
fetch_fragment (struct rev_delta_text *txtdelta, char **str)
{
	enum rev_delta_text_fragment_type action;
	int pos, count, i;

	assert (str && *str && **str);

	switch (*((*str)++)) {
		case 'a': action = DFT_ADD; break;
		case 'd': action = DFT_REMOVE; break;
		default: return -1;
	}

	pos = strtoul (*str, str, 10);
	if (*((*str)++) != ' ')
		return -2;

	count = strtoul (*str, str, 10);
	if (*((*str)++) != '\n')
		return -3;

	for (i = 0; i < count; i++) {
		char *line = NULL;

		if (action == DFT_ADD) {
			size_t l = strcspn (*str, "\n");

			line = malloc (l + 1);
			if (! line) return -4;
			strncpy (line, *str, l);
			line [l] = 0;

			(*str) += l + 1;
		}

		rev_delta_text_fragment_init (txtdelta, pos + i - 1, action,
						line);

		if (line) free (line);
	}

	return 0;
}
