/**********************************************************************
 
	Copyright (C) 2003 Hirohisa MORI <joshua@nichibun.ac.jp>
 
	This program is free software; you can redistribute it 
	and/or modify it under the terms of the GLOBALBASE 
	Library General Public License (G-LGPL) as published by 

	http://www.globalbase.org/
 
	This program 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.

**********************************************************************/


#include	<string.h>
#include	"stream.h"
#include	"memory_debug.h"
#include	"netmapper.h"

#define MAX_TARGET	2

NM_TARGET net_target[MAX_TARGET];
NM_VERTEX * vertex_head,* vertex_tail;

int
test_point(NAME_PTR * p,double x,double y)
{
	if ( p->x < x-1 )
		return -1;
	if ( p->x > x+1 )
		return -1;
	if ( p->y < y-1 )
		return -1;
	if ( p->y > y+1 )
		return -1;
	return 0;
}

int
cmp_ptr(NM_VERTEX * v1,NM_VERTEX * v2)
{
NAME_PTR * p1,* p2;
	p1 = v1->name;
	p2 = v2->name;
	if ( p1->x < p2->x )
		return -1;
	if ( p1->x > p2->x )
		return 1;
	if ( p1->y < p2->y )
		return -1;
	if ( p1->y > p2->y )
		return 1;
	return 0;
}

int
cmp_str(NM_VERTEX * v1,NM_VERTEX * v2)
{
	return l_strcmp(v1->name,v2->name);
}


void
init_target(NM_TARGET * t,short name_type)
{
	t->name_type = name_type;
	switch ( name_type ) {
	case NMT_PTR:
		t->avt_cmp = cmp_ptr;
		break;
	case NMT_STR:
		t->avt_cmp = cmp_str;
		break;
	default:
		er_panic("init_target");
	}
	t->tree = 0;
	t->vertex = 0;
}


NM_VERTEX *
new_vertex(NM_TARGET * t,void * name,int size)
{
NM_VERTEX * ret;
AVT_NODE * a;
	ret = d_alloc(sizeof(*ret),1);
	ret->name = d_alloc(size,2);
	ret->size = size;
	memcpy(ret->name,name,size);
	ret->edge = 0;
	ret->co = 0;
	ret->flags = 0;
	a = avt_search(t->tree,ret,t->avt_cmp);
	if ( a == 0 ) {
		a = d_alloc(sizeof(*a),3);
		a->data = ret;
		avt_insert(&t->tree,a,t->avt_cmp);
		return ret;
	}
	else {
		d_f_ree(ret->name);
		d_f_ree(ret);
		return a->data;
	}
}


NM_EDGE *
new_edge(NM_TARGET * t,NM_VERTEX * p1,NM_VERTEX * p2)
{
NM_EDGE * e;
	e = d_alloc(sizeof(*e),4);
	e->ev[0].next = p1->edge;
	p1->edge = &e->ev[0];
	e->ev[0].v = p1;
	e->ev[0].this = e;

	e->ev[1].next = p2->edge;
	p2->edge = &e->ev[1];
	e->ev[1].v = p2;
	e->ev[1].this = e;
	return e;
}

typedef struct clean_work {
	NM_VERTEX * free;
} CLEAN_WORK;

int
clean_func(AVT_NODE * a,CLEAN_WORK * c)
{
NM_VERTEX * v,* v2;
NM_EDGE * e1,* e2;
NM_EDGE_V ** v2p;
int e1_target,e2_target;
	v = a->data;
	if ( v->edge == 0 )
		return 0;
	if ( v->edge->next == 0 ) {
		e1 = v->edge->this;
		if ( e1->ev[0].v == v )
			e1_target = 1;
		else	e1_target = 0;
		v2 = e1->ev[e1_target].v;
		for ( v2p = &v2->edge; *v2p && 
			*v2p != &e1->ev[e1_target];
			v2p = &(*v2p)->next );
		if ( *v2p == 0 )
			er_panic("clean_func(1)");
		*v2p = (*v2p)->next;
		d_f_ree(e1);
		v->next = c->free;
		c->free = v;
		return 0;
	}
	if ( v->edge->next->next )
		return 0;
	e1 = v->edge->this;
	e2 = v->edge->next->this;
	if ( e1 == e2 )
		return 0;
	if ( e1->ev[0].v == v )
		e1_target = 0;
	else 	e1_target = 1;
	if ( e2->ev[0].v != v )
		e2_target = 0;
	else	e2_target = 1;
	e1->ev[e1_target] = e2->ev[e2_target];
	e1->ev[e1_target].this = e1;
	v2 = e2->ev[e2_target].v;
	for ( v2p = &v2->edge; *v2p && *v2p != &e2->ev[e2_target];
			v2p = &(*v2p)->next );
	if ( *v2p == 0 )
		er_panic("clean_func");
	*v2p = &e1->ev[e1_target];
	d_f_ree(e2);
	v->next = c->free;
	c->free = v;
	return 0;
}


