/**********************************************************************
 
	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	<stdlib.h>
#include	"utils.h"
#include	"resource.h"
#include	"xlerror.h"
#include	"memory_debug.h"
#include	"memory_routine.h"
#include	"lock_level.h"
#include	"u_math.h"
#include	"gbgraph.h"



XL_SEXP * point_map();
XL_SEXP * triangle_map();
XL_SEXP * angle();
XL_SEXP * gv_map_status();
XL_SEXP * horizontal_affen();

SEM map_lock;

void
init_map()
{
XLISP_ENV * map_top,* map_cmd;

	map_lock = new_lock(LL_MAP);

	map_cmd = new_env(0);
	map_top = new_env(gblisp_top_env0);

	set_env(map_cmd,l_string(std_cm,"point-map"),
			get_func_prim(point_map,FO_NORMAL,0,4,4));
	set_env(map_cmd,l_string(std_cm,"triangle-map"),
			get_func_prim(triangle_map,FO_NORMAL,0,4,4));
	set_env(map_cmd,l_string(std_cm,"horizontal-affen"),
			get_func_prim(horizontal_affen,FO_APPLICATIVE,0,3,3));
	set_env(map_cmd,l_string(std_cm,"angle"),
			get_func_prim(angle,FO_NORMAL,0,2,2));
	set_env(map_top,l_string(std_cm,"gv-status"),
			get_func_prim(gv_map_status,FO_APPLICATIVE,0,1,1));

	set_env(gblisp_top_env0,l_string(std_cm,"map"),
		get_env(map_top));

	root_tag(map_top,l_string(std_cm,"map"),map_cmd);
	set_gv_resource(RT_MAP,map_top);
}

void
map_test_trace(MAP_HISTORY * mh)
{
	ss_printf("T:");
	for ( ; mh ; mh = mh->next ) {
		switch ( mh->type ) {
		case MHT_MAP:
			ss_printf("MAP-");
			break;
		case MHT_LINEAR:
			ss_printf("LIN-");
			break;
		case MHT_PP:
			ss_printf("PP-");
			break;
		default:
			er_panic("map_from_flame(1)");
		}
		switch ( mh->dir ) {
		case MHD_FORWARD:
			ss_printf("F:");
			break;
		case MHD_REVERSE:
			ss_printf("R:");
		}
	}
	ss_printf("\n");
}



int
map_test(GB_POINT * ptr,REAL1 * reso,int w,int size,char * msg)
{
int i;
GB_POINT * p1,* p2;
int ret;
	ret = 0;
	for ( i = 0 ; i < size-1 ; i ++ ) {
		p1 = &ptr[i];
		p2 = &ptr[i+1];
		if ( reso[i] < 0 )
			continue;
		if ( 		reso[i+1] >= 0 &&
				p1->x == p2->x &&
				p1->y == p2->y ) {
			ss_printf("????? = 1 = %s\n",msg);
			ret = -1;
			break;
		}
		if ( w == 0 )
			continue;
		if ( i + w >= size )
			continue;
		if ( reso[i+w] < 0 )
			continue;
		p2 = &ptr[i+w];
		if ( p1->x == p2->x &&
				p1->y == p2->y ) {
			ss_printf("????? = 2 = %s\n",msg);
			ret = -1;
			break;
		}
	}
	return ret;
}

static int test_map_reso_task;

void
test_map_reso(char* str,REAL1 * reso,int size,int data)
{
int i;

	if ( str == 0 ) {
		if ( data ) 
			test_map_reso_task = get_tid();
		else	test_map_reso_task = 0;
		return;
	}
	if ( test_map_reso_task != get_tid() )
		return;
	ss_printf("RESO %s(%i:%x) ",str,data,data);
	for ( i = 0 ; i < size ; i ++ )
		ss_printf("%f ",reso[i]);
	ss_printf("\n");
}

void
free_clip(CLIP * c)
{
CLIP * cc;
	for ( ; c ; ) {
		cc = c;
		c = c->next;
		d_f_ree(cc);
	}
}

void
free_map_parameter(MAP_PARAMETER * mp)
{
TRIANGLE * t1, * t2;
	if ( mp->type != MT_TRIANGLE ) {
		mp->type = 0;
		return;
	}
	for ( t1 = mp->opt.tri.tri_list ; t1 ; ) {
		t2 = t1->next;
		d_f_ree(t1);
		t1 = t2;
	}
	mp->type = 0;
}


XL_SEXP *
gv_new_map(RESOURCE * r,XL_SEXP * s,int ds)
{
MAP_POINT_LIST * mp;
int ret_dest,ret_src;
L_CHAR * src,* dest;
char * e_param;
L_CHAR * map_url;
L_CHAR * url;


	switch ( ds ) {
	case NR_CLEAR:
		lock_task(map_lock);
		free_map_parameter(&r->map.param_reverse);
		free_map_parameter(&r->map.param_forward);
		for ( ; r->map.point_list.next !=
				&r->map.point_list ; ){
			mp = R_NEXT(MAP_POINT_LIST*,
				&r->map.point_list);
			if ( mp->tag )
				d_f_ree(mp->tag);
			DELETE_RING(&mp->h);
			d_f_ree(mp);
		}
		unlock_task(map_lock,"gv_new_map");
	case NR_NEW:
		get_field(s,
			l_string(std_cm,"src"),"s",&src,&ret_src,
			l_string(std_cm,"dest"),"s",&dest,&ret_dest,
			0);
		if ( ret_src || ret_dest ) {
			e_param = "src/dest";
			goto subtype_error;
		}
		map_url = ll_copy_str(get_url_str2(&r->h.entry));
		url = compose_url(map_url,src);
		get_url2(&r->map.src,url);
		setup_c_unit(&r->map.cu_src,-1,url,0,0);
		if ( r->map.cu_src.system == 0 ) {
			set_c_unit(&r->map.cu_src,
				url,
				l_string(std_cm,"base"),
				l_string(std_cm,"m"));
			r->h.flags |= RF_UNCACHE;
		}
		d_f_ree(url);
		url = compose_url(map_url,dest);
		get_url2(&r->map.dest,url);
		setup_c_unit(&r->map.cu_dest,-1,url,0,0);
		if ( r->map.cu_dest.system == 0 ) {
			set_c_unit(&r->map.cu_dest,
				url,
				l_string(std_cm,"base"),
				l_string(std_cm,"m"));
			r->h.flags |= RF_UNCACHE;
		}
		d_f_ree(url);

		d_f_ree(map_url);

		if ( r->map.cu_src.uenv == 0 ) {
			e_param = "src/uenv cannot read";
			goto subtype_error;
		}
		if ( r->map.cu_dest.uenv == 0 ) {
			e_param = "dest/uenv cannot read";
			goto subtype_error;
		}

		INIT_RING(&r->map.point_list);
		r->map.param_reverse.type = 0;
		r->map.param_forward.type = 0;
	case NR_KEEP:
		;
	}


	return 0;
subtype_error:
	return get_error(
		s->h.file,
		s->h.line,
		XLE_PROTO_INV_PARAM,
		l_string(std_cm,"gv-new-resource(coordinate)"),
		List(n_get_string("invalid subtype in the meta info"),
			get_string(l_string(std_cm,e_param)),
			n_get_string("argument"),
			-1));
}


XL_SEXP * 
gv_map_status(XLISP_ENV * env,XL_SEXP * s,XLISP_ENV * a,XL_SYM_FIELD * sf)
{
RESOURCE * r;
	return cons(
		get_symbol(l_string(std_cm,"map")),
		get_resource_status_header(
			&r,env,s,a,0));
}

void
draw_map()
{
	er_panic("draw_map(1)");
}

void
get_point_map()
{
	er_panic("get_point_map(1)");
}

void
lock_map()
{
	lock_task(map_lock);
}

void
unlock_map()
{
	unlock_task(map_lock,"unlock_map");
}

void
free_mapping_from_sd(RESOURCE * src,RESOURCE * dest)
{
MAP * m1;
	if ( dest->h.type != RT_COORDINATE )
		er_panic("free_mapping");
	for ( m1 = R_NEXT(MAP*,&dest->c.map_children);
			m1 != (MAP*)&dest->c.map_children;
			m1 = R_NEXT(MAP*,&m1->h) )
		if ( m1->src == src )
			goto ok;
	return;
ok:
	lock_task(map_lock);
	free_url(&m1->src_url);
	free_url(&m1->dest_url);
	free_url(&m1->map_url);
	DELETE_RING(&m1->h);
	unlock_task(map_lock,"free_mapping_from_sd");

	if ( src->h.type != RT_COORDINATE )
		return;
	for ( m1 = R_NEXT(MAP*,&src->c.map_parents);
			m1 != (MAP*)&src->c.map_parents;
			m1 = R_NEXT(MAP*,&m1->h) )
		if ( m1->dest == dest )
			goto ok2;
	return;
ok2:
	free_url(&m1->src_url);
	free_url(&m1->dest_url);
	free_url(&m1->map_url);
	DELETE_RING(&m1->h);
	d_f_ree(m1);
}


REAL1
map_radius(MAP_PARAMETER * mp,GB_POINT c,REAL1 radius)
{
TRIANGLE * tt;
GB_RECT r;
double rate,_rate,A0,A1,A2,R;
double rr;
int f;
	lock_task(map_lock);
	rr = radius;
	rate = 1;
retry:
	r.tl.x = c.x - rr;
	r.tl.y = c.y - rr;
	r.br.x = c.x + rr;
	r.br.y = c.y + rr;
	f = 0;
	for ( tt = mp->opt.tri.tri_list ; tt ; tt = tt->next ) {
		if ( cross_rect_triangle(&r,tt->ptp) == 0 )
			continue;
		A0 = tt->a.matrix[0][0]*tt->a.matrix[0][0] +
			tt->a.matrix[1][0]*tt->a.matrix[1][0];
		A1 = tt->a.matrix[0][0]*tt->a.matrix[0][1] +
			tt->a.matrix[1][0]*tt->a.matrix[1][1];
		A2 = tt->a.matrix[0][1]*tt->a.matrix[0][1] +
			tt->a.matrix[1][1]*tt->a.matrix[1][1];
		R = sqrt((A0-A2)*(A0-A2)/4 + A1*A1);
		_rate = (A0 + A2)/2 + R;
		if ( rate < _rate ) {
			f = 1;
			rate = _rate;
		}
	}
	if ( mp->opt.tri.sts != TS_NOSET &&
			cross_rect_rect(&r,&mp->opt.tri.minrect) ) {

		A0 = mp->opt.tri.a.matrix[0][0]*mp->opt.tri.a.matrix[0][0] +
			mp->opt.tri.a.matrix[1][0]*mp->opt.tri.a.matrix[1][0];
		A1 = mp->opt.tri.a.matrix[0][0]*mp->opt.tri.a.matrix[0][1] +
			mp->opt.tri.a.matrix[1][0]*mp->opt.tri.a.matrix[1][1];
		A2 = mp->opt.tri.a.matrix[0][1]*mp->opt.tri.a.matrix[0][1] +
			mp->opt.tri.a.matrix[1][1]*mp->opt.tri.a.matrix[1][1];
		R = sqrt((A0-A2)*(A0-A2)/4 + A1*A1);
		_rate = (A0 + A2)/2 + R;
		if ( rate < _rate ) {
			f = 1;
			rate = _rate;
		}
	}
	if ( f ) {
		rr = radius*rate;
		goto retry;
	}
	unlock_task(map_lock,"map_radius");
	return rr;
}


int test_ff;


GB_POINT
_map_conv_triangle(REAL1 *res,MAP_PARAMETER * mp,GB_POINT p)
{
TRIANGLE ** tt;
TRIANGLE * t;
GB_POINT ret;


	if ( mp->opt.tri.sts != TS_NOSET ) {
		if ( inside_rect(&mp->opt.tri.minrect,p) == 0 )
			goto ok_outside;
	}


	for ( tt = &mp->opt.tri.tri_list ; *tt ; tt = &(*tt)->next ) {
		if ( inside_rect(&(*tt)->minrect,p) == 0 )
			continue;
		if ( inside_triangle((*tt)->ptp,p) == 0 )
			goto ok;
	}

	if ( mp->opt.tri.sts != TS_NOSET )
		goto ok_outside;
	*res = -1;
	return p;
ok:

	t = *tt;
	*tt = t->next;
	t->next = mp->opt.tri.tri_list;
	mp->opt.tri.tri_list = t;
	*res *= t->resolution_rate;
	ret = caffen2d(&t->a,p);


	return ret;
ok_outside:


	*res *= mp->opt.tri.resolution_rate;
	ret = caffen2d(&mp->opt.tri.a,p);

	return ret;
}

GB_POINT
map_conv_triangle(REAL1 *res,MAP_PARAMETER * mp,GB_POINT p)
{
GB_POINT ret;
	lock_task(map_lock);
	ret = _map_conv_triangle(res,mp,p);
	unlock_task(map_lock,"map_conv_triangle");
	return ret;
}


void
map_conv_reverse(RESOURCE * mp,GB_POINT * p,REAL1 * reso,int size)
{
int i;
GB_POINT mv;

	if ( mp == 0 )
		return;
	switch ( mp->map.param_reverse.type ) {
	case 0:
		break;
	case MT_MOVE:
		mv = mp->map.param_reverse.opt.ptr;
		if ( mv.x == 0 && mv.y == 0 ) {
			break;
		}
		for ( i = 0 ; i < size ; i ++ ) {
			if ( reso[i] < 0 )
				continue;
			p[i].x += mv.x;
			p[i].y += mv.y;
		}
		break;
	case MT_ROTATE:
	case MT_LINEAR:
		for ( i = 0 ; i < size ; i ++ ) {
			if ( reso[i] < 0 )
				continue;
			p[i] = caffen2d(
				&mp->map.param_reverse.opt.affen,
				p[i]);
			reso[i] *= mp->
				map.param_reverse.
				resolution_rate;
		}
		break;
	case MT_TRIANGLE:
		lock_task(map_lock);
		for ( i = 0 ; i < size ; i ++ ) {
			if ( reso[i] < 0 )
				continue;
			p[i] = _map_conv_triangle(&reso[i],
				&mp->map.param_reverse,p[i]);
		}
		unlock_task(map_lock,"map_conv_reverse");
		break;
	default:
		er_panic("map_conv_reverse(1)");
	}
}

void
map_conv_forward_ln(MAP_HISTORY * mh,GB_POINT * p,REAL1 * reso,int size)
{
int i;
REAL1 rr;
	rr = mh->d.ln.forward_reso_rate;
	for ( i = 0 ; i < size ; i ++ ) {
		if ( reso[i] < 0 )
			continue;
		p[i] = caffen2d(
			&mh->d.ln.forward,
			p[i]);
		reso[i] *= rr;
	}
}

void
map_conv_reverse_ln(MAP_HISTORY * mh,GB_POINT * p,REAL1 * reso,int size)
{
int i;
REAL1 rr;
	rr = mh->d.ln.reverse_reso_rate;
	for ( i = 0 ; i < size ; i ++ ) {
		if ( reso[i] < 0 )
			continue;
		p[i] = caffen2d(
			&mh->d.ln.reverse,
			p[i]);
		reso[i] *= rr;
	}
}

void
map_conv_reverse_pp(MAP_HISTORY * mh,GB_POINT * p,REAL1 * reso,int size)
{
int i;
GB_POINT pp;
GB_POINT3D pp3d;
double d,rrate,lambda_rate;
GB_POINT * _p;
REAL1 * _reso;
	_p = p;
	_reso = reso;
	rrate = 1/mh->d.pp.forward_rate;
	lambda_rate = mh->d.pp.lambda*mh->d.pp.forward_rate;
	for ( i = 0 ; i < size ; i ++ , p ++ , reso ++ ) {
		if ( *reso < 0 )
			continue;
		pp = *p;
		pp = caffen2d(
			&mh->d.pp.reverse_sur,
			pp);
		d = pp.x * pp.x + pp.y * pp.y;

		if ( d > 1 ) {
			*reso = -1;
			continue;
		}

		pp3d.x = pp.x;
		pp3d.y = pp.y;
		pp3d.z = sqrt(1 - d);
		*reso = (*reso)*pp3d.z;
		pp3d = mp_mul3d(mh->d.pp.reverse_vol,pp3d);
		if ( pp3d.z || pp3d.x ) {
			if ( pp3d.z > pp3d.x ) {
				if ( pp3d.z > - pp3d.x ) {
					/* 1 */
					pp.x = atan( pp3d.x / pp3d.z );
				}
				else {
					/* 2 */
					pp.x = - M_PI/2
						- atan( pp3d.z / pp3d.x );
				}
			}
			else {
				if ( pp3d.z > - pp3d.x ) {
					/* 3 */
					pp.x = M_PI/2
						- atan( pp3d.z / pp3d.x );
				}
				else if ( pp3d.x > 0 ) {
					/* 4 */
					pp.x = M_PI
						+ atan( pp3d.x / pp3d.z );
				}
				else {
					/* 4' */
					pp.x = -M_PI
						+ atan( pp3d.x / pp3d.z );
				}
			}
		}
		else	pp.x = 0;
		pp.y = asin(-pp3d.y);
