/**********************************************************************
 
	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	<stdlib.h>
#include	<stdio.h>
#include	<fcntl.h>
#include	<sys/types.h>
#include	"r64.h"
#include	"jpeg.h"
#include	"machine/include.h"
#include	"u_file.h"

typedef struct cr_buffer {
	int		w_ofs;
	int		h_ofs;
	int		w_len;
	int		h_len;
	unsigned char *	buf;
} CR_BUFFER;


unsigned char *
none_compress(int * size,unsigned char * src,int w,int h)
{
char * ret;
int i;
	ret = malloc(w*h*3+2);
	for ( i = 0 ; i < w*h ; i ++ ) {
		ret[i*3+2] = src[i*4];
		ret[i*3+3] = src[i*4+1];
		ret[i*3+4] = src[i*4+2];
	}
	ret[0] = (w-1);
	ret[1] = (h-1);
	*size = w*h*3 + 2;
	return ret;
}

unsigned char *
none_uncompress(int * w,int * h,unsigned char * src,int size)
{
unsigned char _w,_h;
unsigned char * ret;
int __w,__h;
int i;
	if ( size < 2 )
		return 0;
	_w = src[0];
	_h = src[1];
	__w = ((int)_w) + 1;
	__h = ((int)_h) + 1;
	*w = __w;
	*h = __h;
	ret = malloc(__w*__h*4);
	for ( i = 0 ; i < __w*__h ; i ++ ) {
		ret[i*4] = src[i*3+2];
		ret[i*4+1] = src[i*3+3];
		ret[i*4+2] = src[i*3+4];
		ret[i*4+3] = 0;
	}
	return ret;
}

/*
unsigned char
cr_cal(int cc,char cal)
{
	cc = cc + cal;
	if ( cc >= 256 )
		cc = 255;
	if ( cc < 0 )
		cc = 0;
	return cc;
}


int
cr_inv_cal(int cc,char cal)
{
	return cc - cal;
}
*/

unsigned char 
cr_cal(int cc,char cal)
{
	cc = cc + cal;
	if ( cc >= 256 - CR_TH ) {
		cc = (cc - 256 + CR_TH)*CR_TH/(CR_LIMIT+CR_TH)
			+ 256 - CR_TH;
	}
	else if ( cc < CR_TH ) {
		cc = (cc - CR_TH)*CR_TH/(CR_LIMIT+CR_TH)
			+ CR_TH;
	}
	return cc;
}


int
cr_inv_cal(int cc,char cal)
{
	if ( cc >= 256 - CR_TH ) {
		cc = (cc - 256 + CR_TH)*(CR_LIMIT+CR_TH)/CR_TH
			+ 256 - CR_TH;
	}
	else if ( cc < CR_TH ) {
		cc = (cc - CR_TH)*(CR_LIMIT+CR_TH)/CR_TH
			+ CR_TH;
	}
	cc = cc - cal;
	return cc;
}


unsigned int loading_file_size;
unsigned int indicate_file_size;