void
clean_ve(NM_TARGET * t)
{
CLEAN_WORK c;
NM_VERTEX * v;
AVT_NODE * a;
int f;
	f = 1;
	for ( ; f ; ) {
		c.free = 0;
		avt_trace_from_small(t->tree,clean_func,&c);
		f = 0;
		for ( ; c.free ; ) {
			f = 1;
			v = c.free;
			c.free = v->next;
			a = avt_delete(&t->tree,v,t->avt_cmp);
			if ( a == 0 )
				er_panic("clean_ve");
			d_f_ree(v->name);
			d_f_ree(v);
			d_f_ree(a);
		}
	}
}

void
insert_vertex_list(NM_VERTEX * v)
{
	v->next = 0;
	if ( vertex_head ) {
		vertex_head->next = v;
		vertex_head = v;
	}
	else {
		vertex_head = vertex_tail = v;
	}
}

NM_VERTEX *
delete_vertex_list()
{
NM_VERTEX * v;
	if ( vertex_tail == 0 )
		return 0;
	v = vertex_tail;
	vertex_tail = v->next;
	if ( vertex_tail == 0 )
		vertex_head = 0;
	return v;
}


typedef struct loop_work {
	NM_VERTEX * 		top;
	NM_LOOP *		result;
} LOOP_WORK;

void
catch_loop(NM_VERTEX * v,LOOP_WORK * w)
{
NM_LOOP * lp1, * lp2, **lpp;
int cnt;
	lp2 = 0;
	cnt = 0;
	for ( ; v ; v = v->loop_next ) {
		lp1 = d_alloc(sizeof(*lp1),1);
		lp1->v = v;
		lp1->loop_next = 0;
		lp1->length = 0;
		lp1->next = lp2;
		lp2 = lp1;
		cnt ++;
	}
	lp2->length = cnt;
	for ( lpp = &w->result; *lpp ; lpp = &(*lpp)->loop_next )
		if ( (*lpp)->length < cnt )
			break;
	lp2->loop_next = *lpp;
	*lpp = lp2;
}

void
_detect_loops_trace_edge(NM_VERTEX * v,int depth,int count,LOOP_WORK * w)
{
NM_EDGE_V * ev;
NM_EDGE * e;
int tar;
NM_VERTEX * v2;
	if ( depth == 0 )
		return;
	for ( ev = v->edge ; ev ; ev = ev->next ) {
		e = ev->this;
		for (tar = 0 ; tar < 2 ; tar ++ )
			if ( e->ev[tar].v != v )
				break;
		if ( tar == 2 )
			er_panic("_detect_loops_trace_edge");
		v2 = e->ev[tar].v;
		if ( count >= 2 && v2 == w->top ) {
			catch_loop(v,w);
			continue;
		}
		if ( v2->flags & VF_LOOP )
			continue;
		v2->flags |= VF_LOOP;
		v2 = e->ev[tar].v;
		v2->loop_next = v;
		_detect_loops_trace_edge(v2,depth-1,count+1,w);
		if ( count != 0 )
			v2->flags &= ~VF_LOOP;
	}
	if ( count == 0 ) {
		for ( ev = v->edge ; ev ; ev = ev->next ) {
			e = ev->this;
			for (tar = 0 ; tar < 2 ; tar ++ )
				if ( e->ev[tar].v != v )
					break;
			if ( tar == 2 )
				er_panic("_detect_loops_trace_edge");
			v2 = e->ev[tar].v;
			v2->flags &= ~VF_LOOP;
		}
	}
}

