/**********************************************************************
 
	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 <math.h>

#include	"xlerror.h"
#include	"xl.h"

#include <stdlib.h>
#include "memory_debug.h"
#include "svg2gb.h"
#include "xl2pdb_p.h"
#include "svg2gb_hash.h"


#define CONNECT_PATH_HASH_TABLE_SIZE 20011

extern point_nos;
extern PDB_POLYGON2D * pdb_p_list;


XL_SEXP * xl_ConnectPath();

PDB_PD_POINT *pdb_p_tail_pd(PDB_POLYGON2D *poly);

void
init_ConnectPath(XLISP_ENV * env)
{
	set_env(env,l_string(std_cm,"ConnectPath"),
		get_func_prim(xl_ConnectPath,FO_APPLICATIVE,0,0,-1));
}

int pdb_p_points_hash(GB_POINT *p){
	return ((unsigned)(p->x+p->y)) % CONNECT_PATH_HASH_TABLE_SIZE;
}

int pdb_p_point_equals(GB_POINT *lhs, GB_POINT *rhs){
	return (lhs->x==rhs->x) && (lhs->y==rhs->y);
}

int raw_ptr_equals(void *lhs, void *rhs){
	return lhs == rhs;
}


int _sexp_equals(XL_SEXP *lhs, XL_SEXP *rhs)
{
int ltype,rtype;
int ret;

	if(lhs == rhs)
		return 1;
	ltype = get_type(lhs);
	rtype = get_type(rhs);
	if(ltype != rtype){
		return 0;
	}

	switch(ltype){
	case XLT_PAIR:
		for ( ; get_type(lhs) == XLT_PAIR &&
				get_type(rhs) == XLT_PAIR ; ) {
			ret = _sexp_equals(car(lhs), car(rhs));
			if ( ret == 0 )
				return 0;
			lhs = cdr(lhs);
			rhs = cdr(rhs);
		}
		return _sexp_equals(lhs,rhs);
	case XLT_SYMBOL:
		if ( l_strcmp(lhs->symbol.data,rhs->symbol.data) )
			return 0;
		return field_equals(lhs->symbol.field,rhs->symbol.field);
		break;
	case XLT_STRING:
		{
			return (l_strcmp(lhs->string.data, rhs->string.data)==0);
		}
		break;
	case XLT_INTEGER:
		{
			return (lhs->integer.data == rhs->integer.data);
		}
		break;
	case XLT_FLOAT:
		{
			return (lhs->floating.data == rhs->floating.data);
		}
		break;
	case XLT_RAW:
		{
			return (lhs->raw.size == rhs->raw.size) && 
				(memcmp(lhs->raw.data, rhs->raw.data, lhs->raw.size)==0);
		}
		break;
	case XLT_NULL:
		{
			return 1;
		}
		break;
	case XLT_FUNC:
	case XLT_DELAY:
	case XLT_PTR:
	case XLT_ERROR:
	default:
		{
			er_panic("compareing is not supported");
			return 0;
		}
	}
	
	er_panic("must not reach");
	return 0;
}

int sexp_equals(XL_SEXP *lhs, XL_SEXP *rhs)
{
int l_type,r_type;
int ret;
XL_SEXP * sym1, * sym2;

	if ( lhs == rhs )
		return 1;
	l_type = get_type(lhs);
	r_type = get_type(rhs);
	if ( l_type != r_type ) {
		ret = 0;
		goto end;
	}
	else if ( l_type != XLT_PAIR ) {
		ret = _sexp_equals(lhs,rhs);
		goto end;
	}
	sym1 = car(lhs);
	sym2 = car(rhs);
	l_type = get_type(sym1);
	r_type = get_type(sym2);
	if ( l_type != r_type ) {
		ret = 0;
		goto end;
	}
	if ( l_type != XLT_SYMBOL ) {
		ret = _sexp_equals(lhs,rhs);
		goto end;
	}
	if ( l_strcmp(sym1->symbol.data,l_string(std_cm,"information")) ||
		l_strcmp(sym2->symbol.data,l_string(std_cm,"information")) ) {
		ret = _sexp_equals(lhs,rhs);
		goto end;
	}
	ret = _sexp_equals(get_el(lhs,1),get_el(rhs,1));
end:
	return ret;
}

GB_POINT *pdb_p_head_ptr(PDB_POLYGON2D *poly){
	if(!poly->point)
		return NULL;
	/*	er_panic("polygon has no points"); */
	return &(poly->point->p);
}

