/*
**  bmp2png --- conversion from (Windows or OS/2 style) BMP to PNG
**  png2bmp --- conversion from PNG to (Windows style) BMP
**
**  Copyright (C) 1999-2001 MIYASAKA Masaru
**
**  For conditions of distribution and use,
**  see copyright notice in common.h.
*/

#include "common.h"

#if defined(__DJGPP__)	/* DJGPP V.2 */
#include <crt0.h>
int _crt0_startup_flags = _CRT0_FLAG_DISALLOW_RESPONSE_FILES;
unsigned short _djstat_flags =		/* for stat() */
	_STAT_INODE|_STAT_EXEC_EXT|_STAT_EXEC_MAGIC|_STAT_DIRSIZE|_STAT_ROOT_TIME;
#endif

#if defined(__BORLANDC__)	/* Borland C++ */
#include <wildargs.h>
typedef void _RTLENTRY (* _RTLENTRY _argv_expand_fnc)(char *, _PFN_ADDARG);
typedef void _RTLENTRY (* _RTLENTRY _wargv_expand_fnc)(wchar_t *, _PFN_ADDARG);
_argv_expand_fnc  _argv_expand_ptr  = _expand_wild;		/* expand wild cards */
_wargv_expand_fnc _wargv_expand_ptr = _wexpand_wild;
#endif


/* -----------------------------------------------------------------------
**		screen management
*/

#define LINE_LEN	79
#define STAT_LEN	22
#define DOTS_MAX	(LINE_LEN-STAT_LEN-1)

static char status_msg[320];
static int  scaleflag = 0;
static int  maxdots   = 0;
static int  currdots  = -1;

int quietmode = 0;
int errorlog  = 0;


static void print_status( void )
{
	fprintf( stderr, "\r%-*.*s ", STAT_LEN, STAT_LEN, status_msg );
	fflush( stderr );
	currdots = 0;
}

static void put_dots( int dotchar, int num )
{
	int m,n;

	if( num > DOTS_MAX ) num = DOTS_MAX;
	if( currdots==-1 ) print_status();

	for( m=n=(num-currdots) ; --n>=0 ; ){
		fputc( dotchar, stderr );
	}
	if( m>0 ){
		currdots = num;
		fflush( stderr );
	}
}

static void print_scale( void )
{
	print_status();
	put_dots( '.', maxdots );
	print_status();
	scaleflag = 1;
}

static void init_progress( int max )
{
	if( quietmode ) return;

	maxdots = max;
	print_scale();
}

static void update_progress( int num )
{
	if( quietmode ) return;

	if( !scaleflag ) print_scale();
	put_dots( 'o', num );
}

static void clear_line( void )
{
	if( quietmode ) return;

	fprintf( stderr, "\r%*c\r", LINE_LEN, ' ' );
	scaleflag = 0;
	currdots = -1;
}

void xprintf( const char *fmt, ... )
{
	va_list ap;
	FILE *f;

	va_start( ap, fmt );

	clear_line();
	vfprintf( stderr, fmt, ap );
	fflush( stderr );

	if( errorlog && (f=fopen(errlogfile,"a"))!=NULL ){
		vfprintf( f, fmt, ap );
		fclose( f );
	}

	va_end( ap );
}

void set_status( const char *fmt, ... )
{
	va_list ap;

	if( quietmode ) return;

	va_start( ap, fmt );
	vsprintf( status_msg, fmt, ap );
	va_end( ap );

	print_status();
}

void feed_line( void )
{
	if( quietmode ) return;

	fputc( '\n', stderr );
	fflush( stderr );
	scaleflag = 0;
	currdots = -1;
}


/* -----------------------------------------------------------------------
**		libpng progress meter
*/

/* --------------------------------------------------- *

  PNG のインターレス画像形式 "Adam7" の画像パラメータ

  pass width height origin area progress
    0   1/8   1/8   (0,0)  1/64  1/64
    1   1/8   1/8   (4,0)  1/64  1/32
    2   1/4   1/8   (0,4)  1/32  1/16
    3   1/4   1/4   (2,0)  1/16  1/8
    4   1/2   1/4   (0,2)  1/8   1/4
    5   1/2   1/2   (1,0)  1/4   1/2
    6    1    1/2   (0,1)  1/2    1


  Adam7 の時の進行状況算出法：

  (width/8) * 1 のピクセルグループを１ブロックと考え、
  このブロックを通算でいくつ出力したかで進行状況を
  算出する。例えば、pass0 の時は、一行出力すると
  １ブロック、pass4 の時は、一行で４ブロック、となる。
  インターレスにしなかった場合は一行=８ブロックで
  一定になるので、このブロック数を８で割ると、
  インターレスにしなかった場合に換算して何行相当を
  出力したかが判る。

 * --------------------------------------------------- */