/*
		pp.x = pp.x * rrate + mh->d.pp.lambda;
		pp.y = pp.y * rrate;
*/
		pp.x = pp.x + lambda_rate;
		*p = pp;
	}
	adjust_lambda(l_string(std_cm,"rad"),_p->x - M_PI,_p,_reso,0,size);
	for ( i = 0 ; i < size ; i ++ , _p ++ , _reso ++ ) {
		if ( *_reso < 0 )
			continue;
		pp = *_p;
		pp.x = pp.x * rrate;
		pp.y = pp.y * rrate;
		*_p = pp;
	}
}

void
map_conv_forward_pp(MAP_HISTORY * mh,GB_POINT * p,REAL1 * reso,int size)
{
int i;
GB_POINT pp;
GB_POINT3D pp3d;
double d,frate;

	frate = mh->d.pp.forward_rate;
 
	for ( i = 0 ; i < size ; i ++ , p ++ , reso ++ ) {
		if ( *reso < 0 )
			continue;
		pp = *p;
		pp.x = (pp.x  - mh->d.pp.lambda) * frate;
		pp.y = pp.y * frate;
		pp3d.y = - sin(pp.y);
		d = cos(pp.y);
		pp3d.z = d*cos(pp.x);
		pp3d.x = d*sin(pp.x);
		pp3d = mp_mul3d(mh->d.pp.forward_vol,pp3d);
		if ( pp3d.z < 0 ) {
			*reso = -1;
			continue;
		}
		*reso = (*reso)/pp3d.z;
		pp.x = pp3d.x;
		pp.y = pp3d.y;
		pp = caffen2d(&mh->d.pp.forward_sur,pp);
		*p = pp;
	}
}


