/*
 * topocv64 -- display 1/64 degree mars topography data and convert 
 *             to 8 or 16 bit PNG file(s). 
 * 
 * Version 1.2 (2004-02-13)
 * 
 * Copyright (c) 2003--2004 by Wolfgang Wieser
 *
 */

// REQUIRES:
//   QTXlib, Hlib, libbz2, libpng, Qt
 
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <fcntl.h>
#include <assert.h>
#include <netinet/in.h>
#include <math.h>
#include <stdlib.h>

#include <Qt/qapplication.h>
#include <Qt/qwidget.h>
#include <Qt/qimage.h>
#include <Qt/qpainter.h>

#include <QTX/ximagewidget.h>

#include <hlib/pngwrite.h>
#include <hlib/bzip2_cinfile.h>
 
static const int srcgrid_w=4;
static const int srcgrid_h=4;
const char *_srcgridfile[srcgrid_h][srcgrid_w]=
{
	{ "megt90n000g.img.bz2","megt90n090g.img.bz2","megt90n180g.img.bz2","megt90n270g.img.bz2" },
	{ "megt45n000g.img.bz2","megt45n090g.img.bz2","megt45n180g.img.bz2","megt45n270g.img.bz2" },
	{ "megt00n000g.img.bz2","megt00n090g.img.bz2","megt00n180g.img.bz2","megt00n270g.img.bz2" },
	{ "megt45s000g.img.bz2","megt45s090g.img.bz2","megt45s180g.img.bz2","megt45s270g.img.bz2" }
	//{ "megr90n000g.img.bz2","megr90n090g.img.bz2","megr90n180g.img.bz2","megr90n270g.img.bz2" },
	//{ "megr45n000g.img.bz2","megr45n090g.img.bz2","megr45n180g.img.bz2","megr45n270g.img.bz2" },
	//{ "megr00n000g.img.bz2","megr00n090g.img.bz2","megr00n180g.img.bz2","megr00n270g.img.bz2" },
	//{ "megr45s000g.img.bz2","megr45s090g.img.bz2","megr45s180g.img.bz2","megr45s270g.img.bz2" }
};

static inline const char *GetSrcGridFile(int x,int y)
{  return(_srcgridfile[y][x]);  }

// Gray8/16 only. 
class MyPNGWriter : public PNGWriter
{
	private:
		int width,height;
		int depth;  // 8 or 16
		int written_lines;
	public:
		MyPNGWriter();
		~MyPNGWriter();
		
		// Open file to write image data: 
		int open(int width,int height,int depth,const char *file);
		// Call this when done. 
		int close();
		
		// Store data (one complete scan line). 
		// (Make sure len=width*depth)
		int write(const char *data,size_t len);
};


int MyPNGWriter::write(const char *data,size_t len)
{
	assert(len==width*depth/8 && written_lines<height);
	int rv=WriteRow(data);
	++written_lines;
	return(rv);
}


int MyPNGWriter::open(int _width,int _height,int _depth,const char *file)
{
	if(_width<1 || _height<1 || (_depth!=8 && _depth!=16))
	{  return(-2);  }
	if(!file)
	{  return(-3);  }
	
	int rv=Open(file,/*compression_level=*/9);
	if(rv)
	{  return(rv);  }
	
	width=_width;
	height=_height;
	depth=_depth;
	written_lines=0;
	
	rv=SetSize(width,height,depth,PNG_COLOR_TYPE_GRAY);
	if(rv)
	{  return(rv);  }
	rv=WriteHeader();
	if(rv)
	{  return(rv);  }
	
	rv=SetBasicBitOps(PNGW_Swap16);
	return(rv);
}


int MyPNGWriter::close()
{
	int rv=WriteTail();
	if(rv)
	{  return(rv);  }
	rv=Close();
	width=height=0;
	depth=0;
	written_lines=0;
	return(rv);
}


MyPNGWriter::MyPNGWriter()
{
	width=height=0;
	depth=0;
	written_lines=0;
}

MyPNGWriter::~MyPNGWriter()
{
}


struct ShowWin : public QTX::XImageWidget
{
	private:
		void keyPressEvent(QKeyEvent *ev);
		void mousePressEvent(QMouseEvent *ev);
	public:
		ShowWin();
		~ShowWin();
		
		int load(int tile_width,int tile_height,const char *path_to_tiles,
			int png_mode_switch,int scalefact);
};

ShowWin::ShowWin() : QTX::XImageWidget(0,0)
{
	
}


ShowWin::~ShowWin()
{
	
}


void ShowWin::keyPressEvent(QKeyEvent *)
{
	close();
}

void ShowWin::mousePressEvent(QMouseEvent *)
{
	close();
}


