/**********************************************************************
 
	Copyright (C) 2005- Hirohisa MORI <joshua@nichibun.ac.jp>
						Tomohito Nakajima <nakajima@zeta.co.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 <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include "kokudo_tiff.h"

int machineEndian_i=1;
int KokudoTiff::machineEndian = *(char*)&machineEndian_i;

static char errBuff[512];
static void kokudoTiffError(const char *msg){
	int *i=0;
	*i = 1;
	exit(1);
}

struct TiffHeader{
	BYTE header[2];
	BYTE tiffCode[2];
	ULONG ifdPinter;
};

struct IFDTag{
	KokudoTiff *tiff;
	USHORT tag;
	USHORT valueType;
	ULONG valueCount;
	ULONG valueOrValueOffset;
	
	void load(FILE *fp){
		tag = tiff->readShort(fp);
		valueType = tiff->readShort(fp);
		valueCount = tiff->readLong(fp);
		valueOrValueOffset = tiff->readLong(fp);
		
		if(valueCount==1){
			switch(valueType){
			case 1: // BYTE
				valueOrValueOffset = valueOrValueOffset>>24 & 0xff;
				break;
			case 2: // ASCII (code+\0)
				valueOrValueOffset = valueOrValueOffset>>24 & 0xff;
				break;
			case 3: // SHORT
				valueOrValueOffset = valueOrValueOffset>>16 & 0xffff;
				break;
			case 4: // LONG 
				break;
			case 5: // tow LONGs (offset) first is numerator/second is denominator
				break;
			}
		}
	}
};

struct StripDataHeader{
	ULONG fileOffset;
	ULONG byteCount;
};

//tiff Image File Directory
struct IFD{
	KokudoTiff *tiff;
	USHORT tagCount;
	IFDTag *tags;
	USHORT nextIFD;
	ULONG headOfIFD;
	
	USHORT width;
	USHORT height;
	USHORT bitsPerPixel;
	USHORT compressMethod;
#define PACK_BITS 32773
	USHORT colorType;
#define CT_PALETTE 3
	ULONG offsetsOfStripData;
	ULONG stripDataCount;
	StripDataHeader *stripDataHeaders;
	USHORT colorPlainCount;
	ULONG rowsPerStrip;
	ULONG offsetsOfStripByteCounts;
	
	ULONG XResolution;
	ULONG YResolution;
	USHORT planarConfiguration;
	USHORT resolutionUnit;
	
	TiffColorMap *colorMap;
	ULONG colorCount;
	
	ULONG nextFIDPos;

	IFD(KokudoTiff *t) : tiff(t),tags(0),stripDataHeaders(0),colorMap(0),stripDataBuff(0),stripDataBuffSize(0){}
	
	void load(FILE *fp){
		headOfIFD = ftell(fp);
		tagCount = tiff->readShort(fp);
		tags = new IFDTag[tagCount];
		int i;
		for(i=0; i<tagCount; ++i){
			tags[i].tiff = tiff;
			tags[i].load(fp);
		}
		nextFIDPos = tiff->readLong(fp);
		
		for(i=0; i<tagCount; ++i){
			switch(tags[i].tag){
				case 256:
					width = (USHORT)tags[i].valueOrValueOffset;
				break;
				case 257:
					height = (USHORT)tags[i].valueOrValueOffset;
				break;
				case 258:
					bitsPerPixel = (USHORT)tags[i].valueOrValueOffset;
				break;
				case 259: // 1, 2 or 32773 (kokudo data is 32773 PackBits compression)
					compressMethod = (USHORT)tags[i].valueOrValueOffset;
				break;
				case 262:
					colorType = (USHORT)tags[i].valueOrValueOffset;
				break;
				case 273:
					offsetsOfStripData = tags[i].valueOrValueOffset;
					stripDataCount = tags[i].valueCount;
				break;
				case 277: 
					colorPlainCount = (USHORT)tags[i].valueOrValueOffset;
				break;
				case 278:
					rowsPerStrip = tags[i].valueOrValueOffset;
				break;
				case 279:
					offsetsOfStripByteCounts = tags[i].valueOrValueOffset;
				break;
				case 282:
					fseek(fp, tags[i].valueOrValueOffset, SEEK_SET);
					XResolution = tiff->readRational(fp);
				break;
				case 283:
					fseek(fp, tags[i].valueOrValueOffset, SEEK_SET);
					YResolution =  tiff->readRational(fp);
				break;
				case 284: // How the components of each pixel are stored. Chunky format.
					planarConfiguration = (USHORT)tags[i].valueOrValueOffset;
				break;
				case 296: // 2 == pixel per inch
					resolutionUnit = (USHORT)tags[i].valueOrValueOffset;
				break;
				case 320: // colorMap
					{
						colorCount = tags[i].valueCount/3;
						fseek(fp, tags[i].valueOrValueOffset, SEEK_SET);
						colorMap = new TiffColorMap[colorCount];
						for(size_t i=0; i<colorCount; ++i){
							colorMap[i].r = tiff->readShort(fp);
							colorMap[i].g = tiff->readShort(fp);
							colorMap[i].b = tiff->readShort(fp);
						}
					}
				break;
				default:
					sprintf(errBuff, "unsupported tag %d", tags[i].tag);
					kokudoTiffError(errBuff);
			}
		}
		
		loadStripDataHeader(fp);
	}

	static void uncompressPackBits(BYTE *out, ULONG &outBuffSize, char *in, ULONG inSize)
	{
		ULONG inPos=0;
		ULONG outPos=0;
		while(inPos < inSize){
			if(in[inPos] < 0){
				memset(&out[outPos], in[inPos+1], -in[inPos]+1);
				outPos += -in[inPos]+1;
				inPos += 2;
			}
			else{
				int n = in[inPos++];
				for(int i=0; i<=n; ++i){
					out[outPos++] = in[inPos++];
				}
			}
		}

		if(outBuffSize < outPos){
			kokudoTiffError("to small uncompressed buffer");
		}
		else{
			outBuffSize = outPos;
		}
	}

	BYTE *stripDataBuff;
	ULONG stripDataBuffSize;
	BYTE *uncompressedStripDataBuff;
	void processData(FILE *fp, onTiffStripDataCallBack cbStripData, void *userParam){
		stripDataBuff = new BYTE[stripDataBuffSize];
		
		ULONG uncompressedSize = rowsPerStrip*width*((bitsPerPixel+7)/8);
		uncompressedStripDataBuff = new BYTE[uncompressedSize];
		
		for(size_t i=0; i<stripDataCount; ++i){
			ULONG dataLen = stripDataHeaders[i].byteCount;
			fseek(fp, stripDataHeaders[i].fileOffset, SEEK_SET);
			fread(stripDataBuff, dataLen, 1, fp);
			
			if(compressMethod == PACK_BITS){
				uncompressPackBits(uncompressedStripDataBuff, uncompressedSize, (char*)stripDataBuff, dataLen);
				cbStripData(tiff, uncompressedStripDataBuff, uncompressedSize, userParam);
			}
			else{
				cbStripData(tiff, stripDataBuff, dataLen, userParam);
			}
		}
	}
	
	
	void loadStripDataHeader(FILE *fp){
		size_t i;
		stripDataHeaders = new StripDataHeader[stripDataCount];
		fseek(fp, offsetsOfStripData, SEEK_SET);
		for(i=0; i<stripDataCount; ++i){
			stripDataHeaders[i].fileOffset = tiff->readLong(fp);
		}
		
		fseek(fp, offsetsOfStripByteCounts, SEEK_SET);
		for(i=0; i<stripDataCount; ++i){
			stripDataHeaders[i].byteCount = tiff->readLong(fp);
			if(stripDataBuffSize < stripDataHeaders[i].byteCount)
				stripDataBuffSize = stripDataHeaders[i].byteCount;
		}
	}
	
	~IFD(){
		delete []tags;
		delete []colorMap;
		delete []stripDataHeaders;
		delete []stripDataBuff;
	}
};

void KokudoTiff::load(char *path, void *userParam, onTiffStripDataCallBack cbStripData)
{
	TiffHeader h;
	FILE *fp = fopen(path, "rb");
	if(!fp){
		printf("error open file %s\n", path);
	}
	
	fread(&h.header, 2, 1, fp);
	if(memcmp(h.header, "MM", 2) == 0){
		fileEndian = 0;
	}
	else{
		fileEndian = 1;
	}
	fread(&h.tiffCode, 2, 1, fp);
	
	h.ifdPinter = readLong(fp);
	
	// seek to first ifd
	fseek(fp, h.ifdPinter, SEEK_SET);
	
	while(1){
		IFD fid(this);
		fid.load(fp);
		
		width=fid.width;
		height=fid.height;
		colorMap=fid.colorMap;
		bitPerPixel=fid.bitsPerPixel;
		
		fid.processData(fp, cbStripData, userParam);
		fseek(fp, fid.nextFIDPos, SEEK_SET);
		if(fid.nextFIDPos==0)
			break;
	};

	fclose(fp);
}

