/* Gxsm - Gnome X Scanning Microscopy
 * universal STM/AFM/SARLS/SPALEED/... controlling and
 * data analysis software
 * 
 * Copyright (C) 1999,2000,2001 Percy Zahl
 *
 * Authors: Percy Zahl <zahl@users.sf.net>
 * WWW Home: http://gxsm.sf.net
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

/* -*- Mode: C++; indent-tabs-mode: nil; c-basic-offset: 8 c-style: "K&R" -*- */

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <cmath>
#include <string.h>
#include <zlib.h>

#include <glib.h>
#include <png.h>
#include <popt.h>
#include "thumbpal.h"
#include "writepng.h"

using namespace std;

// ==============================================================
//
// based on the nctopng code
//
// Read GME-STMAFM image/data generated by STMAFM/G.Meyer program
// and convert them to png image/thumbnail
// - read only required subgrid data (scaled down)
// - use quick, "more intelligent" autoscale
// - use palette
//
// ==============================================================

#ifndef WORDS_BIGENDIAN
# define WORDS_BIGENDIAN 0
#endif

typedef enum { GME_READ_OK, GME_MODE_16, GME_MODE_32, GME_OPEN_FAILED, GME_FILE_NOT_VALID } GME_STATUS;

#define THUMB_X 96 // Thumbnail max X size
#define THUMB_Y 91 // Thumbnail max Y size

class raw_image{
public:
	raw_image(){ rgb=NULL; };
	virtual ~raw_image(){ 
		if(rgb){
			for (int i=0; i<ny; delete[] rgb[i++]);
			delete[] rgb; 
		}
	};

	int width() { return nx; };
	int height() { return ny; };
	void calc_line_regress(int y, double &slope, double &offset){
		//
		// OutputFeld[i] = InputFeld[i] - (slope * i + offset)
		//
		double sumi, sumy, sumiy, sumyq, sumiq;
		int i, istep;
		int Xmax, Xmin;
		double a, n, imean, ymean;
			
		sumi = sumiq = sumy = sumyq = sumiy = 0.0;
		Xmax = nx-nx/10; // etwas Abstand (10%) vom Rand !
		Xmin = nx/10;
		istep = 1;
		for (i=Xmin; i < Xmax; i+=istep) { /* Rev 2 */
			sumi   += (double)i;
			sumiq  += (double)i*(double)i;
			a =  rowdata[y][i];
			sumy   += a;
			sumyq  += a * a;
			sumiy  += a * (double)i;
		}
		n = (double)((Xmax-Xmin)/istep);
		if ((Xmax-Xmin) % istep != 0) n += 1.0;
		imean = sumi / n;
		ymean = sumy / n;
		slope  = (sumiy- n * imean * ymean ) / (sumiq - n * imean * imean);
		offset = ymean - slope * imean;
	};
	double soft(int i, int j, double lim=0){
/*
		int a,b,c,d;
		a = max (0, i-1);
		b = min (i+1, nx-1);
		c = max (0, j-1);
		d = min (j+1, ny-1);
*/
		return rowdata[j][i];
	}
	void find_soft_min_max (double &min, double &max){
		min = max = soft(0,0);
		for (int i=0; i<ny; ++i)
			for (int j=0; j<nx; ++j){
				double v = soft (j,i);
				if (min > v) min = v;
				if (max < v) max = v;
			}
	};
	void quick_rgb(int linreg=TRUE) {
			if (rgb){
				for (int i=0; i<ny; delete[] rgb[i++]);
				delete[] rgb;
			}
			rgb = new unsigned char* [ny];
			for (int i=0; i<ny; rgb[i++] = new unsigned char[3*nx]);

			if (linreg){
					// Calc and Apply "Quick"
					for (int i=0; i<ny; ++i){
							double a,b;
							calc_line_regress (i, a, b);
							for (int j=0; j<nx; ++j)
									rowdata[i][j] -= j*a+b;
					}
			}

			// Find Scale
			double min, max, range;
			find_soft_min_max (min, max);
			range = max-min;
			for (int i=0; i<ny; ++i)
				for (int j=0, k=0; j<nx; ++j){
					int idx = 3*(int)((rowdata[i][j]-min)/range*PALETTE_ENTRIES+0.5);
					rgb[i][k++] = thumb_palette[idx++];
					rgb[i][k++] = thumb_palette[idx++];
					rgb[i][k++] = thumb_palette[idx];
				}
			
	};
	unsigned char **row_rgb_pointers() { return rgb; };

protected:
	int nx, ny; // image size
	int x0, y0, w0; // offset, data width
	int onx, ony; // original data size of NC-file
	double **rowdata;
	unsigned char **rgb;
};