int ShowWin::load(int tile_width,int tile_height,const char *path_to_tiles,
	int png_mode_switch,int scalefact)
{
	int tot_src_width=tile_width*4;
	int tot_src_height=tile_height*4;
	int win_width=tot_src_width;
	int win_height=tot_src_height;
	int win_scale=1;
	while(win_width>1440)
	{  win_width/=2;  win_height/=2;  win_scale*=2;  }
	int dest_width=tot_src_width/scalefact;
	int dest_height=tot_src_height/scalefact;
	if(tot_src_width%scalefact || tot_src_height%scalefact)
	{  fprintf(stderr,"Invalid scale factor %d\n",scalefact);  return(1);  }
	
	BZip2CompressedFileReader src_read[srcgrid_w][srcgrid_h];
	
	fprintf(stderr,"Opening.");
	for(int gy=0; gy<srcgrid_h; gy++)
	{
		for(int gx=0; gx<srcgrid_w; gx++)
		{
			char tmp[strlen(path_to_tiles)+strlen(GetSrcGridFile(gx,gy))+2];
			strcpy(tmp,path_to_tiles);
			strcat(tmp,"/");
			strcat(tmp,GetSrcGridFile(gx,gy));
			if(src_read[gx][gy].open(tmp))
			{
				fprintf(stderr,"%s: failed to open %s\n",prg_name,tmp);
				return(1);
			}
			fprintf(stderr,".");
		}
	}
	fprintf(stderr,"OK\n");
	
	CreateImage(win_width,win_height);
	setCaption("Topography data");
	show();
	
	long long tot_in_bytes=0;
	long long tot_out_bytes=0;
	MyPNGWriter *pngout_lo=NULL,*pngout_hi=NULL;
	
	int retval=0;
	{
		if(png_mode_switch==1 || png_mode_switch==2)
		{
			pngout_lo=new MyPNGWriter();
			assert(pngout_lo);
		}
		if(png_mode_switch==2)
		{
			pngout_hi=new MyPNGWriter();
			assert(pngout_hi);
			fprintf(stderr,"Writing two 8 bit data PNGs. (%dx%d, scale=%d)\n",
				dest_width,dest_height,scalefact);
			if(pngout_lo->open(dest_width,dest_height,8,"output_lo.png"))
			{  fprintf(stderr,"PNG output open failure.\n");  goto retclose;  }
			if(pngout_hi->open(dest_width,dest_height,8,"output_hi.png"))
			{  fprintf(stderr,"PNG output open failure.\n");  goto retclose;  }
		}
		else if(png_mode_switch==1)
		{
			fprintf(stderr,"Writing 16 bit data PNG. (%dx%d, scale=%d)\n",
				dest_width,dest_height,scalefact);
			if(pngout_lo->open(dest_width,dest_height,16,"output.png"))
			{  fprintf(stderr,"PNG output open failure.\n");  goto retclose;  }
		}
		else
		{  fprintf(stderr,"Not writing PNG file.\n");  }
		
		fprintf(stderr,"Processing... [%3d%%]",0);
		
		// One scan line, all tiles. 
		int16_t src_line[tot_src_width];
		
		for(int src_yy=0; src_yy<tot_src_height; src_yy++)
		{
			int gy=src_yy/tile_height;
			assert(gy<srcgrid_h);
			
			if(gy-1==((src_yy-1)/tile_height))
			{
				// Close the no longer needed readers: 
				for(int gx=0; gx<srcgrid_w; gx++)
				{  src_read[gx][gy-1].close();  }
			}
			
			// Read one scan line. 
			for(int gx=0; gx<srcgrid_w; gx++)
			{
				size_t sll=tile_width*sizeof(int16_t);
				ssize_t rv=src_read[gx][gy].read(
					(char*)(src_line+gx*tile_width),sll);
				if(rv<0)
				{  fprintf(stderr,"Tile read error: %s (%d,%s)\n",
					GetSrcGridFile(gx,gy),rv,strerror(errno));  goto retclose;  }
				if(size_t(rv)<sll)
				{  fprintf(stderr,"early EOF: %s\n",
					GetSrcGridFile(gx,gy));  goto retclose;  }
				tot_in_bytes+=(long long)rv;
			}
			
			// Display: 
			if(!(src_yy%win_scale))
			{
				for(int xx=0; xx<win_width; xx++)
				{
					int16_t val=ntohs(src_line[xx*win_scale]);
					
					int red=0;
					int green=0;
					int blue=0;
					
					#if 1
					//red=green=blue=(255*(int(val)-min)/(max-min)/10)%2 * 0xff;
					//red=green=blue=255*(int(val)-min)/(max-min);
					red=green=blue=int(val+32768)/256;
					#else
					if(val<0)
					{  blue=255-255*(int(val)-min)/(0-min);  }
					else if(val==0)
					{  green=0xff;  }
					else if(val>0)
					{  red=255*(int(val)-0)/(max-0);  }
					#endif
					
					_PutPixel(xx,src_yy/win_scale,qRgb(red,green,blue));
					//if(_GetPixel(xx,yy)!=color)
					//{  fprintf(stderr,"0x%x != 0x%x (%d,%d,%d)\n",
					//	_GetPixel(xx,yy),color,red,green,blue);  }
				}
				
				PutImageRow(src_yy/win_scale);
			}
			
			if(!(src_yy%scalefact))
			{
				if(png_mode_switch==2)
				{
					uchar pngdata_lo[dest_width],pngdata_hi[dest_width];
					for(int xx=0; xx<dest_width; xx++)
					{
						int16_t val=ntohs(src_line[xx*scalefact]);
						// NO offset subtraction here. 
						u_int16_t uval=u_int16_t(int(val)+32768);
						pngdata_lo[xx]=(uchar)(uval & 0x00ffU);
						pngdata_hi[xx]=(uchar)(uval>>8);
					}
					
					if(pngout_lo->write((char*)&pngdata_lo,dest_width))
					{  fprintf(stderr,"PNG output write failure.\n");
						goto retclose;  }
					if(pngout_hi->write((char*)&pngdata_hi,dest_width))
					{  fprintf(stderr,"PNG output write failure.\n");
						goto retclose;  }
					tot_out_bytes+=(long long)(dest_width*2);
				}
				else if(png_mode_switch==1)
				{
					u_int16_t pngdata[dest_width];
					for(int xx=0; xx<dest_width; xx++)
					{
						int16_t val=ntohs(src_line[xx*scalefact]);
						// NO offset subtraction here. 
						u_int16_t data=u_int16_t(int(val)+32768);
						pngdata[xx]=data;
					}
					
					if(pngout_lo->write((char*)&pngdata,dest_width*2))
					{  fprintf(stderr,"PNG output write failure.\n");
						goto retclose;  }
					tot_out_bytes+=(long long)(dest_width*2);
				}
			}
			
			if(100*(src_yy)/tot_src_height!=100*(src_yy+1)/tot_src_height)
			{  fprintf(stderr,"\b\b\b\b\b\b[%3d%%]",100*(src_yy+1)/tot_src_height);  }
			
			qApp->processEvents();
		}
		
		if(pngout_lo && pngout_lo->close())
		{  fprintf(stderr,"PNG output close failure.\n");  goto retclose;  }
		if(pngout_hi && pngout_hi->close())
		{  fprintf(stderr,"PNG output close failure.\n");  goto retclose;  }
		
		fprintf(stderr,"\b\b\b\b\b\bOK (raw: %ld Mb -> %ld Mb)\n",
			long(tot_in_bytes>>20),long(tot_out_bytes>>20));
	}
	
	if(0)
	{  retclose:;  retval=1;  }
	if(pngout_lo)  delete pngout_lo;
	if(pngout_hi)  delete pngout_hi;
	return(retval);
}