GB_POINT *pdb_p_tail_ptr(PDB_POLYGON2D *poly){
PDB_PD_POINT *pt;
	pt=poly->point;
	if(!pt)
		return NULL;
	return &(pdb_p_tail_pd(poly)->p);
}

PDB_PD_POINT *pdb_p_head_pd(PDB_POLYGON2D *p){
	if(!p->point){
		er_panic("polygon has no points");
	}
	return p->point;
}

PDB_PD_POINT *pdb_p_tail_pd(PDB_POLYGON2D *poly){
PDB_PD_POINT *p;
	if(poly->tail_point)
		return poly->tail_point;
	p=poly->point;
	if(!p){
		er_panic("the polygon must has more than 2 points.");
	}
	while(p->next){
		p = p->next;
	}
	poly->tail_point=p;
	return p;
}

PDB_PD_POINT *pdb_p_tail_pd2(PDB_POLYGON2D *poly){
PDB_PD_POINT *p;
	if(poly->tail2_point)
		return poly->tail2_point;
	p=poly->point;
	if(!p||!p->next){
		er_panic("the polygon must has more than 2 points.");
	}
	while(p->next->next){
		p = p->next;
	}
	poly->tail2_point=p;
	return p;
}

void d_f_ree_pd_points(PDB_PD_POINT *pt)
{
	if(!pt)
		return;
	if(pt->next)
		d_f_ree_pd_points(pt->next);
	d_f_ree(pt);
}

void pdb_p_destroy_single(PDB_POLYGON2D *poly){
	if(poly->name)
		d_f_ree(poly->name);
	if(poly->subname)
		d_f_ree(poly->subname);
	d_f_ree_pd_points(poly->point);
	
	if(poly->start_sg)
		d_f_ree(poly->start_sg);
	if(poly->end_sg)
		d_f_ree(poly->end_sg);
}