void
map_conv_forward(RESOURCE * mp,GB_POINT * p,REAL1 * reso,int size)
{
int i;
GB_POINT mv;

	if ( mp == 0 )
		return; 
	switch ( mp->map.param_forward.type ) {
	case 0:
		break;
	case MT_MOVE:
		mv = mp->map.param_forward.opt.ptr;
		if ( mv.x == 0 && mv.y == 0 ) {
			break;
		}
		for ( i = 0 ; i < size ; i ++ ) {
			if ( reso[i] < 0 )
				continue;
			p[i].x += mv.x;
			p[i].y += mv.y;
		}
		break;
	case MT_ROTATE:
	case MT_LINEAR:
		for ( i = 0 ; i < size ; i ++ ) {
		  if ( reso[i] < 0 )
				continue;
			p[i] = caffen2d(
				&mp->map.param_forward.opt.affen,
				p[i]);
			reso[i] *= mp->
				map.param_forward.
				resolution_rate;
		}
		break;
	case MT_TRIANGLE:
		lock_task(map_lock);
		for ( i = 0 ; i < size ; i ++ ) {
			if ( reso[i] < 0 )
				continue;
			p[i] = _map_conv_triangle(&reso[i],
				&mp->map.param_forward,p[i]);
		}
		unlock_task(map_lock,"map_conv");
		break;
	default:
		er_panic("map_conv_forward(1)");
	}
}