void swap_ushort (unsigned short *addr){
	unsigned short temp1, temp2;
	temp1 = temp2 = *addr;
	*addr = ((temp2 & 0xFF) << 8) | ((temp1 >> 8) & 0xFF);
}

void swap_short (short *addr){
	unsigned short temp1, temp2;
	temp1 = temp2 = *addr;
	*addr = ((temp2 & 0xFF) << 8) | ((temp1 >> 8) & 0xFF);
}


class gme_image : public raw_image {
public:
	gme_image (const gchar *file_name, int thumb, int new_x, int x_off, int y_off, int width) {
		ifstream f;
		gchar line[0x4000];
		// int chan=1;
		int channels=1;

		x0 = x_off; y0 = y_off;

		// Am I resposible for that file, is it a "Gme-Dat" format ?

		f.open(file_name, ios::in);
		if (!f.good()){
			state = GME_OPEN_FAILED;
			return;
		}

		f.getline(line, 0x4000);

		if (strncmp (line, "[Parameter]", 11) != 0){
			// check for 32 bit data version
			if (strncmp (line, "[Paramco32]", 11) != 0){
				f.close ();
				state = GME_FILE_NOT_VALID;
				return;
			}
			state = GME_MODE_32; // 32bit data mode, zip compressed
		} else
			state = GME_MODE_16; // default 16bit data mode, uncompressed

		for (; f.good ();){
			f.getline (line, 0x4000);
			//                  0        1         2         3         4
			//                  1234567890123456789012345678901234567890
			if (strncmp (line, "DATA", 4) == 0) // start of data
				break;
			if (strncmp (line, "Num.X / Num.X=", 14) == 0)
				onx = atoi (&line[14]);
			if (strncmp (line, "Num.Y / Num.Y=", 14) == 0)
				ony = atoi (&line[14]);
#if 0 // do not need
			if (strncmp (line, "Chan(1,2,4) / Chan(1,2,4)=", 26) == 0)
				chan = atoi (&line[26]);
#endif
			if (strncmp (line, "CHMode / CHMode=", 16) == 0)
				; // = atof (&line[0]);
			if (strncmp (line, "Channels / Channels=", 20) == 0)
				channels = atoi (&line[20]);
			if (strncmp (line, "Channels=", 9) == 0)
				channels = atoi (&line[9]);
		}

		//
		// compute scale and offsets
		//
		if (width>0){
			w0=width;
			nx=new_x;
			ny=new_x;
		}else{
			w0=onx;
			if (thumb){
				if(onx < ony*THUMB_X/THUMB_Y){
					ny=THUMB_Y;
					nx=onx*ny/ony;
				}else{
					nx=THUMB_X;
					ny=ony*nx/onx;
				}
			}else{
				if (new_x){ // scale to new X w aspect
					nx=new_x;
					ny=ony*nx/onx;
				}else{
					nx=onx; 
					ny=ony;
				}
			}
		}
		if (x0+nx >= onx) x0=0; // fallback
		if (y0+ny >= ony) y0=0; // fallback


		//prepare memory
		rowdata = new double* [ny];
		for (int i=0; i<ny; rowdata[i++] = new double [nx]);

		//
		// read data
		//

		switch (state){
			
		case GME_MODE_32: { // 32bit data mode, zip compressed
			double scale = (double)nx/(double)w0;
			gint vals = channels > 1? channels:1;
			gulong page = onx * ony;
			gulong sz = page * vals;

			gfloat *zbuf = g_new (gfloat, sz);
			gfloat *buf  = g_new (gfloat, sz);
			gulong nb = sz * sizeof (gfloat);
			memset ((void*) zbuf, 0, nb);
			memset ((void*) buf, 0, nb);

			// compute gzdata length from file length - headersize
			f.seekg (0, ios::end);
			gulong znb = (gulong) ((long)(f.tellg ()) - 0x4000);

			// start of gzdata
			f.seekg (0x4000, ios::beg);
			f.read ((char*) zbuf, nb);
			g_print ("GME32Z-Import: deflating Z data [%d] : zuffer-size: %g data-size: %g ratio: %g ", 
				 uncompress ((Bytef *) buf, &nb, (Bytef *) zbuf, znb),
				 (double)znb, (double)nb, (double)znb/(double)nb*100.);
			g_free (zbuf);

			// convert to ZD_LONG
//			vals = 1; // only 1st for preview!
			for (gint vi = 0; vi < vals; ++vi){
//				for (gint line = 0; line < ony; ++line)
//					for (gint col = 0; col < onx; ++col){
				for (int y=0; y<ny; y++){
					gint line = y0+(int)((double)y/scale+.5);
					for (int i=0; i<nx; ++i){
						gint col = x0+(int)((double)i/scale+.5);
#ifdef WORDS_BIGENDIAN
						guint32 *i32 = (guint32*) &buf[col + line*onx + vi*page];
						guint32 tmp = *i32;
						*i32 = (tmp>>24) | (((tmp>>16)&0xff) << 8) | (((tmp>>8)&0xff) << 16) | ((tmp&0xff) << 24);
#endif
						rowdata[y][i] = (double) buf[col + line*onx + vi*page];
					}
				}
			}
			g_free (buf);
			// funny fix if 0 value:
			if (rowdata[0][0] == 0)
				rowdata[0][0] = rowdata[1][1];

		} break;
			
		case GME_MODE_16: { // default 16bit data mode
			const unsigned long gme_data_offset = 0x4004;
			double scale = (double)nx/(double)w0;
			short *row = new short[onx];
			short data;
			
			for (int y=0; y<ny; y++){
				f.seekg((y0+(int)((double)y/scale+.5))*onx*sizeof(short) + gme_data_offset);
				f.read((char*)row, onx*sizeof(short));
				for (int i=0; i<nx; ++i){
					data = row[x0+(int)((double)i/scale+.5)];
					if (WORDS_BIGENDIAN)
						swap_short (&data);
					rowdata[y][i] = (double)data;
				}
			}
		} break;
		default: break;
		}
			
		f.close ();

		state = GME_READ_OK;
	};

