/* Revision objects implementation */
/* $Id: revision.c,v 1.19 2003/08/02 21:41:55 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.
 */

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

#include <librev/branch.h>
#include <librev/dataobj.h>
#include <librev/delta.h>
#include <librev/librev.h>
#include <librev/list.h>
#include <librev/revision.h>


struct rev_revision *
rev_revision_init (struct rev_branch *branch)
{
	struct rev_revision *obj = calloc (1, sizeof (struct rev_revision));

	if (! obj) return NULL;

	obj->number = -1;
	rev_revision_set_branch (obj, branch);

	rev_list_init (obj->offsprings);

	return obj;
}

struct rev_revision *
rev_revision_allocate (struct rev_branch *branch, int number)
{
	struct rev_revision *obj = calloc (1, sizeof (struct rev_revision));

	if (! obj) return NULL;

	rev_revision_set_number (obj, number);
	rev_revision_set_branch (obj, branch);

	rev_list_init (obj->offsprings);

	return obj;
}

void
rev_revision_done (struct rev_revision *obj)
{
	struct rev_branch *branch;

	assert (obj && ! obj->refcount);

	if (obj->udata && obj->userops && obj->userops->done)
		obj->userops->done (obj);
	if (obj->data && obj->dataops && obj->dataops->done)
		obj->dataops->done (obj);

	rev_revision_set_comment (obj, NULL);
	rev_revision_set_author (obj, NULL);
	rev_revision_set_ctime (obj, NULL);

	rev_revision_set_delta_forw (obj, NULL);
	rev_revision_set_delta_rev (obj, NULL);
	rev_revision_set_dataobj (obj, NULL);

	rev_list_foreach (obj->offsprings, branch) {
		rev_branch_set_parent (branch, NULL);
	} rev_list_foreach_end;

	rev_revision_set_branch (obj, NULL);

	free (obj);
}

struct rev_branch *
rev_revision_find_branch (struct rev_revision *obj, int number)
{
	struct rev_branch *branch;

	assert (obj);

	rev_list_foreach (obj->offsprings, branch) {
		if (rev_branch_get_number (branch) == number)
			return branch;
	} rev_list_foreach_end;

	return NULL;
}

static int
rev_revision_propagate_dataobj_do (struct rev_revision *obj,
				   struct rev_revision *from)
{
	/* This is a stupid performance-consuming way to do things, but it is
	 * by far the simplest one so far. We can optimize this later by doing
	 * a recursive search through the DAG for a dataobj connected by
	 * continuous diffs with this revision. We could certainly use some
	 * backend hinting for that and also do some diffs merging etc,
	 * whatever. This is simplest so far ;-). In some pathological cases,
	 * it can be even the most effective (rev A -> F, then later we also
	 * want D; we'd have to do the traversal again; on the other side, this
	 * case is not gonna happen, IMHO :). */

#define spread_predef_dataobj(dir_,deltadir_) \
do { \
	if (! dir_##obj->dataobj && dir_##obj->delta_##deltadir_) { \
		rev_delta_set_dataobj1 (dir_##obj->delta_##deltadir_, \
					obj->dataobj); \
		if (rev_delta_apply (dir_##obj->delta_##deltadir_) > 0) { \
			dir_##obj->dataobj = \
				dir_##obj->delta_##deltadir_->dataobj2; \
			assert (dir_##obj->dataobj); \
			rev_dataobj_associate (dir_##obj->dataobj); \
		} \
	} \
\
	if (dir_##obj != from) \
		rev_revision_propagate_dataobj_do (dir_##obj, obj); \
} while (0)

#define spread_dataobj(dir_,deltadir_) \
do { \
	struct rev_revision *dir_##obj = obj->rev.dir_->listee; \
\
	spread_predef_dataobj (dir_, deltadir_); \
} while (0)

	if (! obj->dataobj) return 0;

	assert (obj->branch);

	/* TODO: Reverse deltas? */

	{
		struct rev_branch *branch;

		/* Go sideways */
		rev_list_foreach (obj->offsprings, branch) {
			struct rev_revision *sideobj;

			sideobj = rev_list_first (branch->revs);
			spread_predef_dataobj (side, forw);
		} rev_list_foreach_end;
	}

	if (rev_list_last (obj->branch->revs) != obj) {
		/* Go forth */
		spread_dataobj (next, forw);
	}

	if (rev_list_first (obj->branch->revs) != obj) {
		/* Go back */
		spread_dataobj (prev, rev);

	} else if (! rev_revision_is_dummy (obj->branch->parent)) {
		/* Go faar faar away */
		struct rev_revision *ancientobj = obj->branch->parent;

		spread_predef_dataobj (ancient, rev);
	}

	return 1;
#undef spread_dataobj
#undef spread_predef_dataobj
}

int
rev_revision_propagate_dataobj (struct rev_revision *obj)
{
	return rev_revision_propagate_dataobj_do (obj, NULL);
}

void
rev_revision_set_branch (struct rev_revision *obj, struct rev_branch *branch)
{
	assert (obj);

	if (obj->branch) {
		rev_list_delete (*obj, rev);

		if (! branch) {
			/* We just removed the revision from a branch. */
			obj->branch = NULL;
			rev_revision_dissociate (obj);
			return;
		}

	} else {
		if (branch) {
			/* We just added the revision to an branch. */
			rev_revision_associate (obj);
		}
	}

	obj->branch = branch;
	if (! obj->branch) {
		return;
	}

	if (obj->number < 0) {
		/* Take care about the order here, rev_list_add() changes
		 * rev_list_last()'s return value. */

		/* XXX: This is probably a stupid idea. Remove me. */
		if (rev_list_empty (branch->revs))
			rev_revision_set_number (obj, 1);
		else
			rev_revision_set_number (obj,
				((struct rev_revision *)
				 rev_list_first (branch->revs))->number + 1);

		rev_list_add_after (branch->revs.item, *obj, rev);

	} else {
		struct rev_list_item *last_rev = &branch->revs.item;
		struct rev_revision *rev;

		rev_list_foreachback (branch->revs, rev) {
			if (rev->number < obj->number) break;
			last_rev = &rev->rev;
		} rev_list_foreach_end;

		rev_list_add_before (*last_rev, *obj, rev);
	}
}