void
map_from_flame(MAP_HISTORY * mh,GB_POINT * ptr,REAL1 * reso,int size)
{

	for ( ; mh ; mh = mh->next ) {
		switch ( mh->type ) {
		case MHT_MAP:
			switch ( mh->dir ) {
			case MHD_FORWARD:
				map_conv_forward(mh->d.map,ptr,reso,size);
				break;
			case MHD_REVERSE:
				map_conv_reverse(mh->d.map,ptr,reso,size);
			}
			break;
		case MHT_LINEAR:
			switch ( mh->dir ) {
			case MHD_FORWARD:
				map_conv_forward_ln(mh,ptr,reso,size);
				break;
			case MHD_REVERSE:
				map_conv_reverse_ln(mh,ptr,reso,size);
			}
			break;
		case MHT_PP:
			switch ( mh->dir ) {
			case MHD_FORWARD:
				map_conv_forward_pp(mh,ptr,reso,size);
				break;
			case MHD_REVERSE:
				map_conv_reverse_pp(mh,ptr,reso,size);
			}
			break;
		default:
			er_panic("map_from_flame(1)");
		}
	}
}



void
map_from_resource(MAP_HISTORY * mh,GB_POINT * ptr,REAL1 * reso,int size)
{

	mh->prev = 0;
	for ( ; mh && mh->next ; mh = mh->next )
		mh->next->prev = mh;
	for ( ; mh ; mh = mh->prev ) {
		switch ( mh->type ) {
		case MHT_MAP:
			switch ( mh->dir ) {
			case MHD_FORWARD:
				map_conv_reverse(mh->d.map,ptr,reso,size);
				break;
			case MHD_REVERSE:
				map_conv_forward(mh->d.map,ptr,reso,size);
			}
			break;
		case MHT_LINEAR:
			switch ( mh->dir ) {
			case MHD_FORWARD:
				map_conv_reverse_ln(mh,ptr,reso,size);
				break;
			case MHD_REVERSE:
				map_conv_forward_ln(mh,ptr,reso,size);
			}
			break;
		case MHT_PP:
			switch ( mh->dir ) {
			case MHD_FORWARD:
				map_conv_reverse_pp(mh,ptr,reso,size);
				break;
			case MHD_REVERSE:
				map_conv_forward_pp(mh,ptr,reso,size);
			}
			break;
		default:
			er_panic("map_from_resource(1)");
		}
	}

}