	const GME_STATUS status(){
		return state;
	};
	

	virtual ~gme_image() {
		for (int i=0; i<ny; delete [] rowdata[i++]);
		delete [] rowdata;
	};

private:
	GME_STATUS state;
};


int write_png(gchar *file_name, raw_image *img){
// see here for pnglib docu:
// http://www.libpng.org/pub/png/libpng-manual.html

	mainprog_info m;

	m.gamma   = 1.;
	m.width   = img->width();
	m.height  = img->height();
	m.modtime = time(NULL);
	m.infile  = NULL;
	if (! (m.outfile = fopen(file_name, "wb")))
		return -1;
	m.row_pointers = img->row_rgb_pointers();
	m.title  = g_strdup ("gmetopng");
	m.author = g_strdup ("P. Zahl");
	m.desc   = g_strdup ("Gme data to png");
	m.copyright = g_strdup ("GPL");
	m.email   = g_strdup ("zahl@users.sourceforge.net");
	m.url     = g_strdup ("http://gxsm.sf.net");
	m.have_bg   = FALSE;
	m.have_time = FALSE;
	m.have_text = FALSE;
	m.pnmtype   = 6; // TYPE_RGB
	m.sample_depth = 8;
	m.interlaced= FALSE;

	writepng_init (&m);
	writepng_encode_image (&m);
	writepng_encode_finish (&m);
	writepng_cleanup (&m);

	g_free (m.title);
	g_free (m.author);
	g_free (m.desc);
	g_free (m.copyright);
	g_free (m.email);
	g_free (m.url);

	return 0;
}


