/*  libgrbs - geometric rubber band sketch model
    Copyright (C) 2024  Tibor 'Igor2' Palinkas
    (Supported by NLnet NGI0 Entrust in 2024)

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 31 Milk Street, # 960789 Boston, MA 02196 USA

    Contact:
      Project page: http://repo.hu/projects/libgrbs
      lead developer: http://repo.hu/projects/pcb-rnd/contact.html
*/

#include "config.h"

#include <genht/htpp.h>
#include <genht/hash.h>

#include "grbs.h"
#include "snapshot.h"

#include <assert.h>

#if 1
#	include <stdio.h>
#	define tprintf printf
#else
#	define tprintf grbs_nullprintf
#endif

/*** save ***/

GRBS_INLINE void save_obj(void **ptr, void *dst, void *src, long size)
{
	*ptr = src;
	memcpy(dst, src, size);
}

GRBS_INLINE void save_2nets(grbs_snapshot_t *snap)
{
	grbs_t *grbs = snap->grbs;
	grbs_2net_t *src, *dst;
	void **ptr;

	for(src = gdl_first(&grbs->all_2nets), dst = snap->saved_2net, ptr = snap->ptr_2net; src != NULL; src = gdl_next(&grbs->all_2nets, src), dst++, ptr++)
		save_obj(ptr, dst, src, sizeof(grbs_2net_t));
}

GRBS_INLINE void save_points(grbs_snapshot_t *snap)
{
	grbs_t *grbs = snap->grbs;
	grbs_point_t *src, *dst;
	void **ptr;

	for(src = gdl_first(&grbs->all_points), dst = snap->saved_point, ptr = snap->ptr_point; src != NULL; src = gdl_next(&grbs->all_points, src), dst++, ptr++)
		save_obj(ptr, dst, src, sizeof(grbs_point_t));
}




GRBS_INLINE void save_arcs(grbs_snapshot_t *snap)
{
	grbs_t *grbs = snap->grbs;
	grbs_arc_t *src, *dst;
	void **ptr;

	for(src = gdl_first(&grbs->all_arcs), dst = snap->saved_arc, ptr = snap->ptr_arc; src != NULL; src = gdl_next(&grbs->all_arcs, src), dst++, ptr++)
		save_obj(ptr, dst, src, sizeof(grbs_arc_t));
}

GRBS_INLINE void save_lines(grbs_snapshot_t *snap)
{
	grbs_t *grbs = snap->grbs;
	grbs_line_t *src, *dst;
	void **ptr;

	for(src = gdl_first(&grbs->all_lines), dst = snap->saved_line, ptr = snap->ptr_line; src != NULL; src = gdl_next(&grbs->all_lines, src), dst++, ptr++)
		save_obj(ptr, dst, src, sizeof(grbs_line_t));
}



grbs_snapshot_t *grbs_snapshot_save(grbs_t *grbs)
{
	long N2nets  = gdl_length(&grbs->all_2nets);
	long Npoints = gdl_length(&grbs->all_points);
	long Narcs   = gdl_length(&grbs->all_arcs);
	long Nlines  = gdl_length(&grbs->all_lines);
	long size = 0;
	char *end;
	grbs_snapshot_t *snap;


	/* allocate */
	size += N2nets * (sizeof(grbs_2net_t) + sizeof(void *));
	size += Npoints * (sizeof(grbs_point_t) + sizeof(void *));
	size += Narcs * (sizeof(grbs_arc_t) + sizeof(void *));
	size += Nlines * (sizeof(grbs_line_t) + sizeof(void *));

	snap = malloc(size + sizeof(grbs_snapshot_t));
	snap->grbs = grbs;

	end = snap->buff;
	snap->saved_2net  = (void *)end; end += N2nets * sizeof(grbs_2net_t);
	snap->saved_point = (void *)end; end += Npoints * sizeof(grbs_point_t);
	snap->saved_arc   = (void *)end; end += Narcs * sizeof(grbs_arc_t);
	snap->saved_line  = (void *)end; end += Nlines * sizeof(grbs_line_t);

	snap->ptr_2net    = (void *)end; end += N2nets * sizeof(void *);
	snap->ptr_point   = (void *)end; end += Npoints * sizeof(void *);
	snap->ptr_arc     = (void *)end; end += Narcs * sizeof(void *);
	snap->ptr_line    = (void *)end; end += Nlines * sizeof(void *);

	tprintf("size = %ld k (alloced=%ld used=%ld)\n", size/1024, size, end-snap->buff);

	assert(end <= snap->buff + size);

	save_2nets(snap);
	save_points(snap);
	save_arcs(snap);
	save_lines(snap);

	memcpy(&snap->save_all_2nets, &grbs->all_2nets, sizeof(gdl_list_t));
	memcpy(&snap->save_all_points, &grbs->all_points, sizeof(gdl_list_t));
	memcpy(&snap->save_all_arcs, &grbs->all_arcs, sizeof(gdl_list_t));
	memcpy(&snap->save_all_lines, &grbs->all_lines, sizeof(gdl_list_t));

	snap->N2nets  = N2nets;
	snap->Npoints = Npoints;
	snap->Narcs   = Narcs;
	snap->Nlines  = Nlines;

	snap->orig_uids = grbs->uids;

	return snap;
}