REAL1
convert_resolution_reverse(
	REAL1		reso,
	GB_POINT	pt,
	RESOURCE *	m)
{

	if ( reso <= 0 )
		return reso;
	switch ( m->map.param_reverse.type ) {
	case 0:
		return reso;
	case MT_MOVE:
		return reso;
	case MT_ROTATE:
		return reso/puseudo_rate
			(m->map.param_reverse.opt.affen.matrix);
	case MT_TRIANGLE:
		map_conv_triangle(&reso,
			&m->map.param_reverse,
			pt);
		return reso;
	default:
		printf("type = %i\n",m->map.param_reverse.type);
		printf("name = %s\n",
			n_string(std_cm,get_url_str2(&m->h.entry)));
		er_panic("convert_resolution_reverse(1)");
	}
	return 0;
}

REAL1
convert_resolution_forward(
	REAL1		reso,
	GB_POINT	pt,
	RESOURCE *	m)
{

	if ( reso <= 0 )
		return reso;
	switch ( m->map.param_forward.type ) {
	case 0:
		return reso;
	case MT_MOVE:
		return reso;
	case MT_ROTATE:
		return reso/puseudo_rate
			(m->map.param_forward.opt.affen.matrix);
	case MT_TRIANGLE:
		map_conv_triangle(&reso,
			&m->map.param_forward,
			pt);
		return reso;
	default:
		printf("type = %i\n",m->map.param_forward.type);
		printf("name = %s\n",
			n_string(std_cm,get_url_str2(&m->h.entry)));
		er_panic("convert_resolution_forward(1)");
	}
	return 0;
}



MAP_HISTORY *
add_mh(MAP_HISTORY * mh1,MAP_HISTORY * mh2)
{
MAP_HISTORY * mh3,* mh4,* ret;
	mh3 = 0;
	for ( ; mh1 ; mh1 = mh1->next ) {
		mh4 = d_alloc(sizeof(*mh4));
		*mh4 = *mh1;
		mh4->next = mh3;
		mh3 = mh4;
	}
	for ( ; mh2 ; mh2 = mh2->next ) {
		mh4 = d_alloc(sizeof(*mh4));
		*mh4 = *mh2;
		mh4->next = mh3;
		mh3 = mh4;
	}
	ret = 0;
	for ( ; mh3 ; ) {
		mh4 = mh3;
		mh3 = mh3->next;
		mh4->next = ret;
		ret = mh4;
	}
	return ret;
}

MAP_HISTORY *
rev_mh(MAP_HISTORY * mh)
{
MAP_HISTORY * ret,* m1;
	ret = 0;
	for ( ; mh ; mh = mh->next ) {
		m1 = d_alloc(sizeof(*m1));
		*m1 = *mh;
		m1->dir = 1-m1->dir;
		m1->next = ret;
		ret = m1;
	}
	return ret;
}

MAP_HISTORY *
copy_mh(MAP_HISTORY * mh)
{
MAP_HISTORY * ret1,* ret2, * m1;
	ret1 = 0;
	for ( ; mh ; mh = mh->next ) {
		m1 = d_alloc(sizeof(*m1));
		*m1 = *mh;
		m1->next = ret1;
		ret1 = m1;
	}
	ret2 = 0;
	for ( ; ret1 ;) {
		m1 = ret1;
		ret1 = ret1->next;
		m1->next = ret2;
		ret2 = m1;
	}
	return ret2;
}