static void swap_point(GB_POINT *p1, GB_POINT *p2){
	GB_POINT tmp;
	tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

static void invert_point_array(GB_POINT *point_array, int point_count){
	int i;
	for(i=0;i<point_count/2;++i){
		swap_point(&point_array[i], &point_array[point_count-i-1]);
	}
}

static PDB_PD_POINT *point_array_to_pd_point_list(GB_POINT* src_array, int src_count){
	PDB_PD_POINT *ret=0;
	PDB_PD_POINT *dest_ptr;
	int i;
	for(i=0; i<src_count; ++i){
		if(ret==0){
			ret = (PDB_PD_POINT*)d_calloc(sizeof(PDB_PD_POINT), 1);
			dest_ptr = ret;
		}
		else{
			dest_ptr->next = (PDB_PD_POINT*)d_calloc(sizeof(PDB_PD_POINT), 1);
			dest_ptr = dest_ptr->next;
		}
		dest_ptr->reso = -1;
		dest_ptr->no = 0;
		dest_ptr->lod_max = 0;
		dest_ptr->lod_min = 0;
		dest_ptr->p = src_array[i];
		
		/* increment grobal 'point_nos' */
		/* ++point_nos; */
	}
	return ret;
}

void pdb_p_renumber(PDB_POLYGON2D *dest)
{
PDB_PD_POINT *p;
int no=0;
	for(p=dest->point; p; p=p->next, ++no){
		if ( p->no & PP_LMOVE )
			p->no = PP_LMOVE | no;
		else	p->no = no;
	}
}
	
void pdb_p_connect(PDB_POLYGON2D *dest, PDB_POLYGON2D* src, int connect_info_flag)
{
PDB_PD_POINT *dest_head,*dest_tail,*src_head, *src_tail;
PDB_PD_POINT *src_ptr;
GB_POINT *src_points;
int src_count=0;
int i;
int src_to_dest_flag=0;
PDB_PD_POINT *new_point_list;

	for(src_ptr=src->point; src_ptr; src_ptr=src_ptr->next){
		++src_count;
	}
	src_points = (GB_POINT*)malloc(sizeof(GB_POINT)*src_count);
	
	dest_head = pdb_p_head_pd(dest);
	dest_tail = pdb_p_tail_pd(dest);
	src_head = pdb_p_head_pd(src);
	src_tail = pdb_p_tail_pd(src);
	
	for(i=0,src_ptr=src->point; src_ptr; src_ptr=src_ptr->next, ++i){
		src_points[i].x = src_ptr->p.x;
		src_points[i].y = src_ptr->p.y;
	}
	if(pdb_p_point_equals(&src_head->p, &dest_head->p) || 
		pdb_p_point_equals(&src_tail->p, &dest_tail->p)
		){
		invert_point_array(src_points, src_count);
	}
	
	if(pdb_p_point_equals(&src_head->p, &dest_head->p)){
		src_to_dest_flag=1;
	}
	else if(pdb_p_point_equals(&src_tail->p, &dest_head->p)){
		src_to_dest_flag=1;
	}
	else if(pdb_p_point_equals(&src_head->p, &dest_tail->p)){
		src_to_dest_flag=0;
	}
	else if(pdb_p_point_equals(&src_tail->p, &dest_tail->p)){
		src_to_dest_flag=0;
	}
	else{
		er_panic("cannt connectable.");
	}
	
	if(src_to_dest_flag){
		new_point_list = point_array_to_pd_point_list(src_points, src_count-1);
	}
	else{
		new_point_list = point_array_to_pd_point_list(src_points+1, src_count-1);
	}

	free(src_points);
	
	if(src_to_dest_flag){
		PDB_PD_POINT *dest_original;

		dest_original = dest->point;
		dest->point = new_point_list;
		dest->tail_point=NULL;
		dest->tail2_point=NULL;
		pdb_p_tail_pd(dest)->next = dest_original;
		dest->tail_point=NULL;
		dest->tail2_point=NULL;
	}
	else{
		pdb_p_tail_pd(dest)->next = new_point_list;
		dest->tail_point=NULL;
		dest->tail2_point=NULL;
	}
	
	pdb_p_renumber(dest);
	
	if(dest->info==NULL){
		dest->info = src->info;
	}
}

double get_connect_candidate_level_p(GB_POINT *o, GB_POINT *p1, GB_POINT *p2){
double p1len,p2len,x,y;
	if( (o->x==p1->x) && (o->y==p1->y) ){
		return -1;
	}
	if( (o->x==p2->x) && (o->y==p2->y) ){
		return -1;
	}
	
	p1len = hypot(o->x-p1->x, o->y-p1->y);
	p2len = hypot(o->x-p2->x, o->y-p2->y);
	x = ((o->x - p1->x) / p1len) - ((o->x - p2->x) / p2len);
	y = ((o->y - p1->y) / p1len) - ((o->y - p2->y) / p2len);
	return (x*x)+(y*y);
}

double get_connect_candidate_level(PDB_POLYGON2D *lhs, PDB_POLYGON2D *rhs){
PDB_PD_POINT *rhs_head,*rhs_tail,*lhs_head, *lhs_tail, *lhs_tail2, *rhs_tail2;

	rhs_head = pdb_p_head_pd(rhs);
	rhs_tail2 = pdb_p_tail_pd2(rhs);
	lhs_head = pdb_p_head_pd(lhs);
	lhs_tail2 = pdb_p_tail_pd2(lhs);
	rhs_tail = rhs_tail2->next;
	lhs_tail = lhs_tail2->next;
	
	if(pdb_p_point_equals(&rhs_head->p, &lhs_tail->p)){
		return get_connect_candidate_level_p(
			&rhs_head->p, &rhs_head->next->p, &lhs_tail2->p);
	}
	if(pdb_p_point_equals(&rhs_tail->p, &lhs_head->p)){
		return get_connect_candidate_level_p(
			&lhs_head->p, &lhs_head->next->p, &rhs_tail2->p);
	}
	if(pdb_p_point_equals(&rhs_head->p, &lhs_head->p)){
		return get_connect_candidate_level_p(
			&rhs_head->p, &rhs_head->next->p, &lhs_head->next->p);
	}
	if(pdb_p_point_equals(&rhs_tail->p, &lhs_tail->p)){
		return get_connect_candidate_level_p(
			&rhs_tail->p, &rhs_tail2->p, &lhs_tail2->p);
	}

	/* er_panic("lhs and rhs is not connectable."); */
	return -1.0;
}

PDB_POLYGON2D *get_prev_from_pdb_p_list(PDB_POLYGON2D *from){
	return from->prev;
	/*
	PDB_POLYGON2D *poly;
	if(pdb_p_list==from){
		return 0;
	}
	for(poly=pdb_p_list; poly; poly=poly->next){
		if(poly->next==from){
			return poly;
		}
	}
	*/
	return 0;
}

PDB_POLYGON2D *connect_and_remove_connected(
	GB_HASH_TABLE *head_tbl,
	GB_HASH_TABLE *tail_tbl,
	PDB_POLYGON2D *target,
	PDB_POLYGON2D *source){
	PDB_POLYGON2D *ret;

	/* erase connectable polygon from hash table */
	gb_hash_erase(head_tbl, pdb_p_head_ptr(target), target);
	gb_hash_erase(tail_tbl, pdb_p_tail_ptr(target), target);
	gb_hash_erase(head_tbl, pdb_p_head_ptr(source), source);
	gb_hash_erase(tail_tbl, pdb_p_tail_ptr(source), source);
	
	/* connect source polygon to target polygon */
	pdb_p_connect(target, source, 0);
	
	/* insert connedted polygon to table as new polygon */
	gb_hash_insert_multi(head_tbl, pdb_p_head_ptr(target), target);
	gb_hash_insert_multi(tail_tbl, pdb_p_tail_ptr(target), target);
	
	/* remove source polygon */
	remove_poly_from_pdb_p_avt(source);
	
	ret = remove_poly_from_pdb_p_list(source);
	pdb_p_destroy_single(source);
	return ret;
}

PDB_POLYGON2D *remove_poly_from_pdb_p_list(PDB_POLYGON2D *poly){
	PDB_POLYGON2D *prev = get_prev_from_pdb_p_list(poly);
	lock_mem();
	if(prev){
		/* remove connected source */
		if(poly->next)
			poly->next->prev=prev;
		prev->next = poly->next;
	}
	else{
		/* connected source was head of pdb_p_list */
		pdb_p_list = poly->next;
		pdb_p_list->prev=NULL;
	}
	unlock_mem();
	return poly->next;
}

void debugPrintPdbpList()
{
FILE *fp;
PDB_POLYGON2D *poly;
PDB_PD_POINT *point;
	fp = fopen("test.svg", "wt");
	fprintf(fp, "<?xml version=\"1.0\" ?>\n");
	fprintf(fp, "<svg style=\"fill:none;stroke:#888888;\" width=\"400\" height=\"400\" viewBox=\"-126000 -38000 36000 28000\">\n");
	
	for(poly=pdb_p_list; poly; poly=poly->next){
		point=poly->point;
		if(point==0)
			continue;
			/* er_panic("no points.");*/
		fprintf(fp, "<path ");
		fprintf(fp, "name=\"%s\" ", n_string(std_cm, poly->name) );
		fprintf(fp, "d=\"M%.1f %.1f", point->p.x, point->p.y);
		point=point->next;
		for(; point; point=point->next){
			fprintf(fp, "L%.1f %.1f", point->p.x, point->p.y);
		}
		if(poly->type == PDT_CLOSE){
			fprintf(fp, "z");
		}
		fprintf(fp, "\" />");
	}
	fprintf(fp, "</svg>\n");
	fclose(fp);
}

void print_connected_count(int count){
	ss_printf("%d connected.\r", count);
}

int f_range_equal(double d1, double d2, double range){
	if( (d2-range<d1) && (d1<d2+range) ){
		return 1;
	}
	return 0;
}

int f_equal(double d1, double d2){
	if( (d2-0.2<d1) && (d1<d2+0.2) ){
		return 1;
	}
	return 0;
}


XL_SEXP *
xl_ConnectPath(XLISP_ENV * env,XL_SEXP * s)
{

SVG2GB_CONTEXT *ctx;
GB_HASH_TABLE *head_tbl, *tail_tbl;
PDB_POLYGON2D *poly;
GB_MULTI_RESULT *ml;
GB_POINT *check_point;

PDB_POLYGON2D *connect_candidate, *connect_candidate_source;
double connect_candidate_level;
int i;
int point_switch;
int connected_count;
int point_count_before;
int	point_count_after;

PDB_POLYGON2D *prev;


	point_count_before = 0;
	point_count_after = 0;
	connected_count = 0;
	prev=NULL;

	ss_printf("CONNECT PATH BEGIN...\n");
	
	for(poly=pdb_p_list; poly; poly=poly->next){
		PDB_PD_POINT *p;
		for(p=poly->point; p; p=p->next){
			++point_count_before;
		}
		poly->prev=prev;
		prev=poly;
		poly->tail_point=NULL;
		poly->tail2_point=NULL;
	}

	ctx = svg2gb_get_context();
	
	head_tbl = gb_make_hash_table(
		CONNECT_PATH_HASH_TABLE_SIZE,
		512,
		pdb_p_points_hash,
		pdb_p_point_equals,
		raw_ptr_equals);
	
	tail_tbl = gb_make_hash_table(
		CONNECT_PATH_HASH_TABLE_SIZE, 
		512, 
		pdb_p_points_hash,
		pdb_p_point_equals,
		raw_ptr_equals);
	
	/*
		regist all polygons to hash table. 
		head-point and tail-point is key.
	*/
	for(poly=pdb_p_list; poly; poly=poly->next){
		if(poly->type == PDT_CLOSE){
			continue;
		}
		gb_hash_insert_multi(head_tbl, pdb_p_head_ptr(poly), poly);
		gb_hash_insert_multi(tail_tbl, pdb_p_tail_ptr(poly), poly);
	}
	
	ml = gb_make_multi_result();
	
	for(poly=pdb_p_list; poly; ){
		if(connected_count%1000 == 0){
			print_connected_count(connected_count);
		}
		
		for(point_switch=0; point_switch<2; ++point_switch){
			connect_candidate_level = -1.0;
			connect_candidate_source = 0;
			connect_candidate = 0;
			if(poly==NULL)
				break;
			if(point_switch==0){
				check_point = pdb_p_head_ptr(poly);
			}
			else{
				check_point = pdb_p_tail_ptr(poly);
			}
			if(check_point == NULL){
				continue;
			}
			gb_reset_multi_result(ml);
			
			gb_hash_find_multi(ml, head_tbl, check_point);
			gb_hash_find_multi(ml, tail_tbl, check_point);
			
			for(i=0; i<ml->count; ++i){
				int j;
				for(j=i+1; j<ml->count; ++j){
					PDB_POLYGON2D *p1,*p2;
					double level;
					p1 = (PDB_POLYGON2D*)(ml->data[i]);
					p2 = (PDB_POLYGON2D*)(ml->data[j]);
					if(p1==p2){
						continue;
					}
					if(p1->info && p2->info){
						if(sexp_equals(p1->info, p2->info)){
							connect_candidate_source = p1;
							connect_candidate = p2;
							goto break2;
						}
					}else{
						level = get_connect_candidate_level(p1, p2);
						if(connect_candidate_level<level){
							connect_candidate_source = p1;
							connect_candidate = p2;
							connect_candidate_level = level;
						}
					}
				}
			}
break2:
			if(connect_candidate_source){
				if(connect_candidate_source==poly){
					poly = poly->next;
				}
				connect_and_remove_connected(
					head_tbl,
					tail_tbl,
					connect_candidate,
					connect_candidate_source);
				++connected_count;
				point_switch=-1;
				continue;
			}
		}
		
		if(!poly)
			break;
		poly = poly->next;
	}
	
	
	print_connected_count(connected_count);
	ss_printf("\n");
	
	gb_free_hash_table(head_tbl);
	gb_free_hash_table(tail_tbl);
	gb_free_multi_result(ml);
	
	ss_printf("CONNECT PATH END...\n");
	
/*	debugPrintPdbpList();*/
	
	for(poly=pdb_p_list; poly; poly=poly->next){
		PDB_PD_POINT *p;
		for(p=poly->point; p; p=p->next){
			++point_count_after;
		}
	}
	
	ss_printf("before:%d  after:%d  connected:%d\n",
		point_count_before,
		point_count_after,
		connected_count);
	
	return 0;
}

