#include "MobilePhoteTool_IFD.h"

namespace MobilePhotoTool{

	IFD::IFD(MPT_Exception *, MPT_Buffer *buffer, size_t position)
	{
		this->position			= position;
		this->buffer			= buffer;
		this->ifd_entrys		= NULL;
		this->sub_ifd			= NULL;
		this->numberofEntrys	= 0;
		this->numberofSub_ifd	= 0;
		//read();
	}


	IFD::~IFD(void)
	{
		size_t i;

		if(this->ifd_entrys != NULL){
			for(i = 0; i < numberofEntrys; i += 1){
				if(ifd_entrys[i] != NULL){
					//ifd_entrys[i]->~IFD_ENTRY();
					delete ifd_entrys[i];
					ifd_entrys[i] = NULL;
				}
			}
			delete[] this->ifd_entrys;
		}

		if(this->sub_ifd != NULL){
			for(i = 0; i < numberofSub_ifd; i += 1){
				if(sub_ifd[i] != NULL){
					//sub_ifd[i]->~IFD();
					delete sub_ifd[i];
					sub_ifd[i] = NULL;
				}
			}
			delete []sub_ifd;
		}

	}

	size_t IFD::read(MPT_Exception *exception)
	{
		size_t offset1, *offset2, i;
		size_t nextIFD = 0;
		buffer->position(exception, position);
		MPT_EXCEPTION_TEST 0;

		numberofEntrys = buffer->get_ushort(exception);
		MPT_EXCEPTION_TEST 0;
		ifd_entrys = new IFD_ENTRY*[numberofEntrys];
		for(i = 0; i < numberofEntrys; i += 1){
			ifd_entrys[i] = NULL;
		}

		offset1 = buffer->position(exception);
		MPT_EXCEPTION_TEST 0;
		
		for(i = 0; i < numberofEntrys; i += 1){
			ifd_entrys[i] = new IFD_ENTRY(exception, buffer, offset1 + 12 * i);
			MPT_EXCEPTION_TEST 0;

			if((ifd_entrys[i]->type->TAG == IFD_TYPE::SubIFDs)){
				if(sub_ifd == NULL){
					offset2			= ifd_entrys[i]->get_integer_as_streamoff(exception, &numberofSub_ifd);
					MPT_EXCEPTION_TEST 0;
					//numberofSub_ifd	= ifd_entrys[i]->get_number_values();
					sub_ifd			= new IFD*[numberofSub_ifd];
					for(unsigned int subifdPos = 0; subifdPos < numberofSub_ifd; subifdPos += 1){
						sub_ifd[subifdPos] = new IFD(exception, buffer, offset2[subifdPos] );
						MPT_EXCEPTION_TEST 0;
						sub_ifd[subifdPos]->read(exception);
						MPT_EXCEPTION_TEST 0;
					}
					delete []offset2;
				}else{
					MPT_EXCEPTION("To many subifds.") 0;
				}
			}
		}
		buffer->position(exception, offset1 + numberofEntrys * 12);
		nextIFD = buffer->get_uint(exception);
		MPT_EXCEPTION_TEST 0;

		return nextIFD;
	}

	IFD_ENTRY *IFD::get_IFD_ENTRY(MPT_Exception *, int tag)
	{
		IFD_ENTRY *ifd_entry = NULL;
		for(unsigned int i = 0; i < this->numberofEntrys; i += 1){
			if(this->ifd_entrys[i]->type->TAG == tag){
				ifd_entry = this->ifd_entrys[i];
				break;
			}
		}
		return ifd_entry;
	}

	bool IFD::is_reduced_resolution(MPT_Exception *exception ){
		bool is_reduced_resolution = false;
		IFD_ENTRY *ifd_entry =  this->get_IFD_ENTRY(exception, IFD_TYPE::NewSubfileType);
		MPT_EXCEPTION_TEST false;

		unsigned int value;
		
		if(ifd_entry != NULL){
			value = ifd_entry->get_integer_as_uinterger(exception);
			MPT_EXCEPTION_TEST false;
			if((value & 1) == 1 ){
				is_reduced_resolution = true;
			}
		}
		return is_reduced_resolution;
	}

	bool IFD::is_raw(MPT_Exception *exception ){
		bool is_raw = false;
		IFD_ENTRY *ifd_entry =  this->get_IFD_ENTRY(exception, IFD_TYPE::NewSubfileType);
		MPT_EXCEPTION_TEST false;
		unsigned int value;
		
		if(ifd_entry != NULL){
			value = ifd_entry->get_integer_as_uinterger(exception);
			MPT_EXCEPTION_TEST false;
			if(value == 0 ){
				is_raw = true;
			}
		}
		return is_raw;
	}