void
free_mh(MAP_HISTORY * mh)
{
MAP_HISTORY * mh1;
	for ( ; mh ; ) {
		mh1 = mh;
		mh = mh->next;
		d_f_ree(mh1);
	}
}

MAP_HISTORY*
new_mh(RESOURCE * m,short dir)
{
MAP_HISTORY * ret;

if ( m == 0 )
er_panic("new_mh");
	ret = d_alloc(sizeof(*ret));
	ret->dir = dir;
	ret->type = MHT_MAP;
	ret->d.map = m;
	ret->next = 0;
	return ret;
}


MAP_HISTORY *
cons_mh(RESOURCE * m,short dir,MAP_HISTORY * mh)
{
MAP_HISTORY * ret;
	ret = new_mh(m,dir);
	ret->next = mh;
	return ret;
}


GROUP_SET *
new_gs(REAL1 * reso,GROUP_ID * g,int size)
{
GROUP_SET * ret;
GROUP_ID id;
	ret = d_alloc(sizeof(*ret));
	memset(ret,0,sizeof(*ret));
	id = 0;
	for ( ; size ; g ++ , reso ++ ,size -- ) {
		if ( *reso < 0 )
			continue;
		if ( *g >= id )
			id = (*g) + 1;
	}
	ret->next_id = id;
	return ret;
}

GROUP_ID
new_group(GROUP_SET * gs,GROUP_ID prev,int x_cnt,int y_cnt)
{
GROUP_SET_LIST ** gslp, * gsl;
GROUP_ID ret;
	for ( gslp = &gs->list ; *gslp ; gslp = &(*gslp)->next ) {
		gsl = *gslp;
		if ( gsl->x_cnt == x_cnt &&
				gsl->y_cnt == y_cnt &&
				gsl->prev_gid == prev ) {
			*gslp = gsl->next;
			gsl->next = gs->list;
			gs->list = gsl;
			return gsl->gid;
		}
	}
	ret = gs->next_id ++;
	gsl = d_alloc(sizeof(*gsl));
	gsl->x_cnt = x_cnt;
	gsl->y_cnt = y_cnt;
	gsl->prev_gid = prev;
	gsl->gid = ret;
	gsl->next = gs->list;
	gs->list = gsl;
	return ret;
}

void
free_gs(GROUP_SET * gs)
{
GROUP_SET_LIST * gsl;
	for ( ; gs->list ; ) {
		gsl = gs->list;
		gs->list = gsl->next;
		d_f_ree(gsl);
	}
	d_f_ree(gs);
}



void
adjust_lambda(L_CHAR * unit,REAL1 min_point,
	GB_POINT * pt_list,REAL1 * reso,GROUP_ID * group,int size)
{
REAL1 loop,max_point,x,y;
REAL1 vertical1,vertical2,vertical3;
int i;
GROUP_SET * gs;
int y_cnt,x_cnt;
	if ( group )
		gs = new_gs(reso,group,size);
	else 	gs = 0;

	if ( l_strcmp(unit,l_string(std_cm,"rad")) == 0 )
		loop = 2*M_PI;
	else if ( l_strcmp(unit,l_string(std_cm,"degree")) == 0 )
		loop = 360;
	else if ( l_strcmp(unit,l_string(std_cm,"'")) == 0 )
		loop = 360*60;
	else if ( l_strcmp(unit,l_string(std_cm,"''")) == 0 )
		loop = 360*60*60;
	else	return;
	vertical1 = loop/4;
	vertical2 = loop/2;
	vertical3 = loop*3/4;
	max_point = min_point + loop;
	for ( i = 0; i< size ; i ++ , pt_list ++ , reso ++ 
			, group ? group ++ : 0 ) {
		if ( *reso < 0 )
			continue;
		x = pt_list->x;
		y = pt_list->y;
		if ( y > 0 ) {
			y_cnt = 0;
			for ( ; y > loop ; y -= loop , y_cnt ++ );
			if ( y <= vertical1 ) {
			}
			else if ( y <= vertical3 ) {
				x += vertical2;
				y = vertical2 - y;
			}
			else {
				y = y - loop;
			}
		}
		else {
			y_cnt = 0;
			for ( ; y < -loop ; y += loop , y_cnt -- );
			if ( y >= -vertical1 ) {
			}
			else if ( y >= -vertical3 ) {
				x += vertical2;
				y = -vertical2 - y;
			}
			else {
				y = loop + y;
			}
		}
		x_cnt = 0;
		for ( ; x < min_point ; x += loop , x_cnt -- );
		for ( ; x > max_point ; x -= loop , x_cnt ++ );
		pt_list->x = x;
		pt_list->y = y;
		if ( group )
			*group = new_group(gs,*group,x_cnt,y_cnt);
	}
	if ( gs )
		free_gs(gs);
}