static png_uint_32 counter;
static png_uint_32 maxcount;
static png_uint_32 step;
static int         currpass;
static int         scaledots;


static png_uint_32
 maxcount_adam7( png_uint_32 width, png_uint_32 height )
{
	png_uint_32 c = 0;

	if(    1    ) c += ((height-0+7)/8)*1;		/* Pass 0 */
	if( width>4 ) c += ((height-0+7)/8)*1;		/* Pass 1 */
	if(    1    ) c += ((height-4+7)/8)*2;		/* Pass 2 */
	if( width>2 ) c += ((height-0+3)/4)*2;		/* Pass 3 */
	if(    1    ) c += ((height-2+3)/4)*4;		/* Pass 4 */
	if( width>1 ) c += ((height-0+1)/2)*4;		/* Pass 5 */
	if(    1    ) c += ((height-1+1)/2)*8;		/* Pass 6 */

	return c;
}


/*
**		initialize the progress meter
*/
void init_progress_meter( png_structp png_ptr, png_uint_32 width,
                          png_uint_32 height )
{
	enum { W=1024,H=768 };

	if( png_set_interlace_handling(png_ptr)==7 ){
		currpass = -1;		/* interlace */
		step     = 0;
		maxcount = maxcount_adam7( width, height );
	}else{
		currpass = 0;		/* non-interlace */
		step     = 8;
		maxcount = 8*height;
	}
	if( height > ((png_uint_32)W*H)/width ){
		scaledots = DOTS_MAX;
	}else{
		scaledots = (DOTS_MAX * width * height + (W*H-1)) / (W*H);
	}
	counter = 0;
	init_progress( scaledots );
}


/*
**		row callback function for progress meter
*/
void row_callback( png_structp png_ptr, png_uint_32 row, int pass )
{
	if( row==0 ) pass--;
		/*
		**	libpng's bug ?? : In the case of interlaced image,
		**	  this function is called with 0(row) and currpass+1(pass)
		**	  when the row should equal to height and pass should equal
		**	  to currpass.
		*/

	if( currpass!=pass ){
		currpass = pass;
		step = 1<<(pass>>1);
	}
	counter += step;
	update_progress( scaledots * counter / maxcount );
}


/* -----------------------------------------------------------------------
**		libpng error handling
*/

/*
**		fatal error handling function
*/
void png_my_error( png_structp png_ptr, png_const_charp message )
{
	xprintf( "ERROR(libpng): %s - %s\n", message,
	               (char *)png_get_error_ptr(png_ptr) );
	longjmp( png_ptr->jmpbuf, 1 );
}


/*
**		non-fatal error handling function
*/
void png_my_warning( png_structp png_ptr, png_const_charp message )
{
	xprintf( "WARNING(libpng): %s - %s\n", message,
	               (char *)png_get_error_ptr(png_ptr) );
}


/* -----------------------------------------------------------------------
**		image buffer management
*/

/*
**		allocate image buffer
*/
BOOL imgalloc( IMAGE *img )
{
	BYTE *bp,**rp;
	LONG n;

	if( img->palnum>0 ){
		img->palette = malloc( (size_t)img->palnum * sizeof(PALETTE) );
		if( img->palette==NULL ) return FALSE;
	}else{
		img->palette = NULL;
	}
	img->rowbytes = ((DWORD)img->width * img->pixdepth + 31) / 32 * 4;
	img->imgbytes = img->rowbytes * img->height;
	img->rowptr   = malloc( (size_t)img->height * sizeof(BYTE *) );
	img->bmpbits  = malloc( (size_t)img->imgbytes );

	if( img->rowptr==NULL || img->bmpbits==NULL ){
		imgfree( img );
		return FALSE;
	}

	n  = img->height;
	rp = img->rowptr;
	bp = img->bmpbits;

	if( img->topdown ){
		while( --n>=0 ){
			*(rp++) = bp;
			bp += img->rowbytes;
		/*	((DWORD *)bp)[-1] = 0;	*/
		}
	}else{	/* bottom-up */
		bp += img->imgbytes;
		while( --n>=0 ){
			/* fill zeros to padding bytes (for write_bmp()) */
			((DWORD *)bp)[-1] = 0;
			bp -= img->rowbytes;
			*(rp++) = bp;
		}
	}

	return TRUE;
}


/*
**		free image buffer allocated by imgalloc()
*/
void imgfree( IMAGE *img )
{
	free( img->palette );
	free( img->rowptr  );
	free( img->bmpbits );
}


/* -----------------------------------------------------------------------
**		コマンドライン引数の処理
*/

#define isoption(p)	(IsOptChar((p)[0]) && (p)[1]!='\0')

