#include "object.h"
#include "blob.h"
#include "tree.h"
#include "commit.h"
#include "tag.h"
#include "delta.h"
#include "cache.h"

/* the delta object definition (it can alias any other object) */
struct delta {
	union {
		struct object object;
		struct blob blob;
		struct tree tree;
		struct commit commit;
		struct tag tag;
	} u;
};

struct delta *lookup_delta(const unsigned char *sha1)
{
	struct object *obj = lookup_object(sha1);
	if (!obj) {
		struct delta *ret = xmalloc(sizeof(struct delta));
		memset(ret, 0, sizeof(struct delta));
		created_object(sha1, &ret->u.object);
		return ret;
	}
	return (struct delta *) obj;
}

int parse_delta_buffer(struct delta *item, void *buffer, unsigned long size)
{
	struct object *reference;
	struct object_list *p;

	if (item->u.object.delta)
		return 0;
	item->u.object.delta = 1;
	if (size <= 20)
		return -1;
	reference = lookup_object(buffer);
	if (!reference) {
		struct delta *ref = xmalloc(sizeof(struct delta));
		memset(ref, 0, sizeof(struct delta));
		created_object(buffer, &ref->u.object);
		reference = &ref->u.object;
	}

	p = xmalloc(sizeof(*p));
	p->item = &item->u.object;
	p->next = reference->attached_deltas;
	reference->attached_deltas = p;
	return 0;
}

int process_deltas(void *src, unsigned long src_size, const char *src_type,
		   struct object_list *delta_list)
{
	int deepest = 0;
	do {
		struct object *obj = delta_list->item;
		static char type[10];
		void *map, *delta, *buf;
		unsigned long map_size, delta_size, buf_size;
		map = map_sha1_file(obj->sha1, &map_size);
		if (!map)
			continue;
		delta = unpack_sha1_file(map, map_size, type, &delta_size);
		munmap(map, map_size);
		if (!delta)
			continue;
		if (strcmp(type, "delta") || delta_size <= 20) {
			free(delta);
			continue;
		}
		buf = patch_delta(src, src_size,
				  delta+20, delta_size-20,
				  &buf_size);
		free(delta);
		if (!buf)
			continue;
		if (check_sha1_signature(obj->sha1, buf, buf_size, src_type) < 0)
			printf("sha1 mismatch for delta %s\n", sha1_to_hex(obj->sha1));
		if (obj->type && obj->type != src_type) {
			error("got %s when expecting %s for delta %s",
			      src_type, obj->type, sha1_to_hex(obj->sha1));
			free(buf);
			continue;
		}
		obj->type = src_type;
		if (src_type == blob_type) {
			parse_blob_buffer((struct blob *)obj, buf, buf_size);
		} else if (src_type == tree_type) {
			parse_tree_buffer((struct tree *)obj, buf, buf_size);
		} else if (src_type == commit_type) {
			parse_commit_buffer((struct commit *)obj, buf, buf_size);
		} else if (src_type == tag_type) {
			parse_tag_buffer((struct tag *)obj, buf, buf_size);
		} else {
			error("unknown object type %s", src_type);
			free(buf);
			continue;
		}
		if (obj->attached_deltas) {
			int depth = process_deltas(buf, buf_size, src_type,
						   obj->attached_deltas);
			if (deepest < depth)
				deepest = depth;
		}
		free(buf);
	} while ((delta_list = delta_list->next));
	return deepest + 1;
}