void
set_surp(SURP_SET * surp,GB_RECT * r,REAL1 reso)
{
GB_POINT * ptr;
GB_POINT center;
int i,j;
	ptr = surp->ptr;
	ptr[0] = r->tl;
	ptr[4] = r->br;
	ptr[2].x = r->tl.x;
	ptr[2].y = r->br.y;
	ptr[6].x = r->br.x;
	ptr[6].y = r->tl.y;

	ptr[1] = p_avg(ptr[0],ptr[2]);
	ptr[3] = p_avg(ptr[2],ptr[4]);
	ptr[5] = p_avg(ptr[4],ptr[6]);
	ptr[7] = p_avg(ptr[6],ptr[0]);

	center = ptr[SURP_NOS-1] = p_avg(r->tl,r->br);

	for ( i = 1 ; i < SURP_DEPTH ; i ++ ) {
		for ( j = 0 ; j < 8 ; j ++ ) {
			ptr[8*i+j].x = 
				(i * ptr[j].x + (SURP_DEPTH-i) * center.x)
				/SURP_DEPTH;
			ptr[8*i+j].y =
				(i + ptr[j].y + (SURP_DEPTH-i) * center.y)
				/SURP_DEPTH;
		}
	}

	for ( i = 0 ; i < SURP_NOS ; i ++ ) {
		surp->reso[i] = reso;
		surp->group[i] = 0;
	}
}

void
print_surp(SURP_SET * surp)
{
int i;
	printf("SP");
	for ( i = 0 ; i < SURP_NOS ; i ++ ) {
		if ( surp->reso[i] < 0 )
			continue;
		printf("([%i]:%f %f - %f)",
			i,
			surp->ptr[i].x,
			surp->ptr[i].y,
			surp->reso[i]);
	}
	printf("\n");
}


GROUP_ID
surp_group_optimize(SURP_SET * surp)
{
int len;
GROUP_ID * list;
GROUP_ID n,g;
int i;
	list = 0;
	len = 0;
	n = 1;
	for ( i = 0 ; i < SURP_NOS ; i ++ ) {
		if ( surp->reso[i] < 0 )
			continue;
		g = surp->group[i];
		if ( g < len ) {
			if ( list[g] )
				continue;
			list[g] = n ++;
			continue;
		}
		len = g + 1;
		list = d_re_alloc(list,len*sizeof(GROUP_ID));
		list[g] = n ++;
	}
	for ( i = 0 ; i < SURP_NOS ; i ++ ) {
		if ( surp->reso[i] < 0 )
			continue;
		surp->group[i] = list[surp->group[i]] - 1;
	}
	if ( list )
		d_f_ree(list);
	return n-1;
}

GB_RECT *
get_surp_rect(int *rect_nos,SURP_SET * surp,RESOURCE * r)
{
int i;
GB_RECT * ret;
GROUP_ID n;

	if ( r )
		adjust_lambda(
			r->h.cu.unit,
			r->h.minrect.tl.x,
			surp->ptr,
			surp->reso,
			surp->group,
			SURP_NOS);
	n = surp_group_optimize(surp);
	ret = d_alloc(sizeof(*ret)*n);

	for ( i = 0 ; i < n ; i ++ ) {
		ret[i].tl.x = ret[i].tl.y = 0;
		ret[i].br.x = ret[i].br.y = -1;
	}
	for ( i = 0 ; i < SURP_NOS ; i ++ ) {
		if ( surp->reso[i] < 0 )
			continue;
		insert_rect(&ret[surp->group[i]],surp->ptr[i]);
	}
	*rect_nos = n;
	if ( n == 0 ) {
		d_f_ree(ret);
		return 0;
	}
	return ret;
}

GB_RECT *
get_surp_rect_rate(int * ret_len,SURP_SET * surp,REAL1 rate,RESOURCE * r)
{
GB_RECT * ret, * rp;
REAL1 w,h;
REAL1 x_ofs,y_ofs;
int i,n;
	ret = get_surp_rect(&n,surp,r);
	for ( i = 0 ; i < n ; i ++ ) {
		rp = &ret[i];
		w = rp->br.x - rp->tl.x;
		h = rp->br.y - rp->tl.y;
		x_ofs = w*rate;
		y_ofs = h*rate;
		rp->br.x += x_ofs;
		rp->br.y += y_ofs;
		rp->tl.x -= x_ofs;
		rp->tl.y -= y_ofs;
	}
	*ret_len = n;
	return ret;
}

REAL1
surp_reso_max(REAL1 * reso)
{
REAL1 max;
int i;
	max = 0;
	for ( i = 0 ; i < SURP_NOS ; i ++ ) {
		if ( reso[i] == -1 )
			continue;
		if ( max < reso[i] )
			max = reso[i];
	}
	return max;
}

REAL1
surp_reso_min(REAL1 * reso)
{
REAL1 min;
int i;
	min = 0;
	for ( i = 0 ; i < SURP_NOS ; i ++ ) {
		if ( reso[i] == -1 )
			continue;
		if ( min == 0 || min > reso[i] )
			min = reso[i];
	}
	return min;
}


REAL1
get_surp_reso(SURP_SET * surp)
{
REAL1 ret;
int i;
	ret = 0;
	for ( i = 0 ; i < SURP_NOS ; i ++ ) {
		if ( surp->reso[i] < 0 )
			continue;
		ret += surp->reso[i];
	}
	return ret/SURP_NOS;
}


int
opt_mh_affen2d(AFFEN2D * a)
{
	if ( a->matrix[0][0] != 1 )
		return -1;
	if ( a->matrix[0][1] != 0 )
		return -1;
	if ( a->matrix[1][0] != 0 )
		return -1;
	if ( a->matrix[1][1] != 1 )
		return -1;
	if ( a->org.x != 0 )
		return -1;
	if ( a->org.y != 0 )
		return -1;
	return 0;
}