NM_LOOP *
detect_loops(NM_VERTEX * top,int depth)
{
LOOP_WORK w;

	w.result = 0;
	w.top = top;
	top->loop_next = 0;
	top->flags |= VF_LOOP;
	_detect_loops_trace_edge(top,depth,0,&w);
	top->flags &= ~VF_LOOP;
	return w.result;
}

int
all_ok(NM_LOOP * lp)
{
	for ( ; lp ; lp = lp->next ) {
		if ( lp->v->co == 0 )
			return -1;
	}
	return 0;
}

int
set_vertex(NM_LOOP * lp1,NM_LOOP * lp2 ,int dir)
{
NM_LOOP * lp3;
int ret;
	ret = 0;
	if ( dir == 1 ) {
		for ( ; lp1 ; lp1 = lp1->next, lp2 = lp2->next ) {
			if ( lp1->v->co )
				continue;
			if ( lp2->v->co )
				er_panic("set_vertex(5)");
			lp2->v->co = lp1->v;
			lp1->v->co = lp2->v;
			insert_vertex_list(lp1->v);
			ret = 1;
		}
	}
	else {
		lp3 = 0;
		for (  ; lp2 ; lp2 = lp2->next ) {
			lp2->work_next = lp3;
			lp3 = lp2;
		}
		lp2 = lp1;
		lp1 = lp1->next;
		for ( ; lp1 ; lp1 = lp1->next , lp3 = lp3->work_next ) {
			if ( lp1->v->co )
				continue;
			if ( lp3->v->co )
				er_panic("set_vertex(4)");
			lp3->v->co = lp1->v;
			lp1->v->co = lp3->v;
			insert_vertex_list(lp1->v);
			ret = 1;
		}
		if ( lp2->v->co != lp3->v )
			er_panic("set_vertex(2)");
		if ( lp2->v != lp3->v->co )
			er_panic("set_vertex(3)");
	}
	return ret;
}

int
cmp_loop(NM_LOOP * lp1, NM_LOOP * lp2)
{
NM_LOOP * p1,* p2, * p3;
int min;
int cnt;
	if ( lp1->length != lp2->length )
		return 0;
	min = 2;
	cnt = 0;
	for ( p1 = lp1, p2 = lp2 ; p1 ; p1 = p1->next , p2 = p2->next ) {
		if ( p1->v->co == 0 && p2->v->co == 0 )
			continue;
		if ( p1->v->co == 0 )
			goto next;
		if ( p2->v->co == 0 )
			goto next;
		if ( p1->v->co == p2->v ) {
			if ( p1->v != p2->v->co )
				er_panic("cmp_loop(1)");
			cnt ++;
			continue;
		}
		goto next;
	}
	if ( cnt < min )
		return 0;
	return 1;
next:
	p3 = 0;
	for ( p2 = lp2 ; p2 ; p2 = p2->next ) {
		p2->work_next = p3;
		p3 = p2;
	}
	p2 = lp1;
	p1 = lp1->next;
	cnt = 0;
	for ( ; p1 ; p1 = p1->next, p3 = p3->work_next ) {
		if ( p1->v->co == 0 && p3->v->co == 0 )
			continue;
		if ( p1->v->co == 0 )
			return 0;
		if ( p3->v->co == 0 )
			return 0;
		if ( p1->v->co == p3->v ) {
			if ( p1->v != p3->v->co )
				er_panic("cmp_loop(2)");
			cnt ++;
			continue;
		}
		return 0;
	}
	if ( p3 == 0 )
		er_panic("cmp_loop(3)");
	if ( p3->v->co == 0 && p2->v->co == 0 )
		return -1;
	if ( p3->v->co == 0 )
		return 0;
	if ( p2->v->co == 0 )
		return 0;
	if ( p3->v->co == p2->v ) {
		if ( p3->v != p2->v->co ) 
			er_panic("cmp_loop(3)");
		cnt ++;
		if ( cnt < min )
			return 0;
		return -1;
	}
	return 0;
}

void
free_loop(NM_LOOP * lp)
{
NM_LOOP * lp1, * lp2;
	for ( ; lp ; ) {
		lp1 = lp;
		lp = lp->loop_next;
		for ( ; lp1 ; ) {
			lp2 = lp1;
			lp1 = lp1->next;
			d_f_ree(lp2);
		}
	}
}