/*** restore ***/

GRBS_INLINE void restore_load_tmp(grbs_snapshot_t *snap, htpp_t *tmp, gdl_list_t *lst)
{
	void *o;

	for(o = gdl_first(lst); o != NULL; o = gdl_next(lst, o))
		htpp_set(tmp, o, o);
}

GRBS_INLINE void restore_obj(grbs_snapshot_t *snap, htpp_t *tmp, void *dst, void *src, long obj_size)
{
	htpp_pop(tmp, dst);
	memcpy(dst, src, obj_size);
}

GRBS_INLINE void restore_2nets(grbs_snapshot_t *snap, htpp_t *tmp)
{
	grbs_t *grbs = snap->grbs;
	htpp_entry_t *e;
	long n;

	restore_load_tmp(snap, tmp, &grbs->all_2nets);
	restore_load_tmp(snap, tmp, &grbs->free_2nets);
	memset(&grbs->free_2nets, 0, sizeof(gdl_list_t));

	for(n = 0; n < snap->N2nets; n++)
		restore_obj(snap, tmp, snap->ptr_2net[n], snap->saved_2net+n, sizeof(grbs_2net_t));

	/* put any unused 2nets to the free list */
	memset(&grbs->free_2nets, 0, sizeof(gdl_list_t));
	for(e = htpp_first(tmp); e != NULL; e = htpp_next(tmp, e)) {
		grbs_2net_t *o = e->key;
		memset(&o->link_2nets, 0, sizeof(gdl_elem_t));
		gdl_append(&grbs->free_2nets, o, link_2nets);
	}

	htpp_clear(tmp);
}

GRBS_INLINE void restore_points(grbs_snapshot_t *snap, htpp_t *tmp)
{
	grbs_t *grbs = snap->grbs;
	htpp_entry_t *e;
	long n;

	restore_load_tmp(snap, tmp, &grbs->all_points);
	restore_load_tmp(snap, tmp, &grbs->free_points);
	memset(&grbs->free_points, 0, sizeof(gdl_list_t));

	for(n = 0; n < snap->Npoints; n++)
		restore_obj(snap, tmp, snap->ptr_point[n], snap->saved_point+n, sizeof(grbs_point_t));

	/* put any unused points to the free list */
	memset(&grbs->free_points, 0, sizeof(gdl_list_t));
	for(e = htpp_first(tmp); e != NULL; e = htpp_next(tmp, e)) {
		grbs_point_t *o = e->key;
		memset(&o->link_points, 0, sizeof(gdl_elem_t));
		gdl_append(&grbs->free_points, o, link_points);
	}

	htpp_clear(tmp);
}



GRBS_INLINE void restore_arcs(grbs_snapshot_t *snap, htpp_t *tmp)
{
	grbs_t *grbs = snap->grbs;
	htpp_entry_t *e;
	long n;

	restore_load_tmp(snap, tmp, &grbs->all_arcs);
	restore_load_tmp(snap, tmp, &grbs->free_arcs);
	memset(&grbs->free_arcs, 0, sizeof(gdl_list_t));

	for(n = 0; n < snap->Narcs; n++)
		restore_obj(snap, tmp, snap->ptr_arc[n], snap->saved_arc+n, sizeof(grbs_arc_t));

	/* put any unused arcs to the free list */
	memset(&grbs->free_arcs, 0, sizeof(gdl_list_t));
	for(e = htpp_first(tmp); e != NULL; e = htpp_next(tmp, e)) {
		grbs_arc_t *o = e->key;
		memset(&o->link_arcs, 0, sizeof(gdl_elem_t));
		gdl_append(&grbs->free_arcs, o, link_arcs);
	}

	htpp_clear(tmp);
}

