/**********************************************************************
 
	Copyright (C) 2005- 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	"memory_debug.h"
#include	"matrix.h"
#include	"change_endian.h"
#include	"memory_routine.h"

INTEGER64 unsigned_convert(INTEGER64 org,int size,int opt);
INTEGER64 signed_convert(INTEGER64 org,int size);
void __short2uchar_tbl_setup();
int get_size_endian(void * ptr);


void *
mxt_alloc_copy(MATRIX_DATA_TYPE * tp,void * d,int atype,void * at_work)
{
int size;
void * ret;
	size = (*tp->get_size)(tp,d);
	if ( atype == MD_MMALLOC )
		ret = atype_alloc(size,atype,gc_text);
	else	ret = atype_alloc(size,atype,at_work);
	(*tp->copy)(tp,ret,d,atype,at_work);
	return ret;
}

void *
xx_atype_alloc(int size,int atype,void * w,char * __f,int __l)
{
void * ret;
	switch ( atype ) {
	case MD_MALLOC:
		ret = malloc(size);
		break;
	case MD_MMALLOC:
		ret = mmalloc(size,w);
		break;
	case MD_DALLOC:
		ret = xx_d_alloc(size,__f,__l);
		break;
	case MD_CALLOC:
		ret = mxc_alloc(w,size);
		break;
	default:
		er_panic("atype_alloc");
	}
	return ret;
}

void *
xx_mxt_alloc_data(MATRIX_DATA_TYPE * tp,int atype,void * d,void * w,
		char * __f,int __l)
{
int size;
void * ret;
	size = (*tp->get_size)(tp,d);
	ret = xx_atype_alloc(size,atype,gc_text,__f,__l);
	(*tp->copy)(tp,ret,d,atype,w);
	return ret;
}

void *
xx_mxt_alloc_vector(MATRIX_DATA_TYPE * tp,int atype,void * d,void * w,
		 char * __f,int __l)
{
MATRIX_ALLOC_VECTOR_PARAM * p;
int header_size;
int element_size;
int offset;
int ix_size;
int total_size;
MATRIX_DATA_HEADER * h;
int * ix;
int i;
char * q;
	p = (MATRIX_ALLOC_VECTOR_PARAM*)d;
	header_size = sizeof(MATRIX_DATA_HEADER)
			+ p->dim * sizeof(int);
	element_size = (*tp->parent->get_size)(tp,0);
	offset = 0;
	for ( ; offset < header_size ; offset += element_size );
	ix_size = 1;
	for ( i = 0 ; i < p->dim ; i ++ )
		ix_size *= p->ix_size[i];
	total_size = offset + ix_size * element_size;
	switch ( atype ) {
	case MD_MALLOC:
		h = malloc(total_size);
		break;
	case MD_MMALLOC:
		h = mmalloc(total_size,gc_text);
		break;
	case MD_DALLOC:
		h = xx_d_alloc(total_size,__f,__l);
		break;
	case MD_CALLOC:
		if ( w )
			h = xx_mxc_alloc(w,total_size,__f,__l);
		else	h = xx_mxc_alloc(p->mxc,total_size,__f,__l);
		break;
	default:
		er_panic("mxt_alloc_vector");
	}
	h->offset = offset;
	h->type = tp->type;
	h->dim = p->dim;
	ix = (int*)(h+1);
	for ( i= 0 ; i < p->dim ; i ++ )
		ix[i] = p->ix_size[i];
	if ( p->default_data ) {
 		q = ((char*)h) + offset;
 		for ( i = 0 ; i < ix_size ; i ++ ) {
 			(*tp->parent->copy)(tp->parent,q,p->default_data,atype,w);
 			q += element_size;
		}
	}
	return (void*)h;
}

void *
mxt_alloc_vector_by_dim_code(MATRIX * m,MATRIX_DATA_TYPE * tp,int atype,
		INTEGER64 * dim_code,void * def)
{
INTEGER64 * dc;
int * dc_size;
int i;
INTEGER64 lim;
MATRIX_ALLOC_VECTOR_PARAM p;
void * ret;
	dc = copy_dim_code(m,dim_code);
	dc_size = d_alloc(sizeof(int)*m->p.dim);
	for ( i = 1 ; i <= m->p.dim ; i ++ ) {
		dc[i] = dc[i] >> (m->dim_divide[i-1] * dc[0]);
		dc[i] &= - (((INTEGER64)1) << m->block_size[i-1]);
		lim = dc[i] + (((INTEGER64)1)<<m->block_size[i-1]);
		if ( lim >= m->pixel_size_list[dc[0]][i-1] )
			lim = m->pixel_size_list[dc[0]][i-1];
		dc_size[i-1] = lim - dc[i];
		if ( m->p.flags & MPF_INDEX_HEM )
			dc_size[i-1] ++;
	}
	p.ix_size = dc_size;
	p.dim = m->p.dim;
	p.default_data = def;
	p.mxc = 0;
	ret = (*tp->alloc_data)(tp,atype,&p,0,__FILE__,__LINE__);
	d_f_ree(dc);
	d_f_ree(dc_size);
	return ret;
}


void 
mxt_free_data(struct matrix_data_type* tp,void * d)
{
	d_f_ree(d);
}



void * 
mxt_vector_sexp2md(int * cpy,struct matrix_data_type* tp,XL_SEXP * s)
{
void * ret;
MATRIX_DATA_HEADER * h;
	switch ( get_type(s) ) {
	case XLT_RAW:
		ret = s->raw.data;
		break;
	case XLT_PTR:
		ret = s->ptr.ptr;
		break;
	default:
		return 0;
	}
	h = (MATRIX_DATA_HEADER*)ret;
	if ( h->type != tp->type )
		return 0;
	if ( cpy )
		*cpy = 0;
	return ret;
}

XL_SEXP * 
mxt_vector_md2sexp(struct matrix_data_type* tp,void * d)
{
	return get_ptr(d,0);
}

void
mxt_vector_copy(struct matrix_data_type* tp,void* d1,void* d2,int atype,void * at_work)
{
int size;
	size = (*tp->get_size)(tp,d2);
	memcpy(d1,d2,size);
}


void
get_matrix_dh_set(MATRIX_DH_SET * dh,void * d)
{
int i;
int t;
	dh->hd = (MATRIX_DATA_HEADER*)d;
	dh->ix = (int*)(dh->hd + 1);
	dh->offset = ((char*)d) + dh->hd->offset;
	t = 1;
	for ( i = 0 ; i < dh->hd->dim ; i ++ )
		t *= dh->ix[i];
	dh->total_element = t;
	dh->tp = get_matrix_data_type(dh->hd->type);
}


int cmp_dh_set(MATRIX_DH_SET* dh1,MATRIX_DH_SET* dh2)
{
int i;
	if ( dh1->hd->type < dh2->hd->type )
		return -1;
	if ( dh1->hd->type > dh2->hd->type )
		return 1;
	if ( dh1->hd->dim < dh2->hd->dim )
		return -1;
	if ( dh1->hd->dim > dh2->hd->dim )
		return 1;
	for ( i = 0 ; i < dh1->hd->dim ; i ++ ) {
		if ( dh1->ix[i] < dh2->ix[i] )
			return -1;
		if ( dh1->ix[i] > dh2->ix[i] )
			return 1;
	}
	return 0;
}


int
mxt_vector_get_size(MATRIX_DATA_TYPE * tp,void * d)
{
MATRIX_DATA_HEADER * h;
int i;
int * ix;
int sz;
	h = d;
	ix = (int*)(h+1);
	sz = 1;
	for ( i = 0 ; i < h->dim ; i ++ )
		sz *= ix[i];
	return h->offset + sz * (*tp->parent->get_size)(tp,d);
}


INTEGER64
signed_convert(INTEGER64 org,int size)
{
INTEGER64 limit;
	limit = ((INTEGER64)1)<<(size-1);
	if ( org >= limit )
		org = limit - 1;
	else if ( org < - limit )
		org = - limit;
	return org;
}


INTEGER64
unsigned_convert(INTEGER64 org,int size,int opt)
{
INTEGER64 limit;
	switch ( opt ) {
	case 0:
		break;
	case OPT_TRUNC:
		break;
	case OPT_CURVE:
		org = __short2uchar(org);
	default:
		er_panic("unsigned_convert");
	}
	limit = ((INTEGER64)1)<<size;
	if ( org >= limit )
		org = limit - 1;
	else if ( org < 0 )
		org = 0;
	return org;
}



void
round_int(char type,void * dest,INTEGER64 org,int opt)
{
	switch ( type & MDT_BASE_TYPE ) {
	case MDT_BIT:
		if ( org )
			*(char*)dest = 1;
		else	*(char*)dest = 0;
		break;
	case MDT_INT8:
		org = signed_convert(org,8);
		*(char*)dest = org;
		break;
	case MDT_INT16:
		org = signed_convert(org,16);
		*(short*)dest = org;
		break;
	case MDT_INT32:
		org = signed_convert(org,32);
		*(int*)dest = org;
		break;
	case MDT_INT64:
		*(INTEGER64*)dest = org;
		break;
	case MDT_UINT8:
		org = unsigned_convert(org,8,opt);
		*(unsigned char*)dest = org;
		break;
	case MDT_UINT16:
		org = unsigned_convert(org,16,0);
		*(unsigned short*)dest = org;
		break;
	case MDT_UINT32:
		org = unsigned_convert(org,32,0);
		*(unsigned int*)dest = org;
		break;
	case MDT_UINT64:
		*(INTEGER64*)dest = org;
		break;
	case MDT_FLOAT:
		*(float*)dest = org;
		break;
	case MDT_DOUBLE:
		*(double*)dest = org;
		break;
	}
}


unsigned char __short2uchar_tbl[256*2];
short __uchar2short_tbl[256];
int tbl_flag;

void
__short2uchar_tbl_setup()
{
int x;
double _x;
unsigned char y;
double a;
char flags[2*256];
	for ( x = 0 ; x < 2*256 ; x ++ )
		flags[x] = -1;
	a = 4.0/(255*255);
	for ( x = 0 ; x < 256 ; x ++ ) {
		_x = ((double)x) - 127.5;
		__uchar2short_tbl[x] = rint(_x + a * _x*_x*_x);
		__short2uchar_tbl[__uchar2short_tbl[x+255]] = x;
		flags[__uchar2short_tbl[x+255]] = 0;
	}
	y = 0;
	for ( x = -255 ; x < 256 ; x ++ ) {
		if ( flags[x] < 0 )
			__short2uchar_tbl[x] = y;
		else 	y = __short2uchar_tbl[x];
	}
}

unsigned char
__short2uchar(short in)
{

	if ( tbl_flag == 0 )
		__short2uchar_tbl_setup();
	if ( in >= 256 )
		return 255;
	if ( in <= 256 )
		return 0;
	return __short2uchar_tbl[in+255];
}

short
__uchar2short(unsigned char in)
{
	if ( tbl_flag == 0 )
		__short2uchar_tbl_setup();
	return __uchar2short_tbl[in];
}


void
mxt_endian_nothing(void * d)
{
}


void
mxt_endian_vector_to_host(void * v)
{
MATRIX_DH_SET ds;
int i;
int size;
char * p;
MATRIX_DATA_TYPE * tp;
	ds.hd = v;
	ds.ix = (int*)(ds.hd + 1);
	change_endian(ds.hd->dim);
	for ( i = 0 ; i < ds.hd->dim ; i ++ ) {
		change_endian(ds.ix[i]);
	}
	get_matrix_dh_set(&ds,v);
	tp = ds.tp->parent;
	size = (*tp->get_size)(tp,0);
	p = ds.offset;
	for ( i = 0 ; i < ds.total_element ; i ++ ) {
		(*tp->endian_to_host)(p);
		p += size;
	}
}



void
mxt_endian_vector_to_net(void * v)
{
MATRIX_DH_SET ds;
int i;
int size;
char * p;
MATRIX_DATA_TYPE * tp;
	get_matrix_dh_set(&ds,v);
	tp = ds.tp->parent;
	size = (*tp->get_size)(tp,0);
	p = ds.offset;
	for ( i = 0 ; i < ds.total_element ; i ++ ) {
		(*tp->endian_to_net)(p);
		p += size;
	}
	for ( i = 0 ; i < ds.hd->dim ; i ++ ) {
		change_endian(ds.ix[i])
	}
	change_endian(ds.hd->dim);
}

void
mxt_convert_basic_to_net(MATRIX_DATA_TYPE * tp,RECORD_LIST64 * rlp,void * d)
{
void * ret;
int size;
	ret = d_alloc(size=(*tp->get_size)(tp,d));
	memcpy(ret,d,size);
	(*tp->endian_to_net)(ret);
	set_recordlist_chain64(rlp,ret,size,1);
}

int
get_size_endian(void * ptr)
{
MATRIX_DATA_HEADER * d;
unsigned short dim;
int * ix;
int _ix;
MATRIX_DATA_TYPE * tp;

int el_size;
int i;
int t;
	d = ptr;
	dim = d->dim;
	change_endian(dim);
	ix = (int*)(d + 1);
	t = 1;
	for ( i = 0 ; i < dim ; i ++ ) {
		_ix = ix[i];
		change_endian(_ix);
		t *= _ix;
	}
	tp = get_matrix_data_type(d->type);
	el_size = (*tp->parent->get_size)(tp->parent,0);
	return d->offset + el_size * t;
}

void *
mxt_convert_basic_to_host(MATRIX_DATA_TYPE * tp,void * d,int atype,void * at_work)
{
void * ret;
int size;

	if ( tp->type & MDT_VECTOR )
		ret = atype_alloc(size=get_size_endian(d),atype,at_work);
	else	ret = atype_alloc(size=(*tp->get_size)(tp,d),atype,at_work);
	memcpy(ret,d,size);
	(*tp->endian_to_host)(ret);
	return ret;
}

void * 
mxt_mul_mm(void * d1,void * d2,MX_CACHE * c)
{
MATRIX_DH_SET ds;
	get_matrix_dh_set(&ds,d1);
	return (*ds.tp->mul_mm)(ds.tp,d1,d2,c);
}


void * 
mxt_mul_sv(void * d1,void * d2,MX_CACHE * c)
{
MATRIX_DH_SET ds;
	get_matrix_dh_set(&ds,d2);
	return (*ds.tp->mul_sv)(ds.tp,d1,d2,c);
}
void * 
mxt_add_vv(void * d1,void * d2,MX_CACHE * c)
{
MATRIX_DH_SET ds;
	get_matrix_dh_set(&ds,d1);
	return (*ds.tp->add_vv)(ds.tp,d1,d2,c);
}

void * mxt_sub_vv(void * d1,void * d2,MX_CACHE * c)
{
MATRIX_DH_SET ds;
	get_matrix_dh_set(&ds,d1);
	return (*ds.tp->sub_vv)(ds.tp,d1,d2,c);
}
void * 
mxt_trans_m(void * d1,MX_CACHE * c)
{
MATRIX_DH_SET ds;
	get_matrix_dh_set(&ds,d1);
	return (*ds.tp->trans_m)(ds.tp,d1,c);
}


void *
mxt_compound_vv(void * d1,void * d2,int ix,MX_CACHE * mxc)
{
MATRIX_DH_SET d1_ds,d2_ds,ret_ds;
int i;
int dim;
int * new_ix;
MATRIX_ALLOC_VECTOR_PARAM vp;
void * ret;
int seq;
char * d_ptr;
char * ret_ptr;
int el_size;
int ret_seq;
	get_matrix_dh_set(&d1_ds,d1);
	get_matrix_dh_set(&d2_ds,d2);
	if ( ix < 0 || ix >= d1_ds.hd->dim )
		return 0;
	if ( d1_ds.hd->dim != d2_ds.hd->dim )
		return 0;
	if ( d1_ds.tp != d2_ds.tp )
		return 0;
	dim = d1_ds.hd->dim;

	new_ix = d_alloc(sizeof(int)*dim);

	for ( i = 0 ; i < dim ; i ++ ) {
		if ( i == ix ) {
			new_ix[i] = d1_ds.ix[i] + d2_ds.ix[i];
		}
		else {
			if ( d1_ds.ix[i] != d2_ds.ix[i] ) {
				d_f_ree(new_ix);
				return 0;
			}
			new_ix[i] = d1_ds.ix[i];
		}
	}
	vp.dim = dim;
	vp.ix_size = new_ix;
	vp.default_data = 0;
	vp.mxc = mxc;
	ret = mxt_alloc_vector(
			d1_ds.tp,
			MD_CALLOC,
			&vp,0);
	get_matrix_dh_set(&ret_ds,ret);
	ret_ptr = ret_ds.offset;

	el_size = (*d1_ds.tp->parent->get_size)(d1_ds.tp->parent,0);
	d_ptr = d1_ds.offset;

	for ( seq = 0 ; seq < d1_ds.total_element ; seq ++ ) {
		get_ix_from_seq(new_ix,d1_ds.ix,seq,dim);
		ret_seq = get_seq_from_ix(new_ix,ret_ds.ix,dim);
		memcpy(ret_ptr+ret_seq*el_size,d_ptr+seq*el_size,el_size);
	}

	d_ptr = d2_ds.offset;

	for ( seq = 0 ; seq < d2_ds.total_element ; seq ++ ) {
		get_ix_from_seq(new_ix,d2_ds.ix,seq,dim);
		new_ix[ix] += d1_ds.ix[ix];
		ret_seq = get_seq_from_ix(new_ix,ret_ds.ix,dim);
		memcpy(ret_ptr+ret_seq*el_size,d_ptr+seq*el_size,el_size);
	}
	d_f_ree(new_ix);
	return ret;
}

void * 
mxt_e_m(MATRIX_DATA_TYPE * tp,int size,MX_CACHE * mxc)
{
MATRIX_ALLOC_VECTOR_PARAM p;
int ix[2];
int el_size;
void * ret;
char * ret_ptr;
MATRIX_DH_SET ds;
int i;
	el_size = (*tp->parent->get_size)(tp->parent,0);
	ix[0] = ix[1] = size;
	p.dim = 2;
	p.ix_size = ix;
	p.default_data = d_alloc(el_size);
	p.mxc = mxc;
	(*tp->parent->get_zero)(tp->parent,p.default_data);
	ret = mxt_alloc_vector(tp,MD_CALLOC,&p,0);
	get_matrix_dh_set(&ds,ret);
	ret_ptr = ds.offset;
	for ( i = 0 ; i < size ; i ++ ) {
		ix[0] = ix[1] = i;
		(*tp->parent->get_el)(tp->parent,
			ret_ptr+get_seq_from_ix(ix,ds.ix,2)*el_size);
	}
	d_f_ree(p.default_data);
	return ret;
}


void * 
mxt_get_matrix_from_ary(MATRIX_DATA_TYPE * tp,void * ary,int size,int dim,
		MX_CACHE * mxc)
{
void * ret;
MATRIX_ALLOC_VECTOR_PARAM p;
int ix[2];
MATRIX_DH_SET ds;
int i;
char * ptr, * qtr;
int sz;
	if ( dim > 2 )
		return 0;
	if ( dim < 1 )
		return 0;
	ix[0] = size;
	ix[1] = 1;
	p.dim = dim;
	p.ix_size = ix;
	p.default_data = 0;
	p.mxc = mxc;
	ret = mxt_alloc_vector(tp,MD_CALLOC,&p,0);
	get_matrix_dh_set(&ds,ret);
	ptr = ds.offset;
	qtr = ary;
	sz = (*tp->parent->get_size)(tp->parent,0);
	for ( i = 0 ; i < ds.total_element ; i ++ )
		memcpy(ptr,qtr,sz);
	return ret;
}


void
mxt_print_vector(
	MATRIX_DATA_TYPE * tp,
	MATRIX_STRING_BUFFER * b,
	void * d,char*fmt)
{
char buffer[20];
MATRIX_DH_SET ds;
int i,j;
int el_size;
char * ptr;

	get_matrix_dh_set(&ds,d);
	sprintf(buffer,"[dim:%i ",ds.hd->dim);
	out_matrix_string_buffer(b,buffer);

	for ( i = 0 ; i < ds.hd->dim ; i ++ ) {
		sprintf(buffer,"%i ",ds.ix[i]);
		out_matrix_string_buffer(b,buffer);
	}
	sprintf(buffer,":::\n");
	out_matrix_string_buffer(b,buffer);

	el_size = (*tp->parent->get_size)(tp->parent,ds.offset);
	ptr = ds.offset;
	switch ( ds.hd->dim ) {
	case 1:
		for ( j = 0 ; j < ds.total_element ; j ++ ) {
			(*tp->parent->print_data)(
				tp->parent,
				b,
				ptr,
				fmt);
			ptr += el_size;
		}
		break;
	default:
		for ( j = 0 ; j < ds.total_element ; j ++ ) {
			(*tp->parent->print_data)(
				tp->parent,
				b,
				ptr,
				fmt);
			ptr += el_size;
			if ( ((j+1) % ds.ix[0]) == 0 && 
					(j+1) != ds.total_element )
				out_matrix_string_buffer(b," /\n");
			else	out_matrix_string_buffer(b," ");
		}
		break;
	}
	out_matrix_string_buffer(b,"]");
}

char* 
mxt_print(MATRIX_DATA_TYPE * tp,void * d,char*fmt)
{
MATRIX_STRING_BUFFER b;
char * ret;
	b.next = 0;
	b.tail = 0;
	b.data = 0;
	(*tp->print_data)(tp,&b,d,fmt);
	ret = get_matrix_string_buffer(&b);
	free_matrix_string_buffer(&b);
	set_buffer(ret);
	return ret;
}


void
out_matrix_string_buffer(MATRIX_STRING_BUFFER * b,char * str)
{
MATRIX_STRING_BUFFER * b2;
	b2 = d_alloc(sizeof(*b2));
	b2->data = copy_str(str);
	b2->next = 0;
	b2->tail = 0;
	if ( b->tail ) {
		b->tail->next = b2;
		b->tail = b2;
	}
	else {
		b->tail = b->next = b2;
	}
}

void
free_matrix_string_buffer(MATRIX_STRING_BUFFER * b)
{
MATRIX_STRING_BUFFER * b2;
	for ( ; b->next ; ) {
		b2 = b->next;
		b->next = b2->next;
		d_f_ree(b2);
	}
	b->tail = 0;
}

char *
get_matrix_string_buffer(MATRIX_STRING_BUFFER * b)
{
char * ret;
int len;
MATRIX_STRING_BUFFER * b2;
char * ptr;
	b2 = b->next;
	for ( len = 0 ; b2 ; b2 = b2->next ) {
		b2->len = strlen(b2->data);
		len += b2->len;
	}
	ret = d_alloc(len+1);
	ptr = ret;
	for ( b2 = b->next ; b2 ; b2 = b2->next ) {
		strcpy(ptr,b2->data);
		ptr += b2->len;
	}
	return ret;
}