/* ****************************
              main
   ***************************/
int main(int argc, const char *argv[]) {
	int thumb = 1;
	int new_x = 0;
	int x_off = 0, y_off = 0, width = 0;	
	int verbose = 0;
	int noquick = 0;
	int help = 0;
	gchar *filename;
	gchar *destinationfilename;
	gme_image *img = NULL;
	poptContext optCon; 

	struct poptOption optionsTable[] = {
		{"x-offset",'x',POPT_ARG_INT,&x_off,0,"x-offset",NULL},
		{"y-offset",'y',POPT_ARG_INT,&y_off,0,"y-offset",NULL},
		{"width",'w',POPT_ARG_INT,&width,0,"width",NULL},
		{"size",'s',POPT_ARG_INT,&new_x,0,"Size in x-direction.",NULL},
		{"verbose",'v',POPT_ARG_NONE,&verbose,1,"Display information.",NULL},
		{"noquick",'n',POPT_ARG_NONE,&noquick,1,"No-quick",NULL},
		{"help",'h',POPT_ARG_NONE,&help,1,"Print help.",NULL},
		{ NULL, 0, 0, NULL, 0 }
	};

	optCon = poptGetContext(NULL, argc, argv, optionsTable, 0);
	poptSetOtherOptionHelp(optCon, "gmefile.t[fb]? [outfile.png]");
	while (poptGetNextOpt(optCon) >= 0) {;}	//Now parse until no more options.

	if ((argc < 2 )||(help)) { 
		poptPrintHelp(optCon, stderr, 0);
		exit(1);
	}
		
	filename = g_strdup(poptGetArg(optCon));
	destinationfilename = g_strdup(poptGetArg(optCon));

	if (destinationfilename == NULL){
		destinationfilename = g_strjoin(NULL, filename, ".png", NULL);
		// using simple join. if you need more sophisticated
		// have a look at 'mmv' for suffix handling.
	}

	if(verbose){
		if (new_x == 0)	
			cout << "Thumbnail-size" << endl;
		else
			cout << "Rescaling to new x-size = " << new_x << endl;
		cout << "Sourcefile: " << filename << endl;
		cout << "Destinationfile: " << destinationfilename << endl;
	}

	if (new_x > 0)
		thumb = 0;
	
	if ( x_off + y_off + width > 0){
		if ( (x_off==0) || (y_off==0) || (width==0) ) {
			cout << "Please use -x and -y and -w together."<< endl;
			exit(-1);
		}
		if(verbose){
			cout << "Offset: " << x_off << " x " << y_off << endl;
			cout << "Width set to: " << width << endl;
		}
	}
		
	// load & rescale image
	img = new gme_image(filename, thumb, new_x, x_off, y_off, width);

	switch (img->status()){
		case GME_READ_OK: break;
		case GME_OPEN_FAILED: 
		        cerr << "Error opening Gme file >" << filename << "<" << endl; 
			exit(-1);
			break;
		case GME_FILE_NOT_VALID:
		        cerr << "invalid Gme file >" << filename << "<" << endl; 
		        exit(-1);
			break;
		case GME_MODE_16:
		        cerr << "mode 16 img read in progress >" << filename << "<" << endl; 
			break;
		case GME_MODE_32:
		        cerr << "mode 32 img read in progress >" << filename << "<" << endl; 
			break;
	}
		
	if  (!img){
		cerr << "Error while creating image from Gme file." << endl;
		exit(-1);
	}
		
	if (verbose)
		cout << "Converting ..." << endl; 
		
	if (noquick)
		img->quick_rgb(FALSE);
	else
		img->quick_rgb();

	if (verbose)
		cout << "Writing >" << destinationfilename << "<" << endl; 
		
	write_png(destinationfilename, img);

	if(verbose)
		cout << "Writing complete." << endl;
		
	g_free(filename);
	g_free(destinationfilename);
	poptFreeContext(optCon);
	exit(0);
}