void
print_vertex(NM_VERTEX * v)
{
NAME_PTR * p;
	p = v->name;
	printf("v[%x](%lf %lf)",v,p->x,p->y);
}

void
print_loop(NM_LOOP * lp)
{
	printf("[");
	for ( ; lp ; lp = lp->next ) {
		printf("<");
		print_vertex(lp->v);
		printf("-");
		if ( lp->v->co == 0 )
			printf("0>");
		else {
			print_vertex(lp->v->co);
			printf(">");
		}
	}
	printf("]");
}

int
_do_mapping(int maxloop)
{
NM_VERTEX * v1, * v2;
NM_LOOP * lp1,* lp2;
NM_LOOP * lp1p, * lp2p, *lp2pp;
NM_LOOP * match;
int match_dir;
int ret;
int f;
int ret_f;
	ret_f = 0;
	for ( ; ; ) {
		v1 = delete_vertex_list();
		if ( v1 == 0 )
			break;
		v2 = v1->co;
/*
printf("a ");
print_vertex(v1);
printf("\n");
print_vertex(v2);
*/
		lp1 = detect_loops(v1,maxloop);
		lp2 = detect_loops(v2,maxloop);
/*
print_loop(lp1);
print_loop(lp2);
printf("\n");
*/
	retry:
		f = 0;
		lp2p = lp2;
		for ( lp1p = lp1 ; lp1p ; lp1p = lp1p->loop_next ) {
			if ( all_ok(lp1p) == 0 )
				continue;
			for ( ; lp2p ; lp2p = lp2p->loop_next )
				if ( lp2p->length == lp1p->length )
					break;
			if ( lp2p == 0 )
				break;
			match = 0;
			for ( lp2pp = lp2p ; lp2pp ;
					lp2pp = lp2pp->loop_next ) {
				if ( lp2pp->length != lp2p->length )
					break;
				if ( all_ok(lp2pp) == 0 )
					continue;
				ret = cmp_loop(lp1p,lp2pp);
				if ( ret == 0 )
					continue;
				if ( match )
					goto next;
				match = lp2pp;
				match_dir = ret;
			}
			if ( match )
				if ( set_vertex(lp1p,match,match_dir) ) {
					f = 1;
					ret_f = 1;
				}
		next:	{}
		}
		if ( f )
			goto retry;
		free_loop(lp1);
		free_loop(lp2);
	}
	return ret_f;
}

int
domapping_func(AVT_NODE * a)
{
NM_VERTEX * v;
	v = a->data;
	if ( v->co == 0 )
		return 0;
	insert_vertex_list(v);
	return 0;
}

void
do_mapping(int maxloop)
{
	for ( ; ; ) {
printf("loop\n");
	avt_trace_from_small(net_target[0].tree,domapping_func,0);
		if ( _do_mapping(maxloop) == 0 )
			break;
	}
}

typedef struct out_work {
	XL_SEXP *	lst;
} OUT_WORK;

int
output_func(AVT_NODE * a,OUT_WORK * w)
{
NM_VERTEX * v, * v2;
NAME_PTR * p1,* p2;
XL_SEXP * d1, * d2;
	v = a->data;
	if ( v->co == 0 )
		return 0;
	v2 = v->co;
	p1 = v->name;
	p2 = v2->name;
	switch ( net_target[0].name_type ) {
	case NMT_PTR:
		d1 = List(
			n_get_symbol("point"),
			get_floating(p1->x,0),
			get_floating(p1->y,0),
			-1);
		break;
	case NMT_STR:
		d1 = get_string(v->name);
		break;
	}
	switch ( net_target[1].name_type ) {
	case NMT_PTR:
		d2 = List(
			n_get_symbol("point"),
			get_floating(p2->x,0),
			get_floating(p2->y,0),
			-1);
		break;
	case NMT_STR:
		d2 = get_string(v2->name);
		break;
	}
	w->lst = cons(List(n_get_symbol("correspond"),d1,d2,-1),w->lst);
	return 0;
}

XL_SEXP *
output_mapping()
{
OUT_WORK w;
	w.lst = 0;
	avt_trace_from_small(net_target[0].tree,output_func,&w);
	return cons(n_get_symbol("correspond-list"),w.lst);
}