	IFD *IFD::reduced_resolution_IFD(MPT_Exception *exception ){
		IFD *ifd = NULL;
		if(this->is_reduced_resolution(exception)){
			MPT_EXCEPTION_TEST NULL;
			ifd = this;
		}else{			
			MPT_EXCEPTION_TEST NULL;
			for(unsigned int i = 0; i < numberofSub_ifd; i += 1){
				ifd = sub_ifd[i]->reduced_resolution_IFD(exception);
				MPT_EXCEPTION_TEST NULL;
				if(ifd != NULL){
					break;
				}
			}
		}
		return ifd;
	}
	

	IFD *IFD::raw_IFD(MPT_Exception *exception){
		IFD *ifd = NULL;
		if(this->is_raw(exception)){
			ifd = this;
		}else{
			for(unsigned int i = 0; i < this->numberofSub_ifd; i += 1){
				ifd = sub_ifd[i]->raw_IFD(exception);
				MPT_EXCEPTION_TEST NULL;
				if(ifd != NULL){
					break;
				}
			}
		}
		return ifd;
	}
	

	unsigned int IFD::getImageWidth(MPT_Exception *exception){
		unsigned int width = 0;
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception , IFD_TYPE::ImageWidth);
		MPT_EXCEPTION_TEST 0;
		if(ifd_entry != NULL){
			width = ifd_entry->get_integer_as_uinterger(exception);
			MPT_EXCEPTION_TEST 0;
		}else{
			MPT_EXCEPTION("No Width") 0;
		}
		return width;
	}


	unsigned int IFD::getImageLength(MPT_Exception *exception)
	{
		unsigned int length = 0;
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::ImageLength);
		MPT_EXCEPTION_TEST 0;
		if(ifd_entry != NULL){
			length = ifd_entry->get_integer_as_uinterger(exception);
			MPT_EXCEPTION_TEST 0;
		}else{
			MPT_EXCEPTION("No Length") 0;
		}
		
		return length;
	}
	
	unsigned int *IFD::getBitsPerSample(MPT_Exception *exception, size_t *size)
	{
		unsigned int *bitspersample = NULL;
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::BitsPerSample);
		MPT_EXCEPTION_TEST 0;
		if(ifd_entry != NULL){
			bitspersample = ifd_entry->get_integer_as_uinterger_array(exception, size);
			MPT_EXCEPTION_TEST NULL;
		}else{
			MPT_EXCEPTION("No BitsPerSample") 0;
		}
		
		return bitspersample;
	}
	
	unsigned int IFD::getPhotometricInterpretation(MPT_Exception *exception)
	{
		unsigned int PhotometricInterpretation = 0; 
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::PhotometricInterpretation);
		MPT_EXCEPTION_TEST 0;
		if(ifd_entry != NULL){
			PhotometricInterpretation = ifd_entry->get_integer_as_uinterger(exception);
			MPT_EXCEPTION_TEST 0;
		}else{
			MPT_EXCEPTION("No PhotometricInterpretation") 0;
		}
		return PhotometricInterpretation;
	}
	
	unsigned int IFD::getCompression(MPT_Exception *exception)
	{
		int Compression = 0; 
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::Compression);
		MPT_EXCEPTION_TEST 0;
		if(ifd_entry != NULL){
			Compression = ifd_entry->get_integer_as_uinterger(exception);
			MPT_EXCEPTION_TEST 0;
		}else{
			MPT_EXCEPTION("No Compression") 0;
		}
		return Compression;
	}
	
	unsigned int *IFD::getStripOffsets(MPT_Exception *exception, size_t *size)
	{
		unsigned int *StripOffsets = NULL;
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::StripOffsets);
		MPT_EXCEPTION_TEST NULL;
		if(ifd_entry != NULL){
			StripOffsets= ifd_entry->get_integer_as_uinterger_array(exception, size);
			MPT_EXCEPTION_TEST NULL;
		}else{
			MPT_EXCEPTION("No StripOffsets") 0;
		}		
		return StripOffsets;
	}
	
	unsigned int IFD::getRowsPerStrip(MPT_Exception *exception)
	{
		unsigned int RowsPerStrip = 0;
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::RowsPerStrip);
		MPT_EXCEPTION_TEST 0;

		if(ifd_entry != NULL){
			RowsPerStrip = ifd_entry->get_integer_as_uinterger(exception);
			MPT_EXCEPTION_TEST 0;
		}else{
			MPT_EXCEPTION("No RowsPerStrip") 0;
		}			
		return RowsPerStrip;
	}
	
	unsigned int IFD::getStripByteCounts(MPT_Exception *exception)
	{
		unsigned int StripByteCounts = 0;
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::StripByteCounts);
		MPT_EXCEPTION_TEST 0;
		if(ifd_entry != NULL){
			StripByteCounts= ifd_entry->get_integer_as_uinterger(exception);
			MPT_EXCEPTION_TEST 0;
		}else{
			MPT_EXCEPTION("No StripByteCounts") 0;
		}			
		return StripByteCounts;
	}
	
	unsigned int IFD::getCFALayout(MPT_Exception *exception)
	{
		unsigned int CFALayout = 0;
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::CFALayout);
		MPT_EXCEPTION_TEST 0;
		if(ifd_entry != NULL){
			CFALayout= ifd_entry->get_integer_as_uinterger(exception);
			MPT_EXCEPTION_TEST 0;
		}else{
			MPT_EXCEPTION("No CFALayout") 0;
		}			
		return CFALayout;		
	}
	
	unsigned int *IFD::getCFAPattern(MPT_Exception *exception, size_t *size)
	{
		unsigned int *CFAPattern;
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::CFAPattern);
		MPT_EXCEPTION_TEST NULL;
		if(ifd_entry != NULL){
			CFAPattern= ifd_entry->get_integer_as_uinterger_array(exception, size);
			MPT_EXCEPTION_TEST NULL;
		}else{
			MPT_EXCEPTION("No CFALayout") NULL;
		}			
		return CFAPattern;		
	}

	unsigned int IFD::getSamplesPerPixel(MPT_Exception *exception){
		unsigned int samples = 0;
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::SamplesPerPixel);
		MPT_EXCEPTION_TEST 0;
		if(ifd_entry != NULL){
			samples = ifd_entry->get_integer_as_uinterger(exception);
			MPT_EXCEPTION_TEST 0;
		}else{
			MPT_EXCEPTION("No CFALayout") 0;
		}		

		return samples;
	}

	void IFD::getDefaultCropOrigin(MPT_Exception *exception, unsigned int *value)
	{
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::DefaultCropOrigin);
		MPT_EXCEPTION_TEST;
		if(ifd_entry != NULL){
			ifd_entry->get_as_rational(exception, value, 4);
			MPT_EXCEPTION_TEST;
		}else{
			MPT_EXCEPTION("No DefaultCropOrigin") ;
		}			
	}

	void IFD::getDefaultCropSize(MPT_Exception *exception, unsigned int *value)
	{
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::DefaultCropSize);
		MPT_EXCEPTION_TEST;
		if(ifd_entry != NULL){
			ifd_entry->get_as_rational(exception, value, 4);
			MPT_EXCEPTION_TEST;
		}else{
			MPT_EXCEPTION("No DefaultCropOrigin") ;
		}			
	}

	void IFD::getAsShotNeutral(MPT_Exception *exception, unsigned int *value)
	{
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::AsShotNeutral);
		MPT_EXCEPTION_TEST;
		if(ifd_entry != NULL){
			ifd_entry->get_as_rational(exception, value, 6);
			MPT_EXCEPTION_TEST;
		}else{
			MPT_EXCEPTION("No AsShotNeutral") ;
		}	
	}

	void IFD::getColorMatrix1(MPT_Exception *exception, float *values)
	{
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::ColorMatrix1);
		MPT_EXCEPTION_TEST;
		if(ifd_entry != NULL){
			ifd_entry->get_as_float(exception, values, 9);
			MPT_EXCEPTION_TEST;
		}else{
			MPT_EXCEPTION("No AsShotNeutral") ;
		}
	}

	void IFD::getColorMatrix2(MPT_Exception *exception, float *values)
	{
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::ColorMatrix2);
		MPT_EXCEPTION_TEST;
		if(ifd_entry != NULL){
			ifd_entry->get_as_float(exception, values, 9);
			MPT_EXCEPTION_TEST;
		}else{
			MPT_EXCEPTION("No AsShotNeutral") ;
		}
	}

	void IFD::getAnalogBalance(MPT_Exception *exception, float *values)
	{
		IFD_ENTRY *ifd_entry = this->get_IFD_ENTRY(exception, IFD_TYPE::AnalogBalance);
		MPT_EXCEPTION_TEST;
		if(ifd_entry != NULL){
			ifd_entry->get_as_float(exception, values, 3);
			MPT_EXCEPTION_TEST;
		}else{
			MPT_EXCEPTION("No AsShotNeutral") ;
		}
	}


	unsigned char* IFD::getRawData(MPT_Exception *exception)
	{
		unsigned char *data = NULL;
		unsigned int	width, length, compression, *bitspersample,
						*stripoffsets, stripbytecount, numbytes, numbytestoread, readbytes;
		size_t numbitspersample, numstripoffsets;
		stripoffsets	= NULL;
		bitspersample	= NULL;

		if(!this->is_raw(exception)){
			MPT_EXCEPTION("Not allowed function call.") NULL;
		}
		MPT_EXCEPTION_TEST NULL;

		compression 					= this->getCompression(exception);
		MPT_EXCEPTION_TEST NULL;
		bitspersample 					= this->getBitsPerSample(exception, &numbitspersample);		
		MPT_EXCEPTION_TEST NULL;
		width 							= this->getImageWidth(exception);
		MPT_EXCEPTION_TEST NULL;
		length  						= this->getImageLength(exception);
		MPT_EXCEPTION_TEST NULL;
		stripoffsets					= this->getStripOffsets(exception, &numstripoffsets);
		MPT_EXCEPTION_TEST NULL;
		stripbytecount					= this->getStripByteCounts(exception);	
		MPT_EXCEPTION_TEST NULL;

		numbytes = width * length;
		if(bitspersample[0] == 12){
			if((numbytes % 2) != 0){
				numbytes += numbytes / 2 + 1; 
			}else{
				numbytes += numbytes / 2;
			}
		}else if(bitspersample[0] == 16){
			numbytes *= 2;
		}else if(bitspersample[0] != 8){
			MPT_EXCEPTION("Unknown PitsPerSample format") NULL;
		}

		if(compression == 1){
			data = new unsigned char[numbytes];
			for(unsigned int i = 0; i < numstripoffsets; i += 1){
				readbytes 		= i * stripbytecount;
				numbytestoread	= stripbytecount;
				if(readbytes + stripbytecount > numbytes){
					numbytestoread = numbytes - readbytes;
				}
				if(numbytestoread > stripbytecount){
					MPT_EXCEPTION("Illegal opperation.") NULL;
				}

				buffer->position(exception, stripoffsets[i]);
				buffer->get_ubytes(exception, &data[readbytes], numbytestoread);
				MPT_EXCEPTION_TEST NULL;
			}
		}else{
			MPT_EXCEPTION("Not supported compression.") NULL;
		}


		if(stripoffsets != NULL){
			delete []stripoffsets;
		}
		if(bitspersample != NULL){
			delete []bitspersample;
		}
		return data;
	}

	unsigned int IFD::getRawData_ts(
			MPT_Exception *exception,
			unsigned int length_pos,
			unsigned int num_row,
			unsigned char *data,
			unsigned int data_size)
	{
		unsigned int	width, length, compression, *bitspersample,
						*stripoffsets, stripbytecount, numbytestoread,
						num_bytes_per_row, offset, rows;
		size_t numbitspersample, numstripoffsets;
		stripoffsets	= NULL;
		bitspersample	= NULL;
		rows = 0;

		if(!this->is_raw(exception)){
			MPT_EXCEPTION("Not allowed function call.") rows;
		}
		MPT_EXCEPTION_TEST rows;

		compression 					= this->getCompression(exception);
		MPT_EXCEPTION_TEST rows;
		bitspersample 					= this->getBitsPerSample(exception, &numbitspersample);
		MPT_EXCEPTION_TEST rows;
		width 							= this->getImageWidth(exception);
		MPT_EXCEPTION_TEST rows;
		length  						= this->getImageLength(exception);
		MPT_EXCEPTION_TEST rows;
		stripoffsets					= this->getStripOffsets(exception, &numstripoffsets);
		MPT_EXCEPTION_TEST rows;
		stripbytecount					= this->getStripByteCounts(exception);
		MPT_EXCEPTION_TEST rows;

		num_bytes_per_row = width;
		if(bitspersample[0] == 12){
			if((num_bytes_per_row % 2) != 0){
				num_bytes_per_row += num_bytes_per_row / 2 + 1;
			}else{
				num_bytes_per_row += num_bytes_per_row / 2;
			}
		}else if(bitspersample[0] == 16){
			num_bytes_per_row *= 2;
		}else if(bitspersample[0] != 8){
			MPT_EXCEPTION("Unknown PitsPerSample format") rows;
		}

		if(length < length_pos + num_row){
			if(length > length_pos){
				num_row = length - length_pos;
			}else{
				num_row = 0;
			}
		}

		offset = length_pos * num_bytes_per_row;
		numbytestoread = num_row * num_bytes_per_row;
		if(compression == 1 && numstripoffsets == 1){
			//buffer->position(exception, stripoffsets[i]);
			buffer->get_ubytes_ts(exception, data, numbytestoread, stripoffsets[0] + offset);
			MPT_EXCEPTION_TEST rows;

		}else{
			MPT_EXCEPTION("Not supported compression.") rows;
		}


		if(stripoffsets != NULL){
			delete[] stripoffsets;
		}
		if(bitspersample != NULL){
			delete[] bitspersample;
		}
		return num_row;
	}

}