GRBS_INLINE void restore_lines(grbs_snapshot_t *snap, htpp_t *tmp)
{
	grbs_t *grbs = snap->grbs;
	htpp_entry_t *e;
	long n;

	restore_load_tmp(snap, tmp, &grbs->all_lines);
	restore_load_tmp(snap, tmp, &grbs->free_lines);
	memset(&grbs->free_lines, 0, sizeof(gdl_list_t));

	for(n = 0; n < snap->Nlines; n++)
		restore_obj(snap, tmp, snap->ptr_line[n], snap->saved_line+n, sizeof(grbs_line_t));

	/* put any unused lines to the free list */
	memset(&grbs->free_lines, 0, sizeof(gdl_list_t));
	for(e = htpp_first(tmp); e != NULL; e = htpp_next(tmp, e)) {
		grbs_line_t *o = e->key;
		memset(&o->link_lines, 0, sizeof(gdl_elem_t));
		gdl_append(&grbs->free_lines, o, link_lines);
	}

	htpp_clear(tmp);
}

GRBS_INLINE void rebuild_rtree(grbs_snapshot_t *snap, grbs_rtree_t *tree, gdl_list_t *all)
{
	void *o;
	grbs_rtree_uninit(tree);
	grbs_rtree_init(tree);
	for(o = gdl_first(all); o != NULL; o = gdl_next(all, o))
		grbs_rtree_insert(tree, o, o);
}

int grbs_snapshot_restore(grbs_snapshot_t *snap)
{
	grbs_t *grbs = snap->grbs;
	htpp_t tmp;

	htpp_init(&tmp, ptrhash, ptrkeyeq);

	restore_2nets(snap, &tmp);
	restore_points(snap, &tmp);
	restore_arcs(snap, &tmp);
	restore_lines(snap, &tmp);

	memcpy(&grbs->all_2nets, &snap->save_all_2nets, sizeof(gdl_list_t));
	memcpy(&grbs->all_points, &snap->save_all_points, sizeof(gdl_list_t));
	memcpy(&grbs->all_arcs, &snap->save_all_arcs, sizeof(gdl_list_t));
	memcpy(&grbs->all_lines, &snap->save_all_lines ,sizeof(gdl_list_t));

	htpp_uninit(&tmp);

	/* redo the rtrees */
	rebuild_rtree(snap, &grbs->line_tree, &grbs->all_lines);
	rebuild_rtree(snap, &grbs->arc_tree, &grbs->all_arcs);
	rebuild_rtree(snap, &grbs->point_tree, &grbs->all_points);

	return 0;
}

void grbs_snapshot_cmp(grbs_snapshot_t *snap, void (*cmp_cb)(void *uctx, grbs_obj_type_t type, void *orig_obj, void *new_obj), void *uctx)
{
	grbs_t *grbs = snap->grbs;
	long n;
	grbs_point_t *p;
	grbs_arc_t *a;
	grbs_line_t *l;

	/* announce diffs on existing objects */
	for(n = 0; n < snap->Npoints; n++) {
		grbs_point_t *old = snap->saved_point+n, *new = snap->ptr_point[n];
		if (old->uid < 0) old = NULL;
		if (new->uid < 0) new = NULL;
		if ((old != NULL) || (new != NULL))
			cmp_cb(uctx, GRBS_POINT, old, new);
	}
	for(n = 0; n < snap->Narcs; n++) {
		grbs_arc_t *old = snap->saved_arc+n, *new = snap->ptr_arc[n];
		if (!old->in_use || (old->uid < 0)) old = NULL;
		if (!new->in_use || (new->uid < 0)) new = NULL;
		if ((old != NULL) || (new != NULL))
			cmp_cb(uctx, GRBS_ARC, old, new);
	}
	for(n = 0; n < snap->Nlines; n++) {
		grbs_line_t *old = snap->saved_line+n, *new = snap->ptr_line[n];
		if (old->uid < 0) old = NULL;
		if ((new->uid < 0) || (new->uid > snap->orig_uids)) new = NULL;
		if ((old != NULL) || (new != NULL))
			cmp_cb(uctx, GRBS_LINE, old, new);
	}

	/* announce new arcs */
	for(p = gdl_first(&grbs->all_points); p != NULL; p = gdl_next(&grbs->all_points, p))
		if (p->uid > snap->orig_uids)
			cmp_cb(uctx, GRBS_POINT, NULL, p);
	for(a = gdl_first(&grbs->all_arcs); a != NULL; a = gdl_next(&grbs->all_arcs, a))
		if ((a->uid > snap->orig_uids) && a->in_use)
			cmp_cb(uctx, GRBS_ARC, NULL, a);
	for(l = gdl_first(&grbs->all_lines); l != NULL; l = gdl_next(&grbs->all_lines, l))
		if (l->uid > snap->orig_uids)
			cmp_cb(uctx, GRBS_LINE, NULL, l);
}


/*** free ***/

void grbs_snapshot_free(grbs_snapshot_t *snap)
{
	free(snap);
}