char *prg_name="topocv64";

static void PrintHelp()
{
	printf(
		"USAGE: %s [QTopts] [-h12] [-s64] [--help] dir_to_files\n"
		"   -h --help  print this\n"
		"   -sXX  only process every XXth pixel (scale) (default: 64)\n"
		"   -1    output one 16 bit PNG from data\n"
		"             value range: symmetric around 0x7fff\n"
		"   -2    output two 8 bit PNGs (low and high) from data\n"
		"             value range: symmetric around 0x7fff\n"
		"   QTopts usual QT options (-display, ...)\n"
		"Show and convert topography data.\n",prg_name);
}


int main(int argc,char **arg)
{
	prg_name=GetPrgName(arg[0]);
	
	QApplication qapp(argc,arg);
	ShowWin swin;
	
	char *dir_to_files=NULL;
	int png_mode_switch=0;
	int scalefact=64;
	for(int i=1; i<argc; i++)
	{
		if(*arg[i]=='-')
		{
			if(!strcmp(arg[i],"--help") || !strcmp(arg[i],"-help"))
			{  PrintHelp();  return(0);  }
			else if(arg[i][1]=='s')
			{  scalefact=strtol(&arg[i][2],NULL,0);  }
			else for(const char *c=arg[i]+1; *c; c++)
			{
				switch(*c)
				{
					case '-':  break;
					case 'h':  PrintHelp();  return(0);
					case '1':  png_mode_switch=1;  break;
					case '2':  png_mode_switch=2;  break;
					default:
						fprintf(stderr,"Illegal option '%c' in arg \"%s\". "
							"Try --help\n",*c,arg[i]);
						return(1);
				}
			}
		}
		else
		{
			if(dir_to_files)
			{  fprintf(stderr,"More than one dir to files.\n");
				return(1);  }
			dir_to_files=arg[i];
		}
	}
	
	if(!dir_to_files)
	{
		dir_to_files="1_64_degree";
		fprintf(stderr,"No dir-to-files specified. Falling back to %s\n",
			dir_to_files);
	}
	
	if(swin.load(5760,2880,dir_to_files,png_mode_switch,scalefact))
	{  return(1);  }
	
	qapp.setMainWidget(&swin);
	qapp.exec();
	
	return(0);
}