/*
**	コマンドライン引数を読む
*/
int parsearg( int *opt, char **arg, int argc, char **argv, char *aopts )
{
	static int   agi = 1;
	static char *agp = NULL;
	char *p;
	int c,i;

	if( agp!=NULL && *agp=='\0' ){
		agp = NULL;
		agi++;
	}
	if( agi>=argc ) return 0;		/* end */

	if( p=argv[agi], agp==NULL && !isoption(p) ){
		/* non-option element */
		c = 0;
		agi++;
	}else{
		if( agp==NULL ) agp = p+1;
		if( c=(*agp & 0xff), strchr(aopts,c)!=NULL ){
			/* option with an argument */
			if( p=agp+1, *p!='\0' ) ;
			else if( i=agi+1, p=argv[i], i<argc && !isoption(p) ) agi = i;
			else p = NULL;
			agp = NULL;
			agi++;
		}else{
			/* option without an argument */
			p = NULL;
			agp++;
		}
	}
	*opt = c;
	*arg = p;

	return 1;
}


/*
**	環境変数で指定されているオプションを argc, argv に併合する
*/
char **envargv( int *argcp, char ***argvp, const char *envn )
{
	int argc,nagc,envc,i;
	char **argv,**nagv,*envs,*ep;

	ep = getenv( envn );
	if( ep==NULL || ep[0]=='\0' ) return NULL;

	envs = malloc( strlen(ep)+1 );
	if( envs==NULL ) return NULL;
	strcpy( envs, ep );

	envc = tokenize( envs, envs );
	if( envc==0 ){ free(envs); return NULL; }

	argc = *argcp;
	argv = *argvp;
	nagv = malloc ( (argc+envc+1) * sizeof(char *) );
	if( nagv==NULL ){ free(envs); return NULL; }

	nagc = 1;
	nagv[0] = argv[0];

	for( i=0 ; i<envc ; i++ ){
		nagv[nagc++] = envs;
		while( *(envs++)!='\0' );
	}
	for( i=1 ; i<argc ; i++ ){
		nagv[nagc++] = argv[i];
	}
	nagv[nagc] = NULL;

	*argcp = nagc;
	*argvp = nagv;

	return argv;
}


/*
**	文字列を空白文字(スペース/水平タブ/改行)の所で区切る(クオート処理付き)
**	区切られた部分文字列の数を返す
*/
int tokenize( char *buf, const char *str )
{
	enum { STR=0x01,QUOTE=0x02 };
	int flag = 0;
	int num = 0;
	char c;
	int i;
	
	while( (c=*str++)!='\0' ){
		if( !(flag & QUOTE) &&
		    (c==' ' || c=='\t' || c=='\n' || c=='\r') ){
			if( flag & STR ){
				flag &= ~STR;
				*buf++ = '\0';
			}
		}else{
			if( !(flag & STR) ){
				flag |= STR;
				num++;
			}
			switch(c){
			case '\\':
				/*
				**	Escaping of `"' is the same as
				**	command-line parsing of MS-VC++.
				**
				**	ex.) "     ->      quote
				**	     \"    ->      "
				**	     \\"   -> \  + quote
				**	     \\\"  -> \  + "
				**	     \\\\" -> \\ + quote
				**	     \\\\\ -> \\\\\
				*/
				for( i=1 ; (c=*str)=='\\' ; str++,i++ );
				if( c=='"' ){
					while( (i-=2)>=0 )
						*buf++ = '\\';
					if( i==-1 ){
						*buf++ = '"';
						str++;
					}
				}else{
					while( (--i)>=0 )
						*buf++ = '\\';
				}
				break;
			
			case '"':
				flag ^= QUOTE;
				break;
			
			default:
				*buf++ = c;
			}
		}
	}
	if( flag & STR ) *buf = '\0';
	
	return num;
}


/* -----------------------------------------------------------------------
**		ファイルに関する雑用処理
*/

/*
**	複数階層のディレクトリを一度に作成する
*/
int makedir( const char *path )
{
	char dir[FILENAME_MAX];
	struct stat sbuf;
	char *p,c;
	int r;
	
	delslash( strcpy(dir,path) );
	if( stat(dir,&sbuf)==0 ){
		if( (sbuf.st_mode & S_IFMT)==S_IFDIR ) return 0;
		/* errno = EEXIST; */
		return -1;
	}
	p = path_skiproot( dir );
	do{
		p = path_nextslash( p );
		c = *p;  *p = '\0';
		r = MKDIR( dir, 0777 );
		*p++ = c;
	}while( c!='\0' );
	
	return r;
}