int
opt_mh_i_map(RESOURCE * r)
{
GB_POINT mv;
	switch ( r->map.param_forward.type ) {
	case MT_MOVE:
		mv = r->map.param_forward.opt.ptr;
		if ( mv.x == 0 && mv.y == 0 )
			return 0;
		return -1;
	case MT_ROTATE:
	case MT_LINEAR:
		return opt_mh_affen2d(&r->map.param_forward.opt.affen);
	default:
		return -1;
	}
}

MAP_HISTORY *
optimize_mh_identical(MAP_HISTORY * mh)
{
MAP_HISTORY ** mhp, * mh2;
	mhp = &mh;
	for ( ; *mhp ; ) {
		mh2 = *mhp;
		switch ( mh2->type ) {
		case MHT_MAP:
			if ( opt_mh_i_map(mh2->d.map) == 0 )
				goto ident;
			goto no_ident;
		case MHT_LINEAR:
			if ( opt_mh_affen2d(&mh2->d.ln.forward) == 0 )
				goto ident;
			goto no_ident;
		default:
			break;
		}
	no_ident:
		mhp = &mh2->next;
		continue;
	ident:
		*mhp = mh2->next;
		mh2->next = 0;
		free_mh(mh2);
	}
	return mh;
}

void
set_me(REAL1 m[2][2])
{
	m[0][0] = m[1][1] = 1;
	m[0][1] = m[1][0] = 0;
}

void
copy_ln_map2mh(MAP_HISTORY * mh)
{
RESOURCE * r;
	for ( ; mh ; mh = mh->next ) {
		if ( mh->type != MHT_MAP )
			continue;
		r = mh->d.map;
		switch ( r->map.param_forward.type ) {
		case MT_MOVE:
			mh->type = MHT_LINEAR;
			set_me(mh->d.ln.forward.matrix);
			mh->d.ln.forward.org = r->map.param_forward.opt.ptr;
			set_me(mh->d.ln.reverse.matrix);
			mh->d.ln.reverse.org = r->map.param_reverse.opt.ptr;
			mh->d.ln.forward_reso_rate =
				mh->d.ln.reverse_reso_rate = 1;
			break;
		case MT_ROTATE:
		case MT_LINEAR:
			mh->type = MHT_LINEAR;
			mh->d.ln.forward = r->map.param_forward.opt.affen;
			mh->d.ln.reverse = r->map.param_reverse.opt.affen;
			mh->d.ln.forward_reso_rate
				= r->map.param_forward.resolution_rate;
			mh->d.ln.reverse_reso_rate
				= r->map.param_reverse.resolution_rate;
			break;
		default:
			break;
		}
	}
}

MAP_HISTORY *
optimize_mh_linear(MAP_HISTORY * mh)
{
MAP_HISTORY * mh1,* mh2;
AFFEN2D a;
	copy_ln_map2mh(mh);
	for ( mh1 = mh ; mh1 ; ) {
		if ( mh1->next == 0 )
			break;
		if ( mh1->type != MHT_LINEAR ) {
			mh1 = mh1->next;
			continue;
		}
		mh2 = mh1->next;
		if ( mh2->type != MHT_LINEAR ) {
			mh1 = mh1->next;
			continue;
		}
		if ( mh1->dir == MHD_REVERSE ) {
			a = mh1->d.ln.forward;
			mh1->d.ln.forward = mh1->d.ln.reverse;
			mh1->d.ln.reverse = a;
			mh1->dir = MHD_FORWARD;
		}
		if ( mh2->dir == MHD_REVERSE ) {
			conv_affen2d(&a,&mh2->d.ln.reverse,&mh1->d.ln.forward);
			mh1->d.ln.forward = a;
			mh1->d.ln.forward_reso_rate
				= mh2->d.ln.reverse_reso_rate *
				  mh1->d.ln.forward_reso_rate;
			conv_affen2d(&a,&mh1->d.ln.reverse,&mh2->d.ln.forward);
			mh1->d.ln.reverse = a;
			mh1->d.ln.reverse_reso_rate
				= mh1->d.ln.reverse_reso_rate *
				  mh2->d.ln.forward_reso_rate;
		}
		else {
			conv_affen2d(&a,&mh2->d.ln.forward,&mh1->d.ln.forward);
			mh1->d.ln.forward = a;
			mh1->d.ln.forward_reso_rate
				= mh2->d.ln.forward_reso_rate *
				  mh1->d.ln.forward_reso_rate;
			conv_affen2d(&a,&mh1->d.ln.reverse,&mh2->d.ln.reverse);
			mh1->d.ln.reverse = a;
			mh1->d.ln.reverse_reso_rate
				= mh1->d.ln.reverse_reso_rate *
				  mh2->d.ln.reverse_reso_rate;
		}
		mh1->next = mh2->next;
		mh2->next = 0;
		free_mh(mh2);
	}
	return mh;
}

MAP_HISTORY *
optimize_mh(MAP_HISTORY * mh)
{
	mh = optimize_mh_identical(mh);
	mh = optimize_mh_linear(mh);
	return mh;
}