int
_cr_write(
	CR_BUFFER * parent,
	CR_FILE * f,R64_FILE * r64,int w_ofs,int h_ofs,int level)
{
unsigned char col[3];
CR_BUFFER crb;
int x,y;
int xx,yy;
int ret;
int * ibuf;
int ix,pix;
int max[3],min[3];
CR_TBL tbl;
int cal[3];
int size;
char * dst;
int rst;
unsigned char * ret_buf;
int _w,_h;
int i;
int tbl_size;
int test;

	ret = 0;

	if ( w_ofs >= f->level_w[level] )
		return 0;
	if ( h_ofs >= f->level_h[level] )
		return 0;
	crb.w_ofs = w_ofs;
	crb.h_ofs = h_ofs;
	crb.w_len = crb.h_len = f->header.rect_size;
	if ( crb.w_ofs + crb.w_len > f->level_w[level] )
		crb.w_len = f->level_w[level] - crb.w_ofs;
	if ( crb.h_ofs + crb.h_len > f->level_h[level] )
		crb.h_len = f->level_h[level] - crb.h_ofs;
	crb.buf = malloc(crb.h_len*crb.w_len*4);
	ibuf = malloc(crb.h_len*crb.w_len*4*sizeof(int));

	for ( x = 0 ; x < crb.w_len ; x ++ )
		for ( y = 0 ; y < crb.h_len ; y ++ ) {
			if ( r64_red(col,r64,
					crb.w_ofs+x,
					crb.h_ofs+y,
					level) < 0 ) {
				ret = -1;
				free(ibuf);
				goto err1;
			}
			if ( r64->header.type == 'B' ||
				r64->header.type == 'G' ) {
				col[1] = col[0];
				col[2] = col[0];
			}
			ix = (x + y*crb.w_len)*4;
			if ( parent ) {
				xx = (crb.w_ofs + x)>>1;
				yy = (crb.h_ofs + y)>>1;
				pix = ((xx - parent->w_ofs)
					+ (yy - parent->h_ofs)
					* parent->w_len)*4;
				ibuf[ix] = (((int)col[0])
					- ((int)parent->buf[pix]));
				ibuf[ix+1] = (((int)col[1])
					- ((int)parent->buf[pix+1]));
				ibuf[ix+2] = (((int)col[2])
					- ((int)parent->buf[pix+2]));
				ibuf[ix+3] = 0;
			}
			else {
				ibuf[ix] = col[0];
				ibuf[ix+1] = col[1];
				ibuf[ix+2] = col[2];
			}
		}

	loading_file_size += crb.w_len * crb.h_len * 3;
	if ( loading_file_size >= indicate_file_size ) {
		printf("loading %i   \r",loading_file_size);
		indicate_file_size += 10000;
	}

	for ( i = 0 ; i < 3 ; i ++ ) {
		max[i] = 0x80000000;
		min[i] = 0x7fffffff;
	}
	for ( i = 0 ; i < crb.h_len * crb.w_len * 4 ; i ++ ) {
		rst = i % 4;
		if ( rst == 3 )
			continue;
		if ( ibuf[i] < min[rst] )
			min[rst] = ibuf[i];
		if ( ibuf[i] > max[rst] )
			max[rst] = ibuf[i];
	}
	for ( i = 0 ; i < 3 ; i ++ ) {
		cal[i] = 128 - (max[i]+min[i])/2;
		if ( cal[i] > 0x7f )
			cal[i] = 0x7f;
		else if ( cal[i] < -0x80 )
			cal[i] = -0x80;
	}

	if ( parent ) {
		for ( i = 0 ; i < crb.h_len * crb.w_len * 4 ; i ++ ) {
			if ( (i % 4) == 3 )
				crb.buf[i] = 0;
			else {
				crb.buf[i] = cr_cal(ibuf[i],cal[i%4]);
			}
		}
	}
	else {
		for ( i = 0 ; i < crb.h_len * crb.w_len * 4 ; i ++ ) {
			if ( (i % 4) == 3 )
				crb.buf[i] = 0;
			else {
				crb.buf[i] = ibuf[i];
			}
		}
	}

	if ( crb.h_len >= 16 && crb.w_len >= 16 ) {
		dst = jpeg_compress(&tbl.size,crb.buf,crb.w_len,crb.h_len,
					80);
		tbl.type = 'j';
	}
	else {
		dst = none_compress(&tbl.size,crb.buf,
			crb.w_len,crb.h_len);
		tbl.type = 'n';
	}

	for ( i = 0 ; i < 3 ; i ++ )
		tbl.cal[i] = cal[i];
	tbl.ofs = f->write_ofs;
	tbl_size = tbl.size;
	change_endian_cr_tbl(&tbl);
	u_lseek64(f->fd,f->write_ofs,SEEK_SET);
	u_write(f->fd,dst,tbl_size);
	f->write_ofs = u_lseek64(f->fd,0,SEEK_CUR);
	u_lseek64(f->fd,get_tbl_ofs(f,crb.w_ofs,crb.h_ofs,level),SEEK_SET);
	u_write(f->fd,&tbl,sizeof(tbl));

	change_endian_cr_tbl(&tbl);

	if ( level == 0 ) {
		free(ibuf);
		free(dst);
		goto err1;
	}

	switch ( tbl.type ) {
	case 'n':
		ret_buf = none_uncompress(&_w,&_h,dst,tbl.size);
		break;
	case 'j':
		ret_buf = jpeg_uncompress(&_w,&_h,dst,tbl.size);
		break;
	default:
		er_panic("_cr_write(1)");
	}
	free(dst);

	if ( _w != crb.w_len || _h != crb.h_len )
		er_panic("_cr_write(2)");

	if ( parent ) {
		for ( i = 0 ; i < crb.h_len * crb.w_len * 4 ; i ++ ) {
			if ( (i % 4) == 3 )
				ibuf[i] = 0;
			else	ibuf[i] = cr_inv_cal(ret_buf[i],cal[i%4]);
		}

		for ( x = 0 ; x < crb.w_len ; x ++ )
			for ( y = 0 ; y < crb.h_len ; y ++ ) {
			int tmp;
				ix = (x + y*crb.w_len)*4;
				xx = (crb.w_ofs + x)>>1;
				yy = (crb.h_ofs + y)>>1;
				pix = ((xx - parent->w_ofs)
					+ (yy - parent->h_ofs)
					* parent->w_len)*4;

				tmp = parent->buf[pix] + ibuf[ix];
				if ( tmp >= 256 )
					tmp = 255;
				if ( tmp < 0 )
					tmp = 0;
				crb.buf[ix] = tmp & 0x0ff;
				tmp = parent->buf[pix+1] + ibuf[ix+1];
				if ( tmp >= 256 )
					tmp = 255;
				if ( tmp < 0 )
					tmp = 0;
				crb.buf[ix+1] = tmp & 0x0ff;
				tmp = parent->buf[pix+2] + ibuf[ix+2];
				if ( tmp >= 256 )
					tmp = 255;
				if ( tmp < 0 )
					tmp = 0;
				crb.buf[ix+2] = tmp & 0x0ff;

			}
	}
	else {
		for ( i = 0 ; i < crb.w_len * crb.h_len * 4 ; i ++ )
			crb.buf[i] = ret_buf[i];
	}
	free(ibuf);
	free(ret_buf);

	if ( _cr_write(&crb,f,r64,
			2*w_ofs,
			2*h_ofs,
			level-1) < 0 ) {
		ret = -1;
		goto err1;
	}
	if ( _cr_write(&crb,f,r64,
			2*w_ofs + f->header.rect_size,
			2*h_ofs,
			level-1) < 0 ) {
		ret = -1;
		goto err1;
	}
	if ( _cr_write(&crb,f,r64,
			2*w_ofs,
			2*h_ofs + f->header.rect_size,
			level-1) < 0 ) {
		ret = -1;
		goto err1;
	}
	if ( _cr_write(&crb,f,r64,
			2*w_ofs + f->header.rect_size,
			2*h_ofs + f->header.rect_size,
			level-1) < 0 ) {
		ret = -1;
		goto err1;
	}

err1:
	free(crb.buf);
	return ret;
}

int
cr_write_file(char * filename,int mode,int rect_size,R64_FILE * r64)
{
CR_FILE * f;
int level;
int ret;

	loading_file_size = 0;
	f = cr_open_file(
		filename,
		O_RDWR|O_TRUNC|O_CREAT,
		mode,
		rect_size,
		r64->header.width,
		r64->header.height);
	printf("r64 width=%d height=%d", r64->header.width, r64->header.height);
	if ( f == 0 )
		return -1;
	level = LEVEL_OFS_LEN-1;
	for ( ; r64->level_ofs[level] == 0 && level >= 0 ; level -- );
	if ( level < 0 ) {
		r64_error = E_FATAL;
		return -1;
	}
	ret = _cr_write(0,f,r64,0,0,level);
	cr_close_file(f);
printf("\n");
	return ret;
}