/*
**	既存の同名ファイルをバックアップ(リネーム)する
*/
int renbak( const char *path )
{
	char bak[FILENAME_MAX];
	struct stat sbuf;
	char *sfx;
	int i;
	
	strcpy( bak, path );
	if( stat(bak,&sbuf)!=0 ) return 0;
	
#ifdef MSDOS
	sfx = suffix( bak );
#else
	sfx = bak + strlen( bak );
#endif
	strcpy( sfx, ".bak" );
	i = 0;
	while(1){
		if( stat(bak,&sbuf)!=0 && rename(path,bak)==0 ) return 0;
		if( i>=1000 ) break;
		sprintf( sfx, ".%03d", i++ );
	}
	return -1;
}


/*
**	ファイルのタイムスタンプをコピーする
*/
int cpyftime( const char *srcf, const char *dstf )
{
	struct stat    sbuf;
	struct utimbuf ubuf;

	if( stat(srcf,&sbuf)!=0 ) return -1;

	ubuf.actime  = sbuf.st_atime;
	ubuf.modtime = sbuf.st_mtime;

	return utime( dstf, &ubuf );
}


/* -----------------------------------------------------------------------
**   path functions
*/

/*
**	Return a pointer that points the suffix of the PATH
**	ex.) c:\dosuty\log\test.exe -> .exe
**	ex.) c:\dosuty\log\test.tar.gz -> .gz
*/
char *suffix( const char *path )
{
	char c,*p,*q,*r;

	for( r=q=p=basname(path) ; (c=*p)!='\0' ; p++ )
									if( c=='.' ) q=p;
	if( q==r ) q=p;		/* dotfile with no suffix */

	return q;
}


/*
**	Return a pointer that points the basename of the PATH
**	ex.) c:\dos\format.exe -> format.exe
*/
char *basname( const char *path )
{
	const char *p,*q;

	for( p=path_skiproot(path) ; *(q=path_nextslash(p))!='\0' ; p=q+1 );

	return (char *)p;
}


/*
**	Append a path-delimiter to the PATH. If the PATH is a string
**	like "c:\", "\", "c:", "", do nothing.
**	ex.) c:\dos -> c:\dos\
*/
char *addslash( char *path )
{
	char *p,*q;

	for( p=path_skiproot(path) ; *(q=path_nextslash(p))!='\0' ; p=q+1 );
	/*
	**	s = path_skiproot( path );
	**	if( q==s && q==p ) - s is a mull string.
	**	if( q!=s && q==p ) - s is followed by a path delimiter.
	**	if( q!=s && q!=p ) - s is not followed by a path delimiter.
	*/
	if( q!=p ){ *(q++)=PATHDELIM; *(q)='\0'; }

	return path;
}


/*
**	Remove a path-delimiter at the end of the PATH. If the PATH is
**	a string like "c:\", "\", "c:", "", append a dot.
**	ex.) c:\dos\ -> c:\dos
**	     c:\ -> c:\.
*/
char *delslash( char *path )
{
	char *p,*q,*s;

	for( p=s=path_skiproot(path) ; *(q=path_nextslash(p))!='\0' ; p=q+1 );
	/*
	**	if( q==s && q==p ) - s is a mull string.
	**	if( q!=s && q==p ) - s is followed by a path delimiter.
	**	if( q!=s && q!=p ) - s is not followed by a path delimiter.
	*/
	if( q==s ){ *(q++)='.'; *(q)='\0'; }
	else if( q==p ) *(--q)='\0';

	return path;
}


char *path_skiproot( const char *path )
{
#ifdef DRIVESUFFIX
	if( isalpha((unsigned char)path[0])
	    && path[1]==DRIVESUFFIX ) path += 2;
#endif
	if( IsPathDelim(path[0]) ) path++;
	return (char *)path;
}


char *path_nextslash( const char *path )
{
	char c;
	
	for( ; (c=*path)!='\0' ; path++ ){
		if( IsDBCSLead((unsigned char)c) ){
			if( *(++path)=='\0' ) break;
			continue;
		}
		if( IsPathDelim(c) ) break;
	}
	return (char *)path;
}

#ifdef WIN32_LFN

/*
**	return TRUE if the PATH is a dos-style filename.
*/
int is_dos_filename( const char *path )
{
	unsigned char c;
	char *b,*p;
	
	for( b=p=basname(path) ; (c=*p)!='\0' && c!='.' ; p++ )
		if( islower(c) ) return 0;
	if( (p-b)==0 || (p-b)>8 ) return 0;
	if( c=='.' ){
		for( b=++p ; (c=*p)!='\0' ; p++ )
			if( islower(c) || c=='.' ) return 0;
		if( (p-b)==0 || (p-b)>3 ) return 0;
	}
	return 1;
}

#endif /* WIN32_LFN */

