#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>                  
#define GLOBAL_DECLARATION

#include "../inc/config.h"
#define DEBUG
#include "genUtil.h"
#include "File.h"
#include "String.h"
#include "Memory.h"
#include "mrcImage.h"
#include "tiff.h"
#include "tiffio.h"

typedef struct ltiff2mrcInfo {
	long    flagSigned;
	long    flagOffset;
	double  Offset;
	long 	flagMax;
	double  Max;
	long 	flagMin;
	double  Min;
	long 	flagrealMaxBit;
	double  realMaxBit;
	long 	flagrealMax;
	double  realMax;
	long 	flagrealMin;
	double  realMin;
	long 	Inverse;
	long    flagTime;
	long    Time;
	long    flagfinalmrcMode;
	long    finalmrcMode;

	long    flagShrink;
	long    Shrink;
} ltiff2mrcInfo;

extern void ltiff2mrc(mrcImage* mrc, TIFF* tiff, ltiff2mrcInfo linfo, long mode);
extern void lsin2ltiff2mrc(ltiff2mrcInfo* linfo, char* filename, long mode);

int
main(int argc, char* argv[]) 
{
    mrcImage mrc;
	TIFF *tiff;
    tiff2mrcInfo info;
	ltiff2mrcInfo linfo;

	init0(&info);
    argCheck(&info, argc, argv);
    init1(&info);

	memoryClear(&(mrc), sizeof(mrc), 0);		
	memoryClear(&(linfo), sizeof(linfo), 0);		

	linfo.flagSigned = info.Signed;
	linfo.flagOffset = info.flagOffset;
	linfo.Offset     = info.Offset;

	linfo.flagShrink = info.flagShrink;
	linfo.Shrink     = info.Shrink;
	DEBUGPRINT2("Offset: %f flag %d\n", linfo.Offset, linfo.flagOffset);

	if(info.flagsinFile) {
		lsin2ltiff2mrc(&linfo, info.sinFile, 0);
	}
	if(info.flagMode) {
		linfo.flagfinalmrcMode = info.flagMode;	
		linfo.finalmrcMode     = info.Mode;	
	}

	tiff = TIFFOpen(info.inFile, "r");
	if(tiff != NULL) {
		if(info.dirnum != -1 && !TIFFSetDirectory(tiff, info.dirnum))	 {
			fprintf(stderr, "Cannot read the directory (%ld) in tiff file(%s).\n", info.dirnum, info.inFile);
			exit(EXIT_FAILURE);
		}
		mrc.HeaderLength.x = mrc.HeaderLength.y = mrc.HeaderLength.z = info.Resolution;
		linfo.Inverse = info.Inverse;
		if(info.flagtime) {
			linfo.Time    	= info.time;
			linfo.flagTime  = info.flagtime;
		}
		if(info.flagMax) {
			linfo.Max     = pow(2, info.Max) - 1;
			linfo.flagMax = info.flagMax;
		}
		if(info.flagMin) {
			linfo.Min     = pow(2, info.Min);
			linfo.flagMin = info.flagMin;
		}
		if(info.flagrealMaxBit) {
			linfo.realMaxBit     = pow(2, info.realMaxBit) - 1;
			linfo.flagrealMaxBit = info.flagrealMaxBit;
		}
		if(info.flagrealMax) {
			linfo.realMax     = info.realMax;
			linfo.flagrealMax = info.flagrealMax;
		}
		if(info.flagrealMin) {
			linfo.realMin     = info.realMin;
			linfo.flagrealMin = info.flagrealMin;
		}
		ltiff2mrc(&mrc, tiff, linfo, info.mode);
	} else {
		fprintf(stderr, "Cannot open tiff file (%s).\n", info.inFile);
		exit(EXIT_FAILURE);
	}
	mrcFileWrite(&mrc, info.outFile, "in main routine", 0);
	return 0;
}

void
additionalUsage(void)
{
	fprintf(stderr, "---- mode ----\n");
	fprintf(stderr, "0x0000000f bits: Direct or Log\n");
	fprintf(stderr, "0x000000f0 bits: Which Color\n");
	fprintf(stderr, "                 R: 00\n");
	fprintf(stderr, "                 G: 10\n");
	fprintf(stderr, "                 B: 20\n");
}

void
lsin2ltiff2mrc(ltiff2mrcInfo* linfo, char* filename, long mode)
{
	FILE* fpt;	
	char s[1024];
	char* keyword;

	fpt = fileOpen(filename, "r");
	while(NULL==stringGetFromFile(s, "Information", fpt, stdout, 3)) {
		keyword = stringGetNthWord(s, 1, " \t,.");
		SSWITCH(keyword) 
			SCASE("SDMI") {
				linfo->flagrealMin = 1;
				linfo->realMin = stringGetNthRealData(s, 2, " \t,."); 
				SBREAK;
			}
			SCASE("SDMA") {
				linfo->flagrealMax = 1;
				linfo->realMax = stringGetNthRealData(s, 2, " \t,."); 
				SBREAK;
			}
		}
	}
}

void
ltiff2mrc(mrcImage* mrc, TIFF* tiff, ltiff2mrcInfo linfo, long mode)
{
	unsigned short samplesPerPixel;
	unsigned short bitsPerSample;
	unsigned short bytesPerSample;
	unsigned short planarConfiguration;
	unsigned short photometric;
	unsigned short matte;
	unsigned short bytesPerRow;
	unsigned short resUnit, resTag;
	unsigned int w, h, row, col;
	float ox, oy, xres, yres;
	unsigned char*  tiffBuffer;
	uint32*  tiffBufferLong;
	double data;

	matte=0;

	bytesPerRow = TIFFScanlineSize(tiff);
	TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric);
	switch(photometric) {
		case PHOTOMETRIC_MINISBLACK:
		case PHOTOMETRIC_MINISWHITE:
		case PHOTOMETRIC_RGB:
			break;
		default: {
			TIFFError("ltiff2mrc", "Cannot handle %d photometric", photometric);
			exit(EXIT_FAILURE);
			break;
		}
	}
	TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bitsPerSample);
	switch(bitsPerSample) {
		case  8:
		case 16:
			bytesPerSample = bitsPerSample/8;
			break;
		default: {
			TIFFError("ltiff2mrc", "Can not handle %d-bit/sample image", bitsPerSample);
			exit(EXIT_FAILURE);
			break;
		}
	}
	TIFFGetFieldDefaulted(tiff, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel);
	switch(samplesPerPixel) {
		case 1:
		case 3:
			break;
		default:
			TIFFError("ltiff2mrc", "Can not handle %d-sample/pixel image", samplesPerPixel);
			exit(EXIT_FAILURE);
			break;
	}
	TIFFGetField(tiff, TIFFTAG_PLANARCONFIG, &planarConfiguration);

	TIFFGetField(tiff, TIFFTAG_MATTEING, &matte);

	TIFFGetField(tiff, TIFFTAG_IMAGEWIDTH, &w);
	TIFFGetField(tiff, TIFFTAG_IMAGELENGTH, &h);
	if(!TIFFGetField(tiff, TIFFTAG_XPOSITION, &ox)) {
		ox = 0;
	}
	if(!TIFFGetField(tiff, TIFFTAG_YPOSITION, &oy)) {
		oy = 0;
	}
	if(!TIFFGetField(tiff, TIFFTAG_RESOLUTIONUNIT, &resUnit)) {
		resTag = RESUNIT_NONE;
	}


	mrc->HeaderN.x = w;
	mrc->HeaderN.y = h;
	mrc->HeaderN.z = 1;

    switch(mode&0xf) {
        case 0: {
			switch(bitsPerSample) {
				case  8: {
					mrc->HeaderMode = mrcCharImage;
					break;
				}
				case 16: {
					mrc->HeaderMode = mrcShortImage;
					break;
				}
				default: {
					TIFFError("ltiff2mrc", "Can not handle %d-bit/sample image", bitsPerSample);
					exit(EXIT_FAILURE);
				}
			}
            break;
        }
        case 1: {
			mrc->HeaderMode = mrcFloatImage;
            break;
        }
        default: {
            fprintf(stderr, "Not supported mode : %ld\n", mode);
            exit(EXIT_FAILURE);
            break;
        }
    }
	if(linfo.flagfinalmrcMode) {
		mrc->HeaderMode = linfo.finalmrcMode;	
	}

	mrcInit(mrc, NULL);
	DEBUGPRINT2("(%ld,%ld)\n", w, h);

	switch(samplesPerPixel) {
		case 1: {
			tiffBuffer = (unsigned char*)memoryAllocate(sizeof(unsigned char)*bytesPerRow, "in ltiff2mrc");
			switch(bitsPerSample) {
				case 8: {
					for(row=0; row < h; row++) {
						if(TIFFReadScanline(tiff, tiffBuffer, row, 0)<0) {
							break;
						}
						for(col=0; col < w; col++) {
							if(!linfo.flagSigned) {
								data = (double)*((unsigned char*)(tiffBuffer+col));
							} else {
								data = (double)*((char*)(tiffBuffer+col));
							}
							if(linfo.flagOffset) {
								data += linfo.Offset;
							}
							mrcPixelDataSet(mrc, col, (float)(h - 1 - row), 0, data, mrcPixelMag);
						}
					}
					break;
				}
				case 16: {
					DEBUGPRINT3("16bits image: gray: signed %d: offset %f flag %d\n", linfo.flagSigned, linfo.Offset, linfo.flagOffset);
					for(row=0; row < h; row++) {
						if(TIFFReadScanline(tiff, tiffBuffer, row, 0)<0) {
							break;
						}
						for(col=0; col < w; col++) {
							if(!linfo.flagSigned) {
								data = (double)*((unsigned short*)(tiffBuffer+col*bytesPerSample));
							} else {
								data = (double)*((short*)(tiffBuffer+col*bytesPerSample));
							}
							if(linfo.flagOffset) {
								data += linfo.Offset;
							}
							mrcPixelDataSet(mrc, (float)col, (float)(h - 1 - row), 0, data, mrcPixelRePart);
						}
					}
					break;
				}
			}
			break;
		}
		case 3: {
			DEBUGPRINT("in Read");
			switch(bitsPerSample) {
				case 8: {
					tiffBufferLong = (uint32*)memoryAllocate(sizeof(uint32)*w*h, "in ltiff2mrc");
					if(TIFFReadRGBAImage(tiff, w, h, tiffBufferLong, 0)<0) {	
						fprintf(stderr, "TIFFReadRGBAImage: Error\n");
						exit(EXIT_FAILURE);
					}
					for(row=0; row < h; row++) {
						for(col=0; col < w; col++) {
							switch(mode&0xf0) {
								case 0x00: {
#ifdef M68
									data = (double)*((unsigned char*)(tiffBufferLong+col+w*row) + 3);
#else
									data = (double)*((unsigned char*)(tiffBufferLong+col+w*row) + 0);
#endif
									mrcPixelDataSet(mrc, col, row, 0, data, mrcPixelMag);
									break;
								}
								case 0x10: {
#ifdef M68
									data = (double)*((unsigned char*)(tiffBufferLong+col+w*row) + 2);
#else
									data = (double)*((unsigned char*)(tiffBufferLong+col+w*row) + 1);
#endif
									mrcPixelDataSet(mrc, col, row, 0, data, mrcPixelMag);
									break;
								}
								case 0x20: {
#ifdef M68
									data = (double)*((unsigned char*)(tiffBufferLong+col+w*row) + 1);
#else
									data = (double)*((unsigned char*)(tiffBufferLong+col+w*row) + 2);
#endif
									mrcPixelDataSet(mrc, col, row, 0, data, mrcPixelMag);
									break;
								}
								case 0x40: {
#ifdef M68
									data = (double)*((unsigned char*)(tiffBufferLong+col+w*row) + 0);
#else
									data = (double)*((unsigned char*)(tiffBufferLong+col+w*row) + 3);
#endif
									mrcPixelDataSet(mrc, col, row, 0, data, mrcPixelMag);
									break;
								}
								default: {
									fprintf(stderr, "\n");
									exit(EXIT_FAILURE);
								}
							}
						}
					}
					break;
				}
				case 16: {
					tiffBufferLong = (uint32*)memoryAllocate(sizeof(uint32)*w*h*2, "in ltiff2mrc");
					if(TIFFReadRGBAImage(tiff, w, h, tiffBufferLong, 0)<0) {	
						fprintf(stderr, "TIFFReadRGBAImage: Error\n");
						exit(EXIT_FAILURE);
					}
					for(row=0; row < h; row++) {
						for(col=0; col < w; col++) {
							switch(mode&0xf0) {
								case 0x00: {
#ifdef M68
									data = (double)*((unsigned short*)(tiffBufferLong+(col+w*row)) + 3);
#else
									data = (double)*((unsigned short*)(tiffBufferLong+(col+w*row)) + 0);
#endif
									mrcPixelDataSet(mrc, col, row, 0, data, mrcPixelMag);
									break;
								}
								case 0x10: {
#ifdef M68
									data = (double)*((unsigned short*)(tiffBufferLong+(col+w*row)) + 2);
#else
									data = (double)*((unsigned short*)(tiffBufferLong+(col+w*row)) + 1);
#endif
									mrcPixelDataSet(mrc, col, row, 0, data, mrcPixelMag);
									break;
								}
								case 0x20: {
#ifdef M68
									data = (double)*((unsigned short*)(tiffBufferLong+(col+w*row)) + 1);
#else
									data = (double)*((unsigned short*)(tiffBufferLong+(col+w*row)) + 2);
#endif
									mrcPixelDataSet(mrc, col, row, 0, data, mrcPixelMag);
									break;
								}
								case 0x40: {
#ifdef M68
									data = (double)*((unsigned short*)(tiffBufferLong+(col+w*row)) + 0);
#else
									data = (double)*((unsigned short*)(tiffBufferLong+(col+w*row)) + 3);
#endif
									mrcPixelDataSet(mrc, col, row, 0, data, mrcPixelMag);
									break;
								}
								default: {
									fprintf(stderr, "\n");
									exit(EXIT_FAILURE);
								}
							}
						}
					}
					break;
				}
			}
			break;
		}
	}	


	if(!linfo.flagMax) {
		switch(bitsPerSample) {
			case 8: {
				linfo.Max = 255;
				break;
			}
			case 16: {
				linfo.Max = 65535;
				break;
			}
			default: {
				fprintf(stderr, "Not supported\n");
				exit(EXIT_FAILURE);
				break;
			}
		}
	} 

	if(!linfo.flagMin) {
		linfo.Min = 1;
	} 

	if(!linfo.flagrealMaxBit) {
		switch(bitsPerSample) {
			case 8: {
				linfo.realMaxBit = 255;
				break;
			}
			case 16: {
				linfo.realMaxBit = 65535;
				break;
			}
			default: {
				fprintf(stderr, "Not supported\n");
				exit(EXIT_FAILURE);
				break;
			}
		}
	} 

	if(!linfo.flagrealMax) {
		linfo.realMin = 0;
	}

	if(!linfo.flagrealMin) {
		linfo.realMax = linfo.Max;
	}

	if(linfo.flagrealMax || linfo.flagrealMin) {
		for(row=0; row < h; row++) {
			for(col=0; col < w; col++) {
				mrcPixelDataGet(mrc, col, row, 0, &data, mrcPixelRePart, mrcPixelHowNearest);
				data = data*(linfo.realMax - linfo.realMin)/linfo.realMaxBit + linfo.realMin;
				mrcPixelDataSet(mrc, col, row, 0, data, mrcPixelRePart);
			}
		}
	}

	if(linfo.Inverse) {
		for(row=0; row < h; row++) {
			for(col=0; col < w; col++) {
				mrcPixelDataGet(mrc, col, row, 0, &data, mrcPixelRePart, mrcPixelHowNearest);
				data = (linfo.Max - data)/linfo.Min;
				mrcPixelDataSet(mrc, col, row, 0, data, mrcPixelRePart);
			}
		}
	}

	if(linfo.flagTime) {
		for(row=0; row < h; row++) {
			for(col=0; col < w; col++) {
				mrcPixelDataGet(mrc, col, row, 0, &data, mrcPixelRePart, mrcPixelHowNearest);
				data = data/linfo.Time;
				mrcPixelDataSet(mrc, col, row, 0, data, mrcPixelRePart);
			}
		}
	}

    switch(mode&0xf) {
        case 0: {
            break;
        }
        case 1: {
			for(row=0; row < h; row++) {
				for(col=0; col < w; col++) {
					mrcPixelDataGet(mrc, col, row, 0, &data, mrcPixelRePart, mrcPixelHowNearest);
					if(0<data) {
                    	data = log10(linfo.Max/data);
					} else {
                    	data = log10(linfo.Max);
					}
					mrcPixelDataSet(mrc, col, row, 0, data, mrcPixelRePart);
				}
            }
            break;
        }
        default: {
            fprintf(stderr, "Not supported mode : %ld\n", mode);
            exit(EXIT_FAILURE);
            break;
        }
    }

	if(linfo.flagShrink) {
		mrcImage shrink;
		mrcImageParaTypeIntegerCoord S;

		S.x = S.y = linfo.Shrink;
		S.z = 1;	
		lmrcImageShrink(&shrink, mrc, S, 0); 

		*mrc = shrink; 
	}
}
