#include "Nbis.h"

namespace Nbis {

	int Shared::DetermineImageDepth(Bitmap ^image)
{
	// Get the image depth, but only accept 8-bit (indexed) or 16-bit grayscale images
	//
	int image_depth;
	switch (image->PixelFormat) {
		case PixelFormat::Format16bppGrayScale:
			image_depth = 16;
			break;
		case PixelFormat::Format8bppIndexed:
			image_depth = 8;
			break;
		default:
			throw gcnew ApplicationException("Unsupported image format.");
		break;

	}
	return image_depth;
}
	void Shared::CheckReadAndDecodeGreyscaleImageOutcome(int return_value)
{
	switch (return_value) {
		case -2:
			throw gcnew NbisException(return_value, "read_and_decode_greyscale_image: grayscale image record not found");
			break;
		case -3:
			throw gcnew NbisException(return_value, "read_and_decode_greyscale_image: image type not supported");
			break;
		case -4:
			throw gcnew NbisException(return_value, "read_and_decode_greyscale_image: unsupported image depth");
			break;
		default:
			if (return_value != 0) {
				throw gcnew NbisException(return_value, "read_and_decode_greyscale_image: other error");
			}
		break;
	};
}

	void* Shared::FromBitmapToRaw(int width, int height, int stride, IntPtr data) {
					
		void* image_data = malloc((size_t) width * height);

		unsigned char* source = (unsigned char*)(void*) data;
		unsigned char* dest = (unsigned char*) image_data;
		for (int i=0; i < height; i++) {
			memcpy(dest, source, width);
			source += stride;
			dest += width;
		}
		
		return image_data;
	}

	Bitmap^ Shared::LoadFileIntoBitmap(String^ filename) {
		Bitmap^ result;

		if(filename->Trim()->ToLower()->EndsWith(".bmp")) {
			result = (Bitmap^)System::Drawing::Bitmap::FromFile(filename);
		} else if(filename->Trim()->ToLower()->EndsWith(".wsq")) {

			System::IO::FileInfo ^info = gcnew System::IO::FileInfo(filename);
			System::IO::FileStream ^fs = info->OpenRead();
			long length = (long)fs->Length;
			array<unsigned char> ^data = gcnew array<unsigned char>(length);
			fs->Read(data, 0, length);
			fs->Close();
			result = Nbis::Wsq::FromWsqToBitmap(data);

			delete info;
			delete fs;
			delete data;
		}

		return result;
	}

int Nfiq::FromBitmap(Bitmap^ image, int pixels_per_inch) {

	int return_value;

	float mlp_activation;
	int nfiq, verbose_flag = 0;

	int image_width = image->Width;
	int image_height = image->Height;
	int image_depth = Shared::DetermineImageDepth(image);
		
	Rectangle^ rect = gcnew Rectangle(0, 0, image_width, image_height);
	BitmapData^ bitmap_data = image->LockBits(*rect, ImageLockMode::ReadOnly, image->PixelFormat);		
	unsigned char* image_data = (unsigned char*) (void*) Shared::FromBitmapToRaw(image_width, image_height, bitmap_data->Stride, bitmap_data->Scan0);

	return_value = comp_nfiq(&nfiq, &mlp_activation, 
		image_data, image_width, image_height, image_depth, pixels_per_inch, &verbose_flag);
	
	image->UnlockBits(bitmap_data);
	
	free(image_data);

  	return nfiq;
};

int Nfiq::FromFile(String^ filename) {

	int return_value;
	int verbose_flag = 0;
	unsigned char* image_data;
	
    int image_type;
	int image_length, image_width, image_height, image_depth;
	int image_pixels_per_inch;
    int nfiq;
	float mlp_activation;
    
	char* image_filename = (char*)(void*) Marshal::StringToHGlobalAnsi(filename);
	return_value = read_and_decode_grayscale_image(
			image_filename, &image_type, &image_data, &image_length,
			&image_width, &image_height, &image_depth, &image_pixels_per_inch);
	Shared::CheckReadAndDecodeGreyscaleImageOutcome(return_value);
	
	
	return_value = comp_nfiq(&nfiq, &mlp_activation, 
		image_data, image_width, image_height, image_depth, image_pixels_per_inch, &verbose_flag);
 

   Marshal::FreeHGlobal((IntPtr) image_filename);
	
	return nfiq;
}

NbisException::NbisException(int errorCode, String^ message) : 
	ApplicationException(message) {
	this->ErrorCode = errorCode;
}

Bitmap^ Wsq::FromWsqToBitmap(array<Byte>^ wsq_data) {
	// Pin the input data until we're done with it
	GCHandle gch = GCHandle::Alloc(wsq_data, GCHandleType::Pinned);
	unsigned char* input_data = (unsigned char*)(void*) gch.AddrOfPinnedObject();

	// Perform the actual decoding
	int return_value;
	unsigned char* decompressed_data;
	int width, height, depth, pixels_per_inch, lossy_flag;
	//Console::WriteLine("input_data " + input_data[wsq_data->Length-1]);

	return_value = wsq_decode_mem(&decompressed_data, &width, &height, &depth,
		&pixels_per_inch, &lossy_flag, input_data, wsq_data->Length);
	
	//Console::Write("after decode");
	// TODO: Throw exceptions based on the return value

	// Create the bitmap; notice that we can use memcpy, suggesting that LockBits
	// gives you a pointer to unmanaged memory (interesting!)
	//
	int length_multiplier = 1;
	PixelFormat output_format = PixelFormat::Format8bppIndexed ;
	if (depth == 8)  { length_multiplier = 1; output_format = PixelFormat::Format8bppIndexed; }
	// TODO: Throw an exception on a bit-depth of something other than 8-bit
	
	// Copy from the output of the WSQ call into the bitmap
	Bitmap^ image = gcnew Bitmap(width, height, output_format);
	BitmapData^ bmd = image->LockBits(Rectangle(0, 0, width, height), ImageLockMode::WriteOnly, output_format);
	
	unsigned char* dest = (unsigned char*)(void*) bmd->Scan0;
	unsigned char* source = (unsigned char*)(void*) decompressed_data;

	for (int i=0; i < height; i++) {
		memcpy(dest, source, width * length_multiplier);
		dest += bmd->Stride;
		source += width;
	}
	image->UnlockBits(bmd);
	
	if (depth == 8) {
		// For some crazy reason, we have to write the palette in this roundabout fashion.
		ColorPalette^ palette = image->Palette;
		for (int i=0; i < 256; i++) {
			palette->Entries[i] = Color::FromArgb(i,i,i);
		}
		image->Palette = palette;
	}

	// Unpin the input data
	gch.Free();

	// Clean up the memory allocated by wsq_decode_image()
	free(decompressed_data);

	return image;
}


array<Byte>^ Wsq::FromBitmapToWsq(Bitmap^ image, float bitrate, int pixels_per_inch, String^ comment) {

	if (image->PixelFormat != PixelFormat::Format16bppGrayScale &&
		image->PixelFormat != PixelFormat::Format8bppIndexed) {
		throw gcnew ArgumentException("Unsupported image format.");
	}

	BitmapData^ bmd = image->LockBits(Rectangle(0, 0, image->Width, image->Height), 
		ImageLockMode::ReadOnly, image->PixelFormat);

	unsigned char* output_data;
	int output_length;
	int depth = 8;
	char* comment_text = (char*) Marshal::StringToHGlobalAnsi(comment).ToPointer();


	unsigned char* image_data = (unsigned char*)(void*) 
		Shared::FromBitmapToRaw(image->Width, image->Height, bmd->Stride, bmd->Scan0);

	if (image->PixelFormat == PixelFormat::Format8bppIndexed)    { depth = 8; }
	if (image->PixelFormat == PixelFormat::Format16bppGrayScale) { depth = 16; }
	int return_value = wsq_encode_mem(&output_data, &output_length, bitrate, 
		(unsigned char*)(void*) image_data,	image->Width, image->Height, 
		depth, pixels_per_inch, comment_text);
	// TODO: Check the return values here

	array<Byte>^ data = gcnew array<Byte>(output_length);
	Marshal::Copy((IntPtr) output_data, data, 0, output_length);
	Marshal::FreeHGlobal((IntPtr) comment_text);
	free(output_data);
	free(image_data);
	return data;
}

int Nfiq::FromWsq(array<Byte>^ wsqAsByteArray) {

	GCHandle gch = GCHandle::Alloc(wsqAsByteArray, GCHandleType::Pinned);
	unsigned char* input_data = (unsigned char*)(void*) gch.AddrOfPinnedObject();

	// Decode the WSQ
	int return_value;
	unsigned char* uncompressed_data;
	int width, height, depth, pixels_per_inch, lossy_flag;
	return_value = wsq_decode_mem(&uncompressed_data, &width, &height, &depth,
		&pixels_per_inch, &lossy_flag, input_data, wsqAsByteArray->Length);
	// TODO: Handle return values

	int nfiq, verbose_flag;
	float mlp_activation;
	return_value = comp_nfiq(&nfiq, &mlp_activation, 
		uncompressed_data, width, height, depth, pixels_per_inch, &verbose_flag);
  
	gch.Free();
	
	return nfiq;
}

array<Segment^>^ Segmentation::FromBitmap(Bitmap^ image, SegmenterParameters^ options) {

	int segment_count;
	if (options->TargetFingers == Finger::LeftSlap || options->TargetFingers == Finger::RightSlap) {
		segment_count = 4;
	} else if (options->TargetFingers == Finger::BothThumbs) {
		segment_count = 2;
	} else {
		throw gcnew ArgumentException("TargetFingers should be LeftSlap, RightSlap, or BothThumbs");	
	}
	
	int image_width = image->Width;
	int image_height = image->Height;
	int image_depth = Shared::DetermineImageDepth(image);
		
	Rectangle^ rect = gcnew Rectangle(0, 0, image_width, image_height);
	BitmapData^ bitmap_data = image->LockBits(*rect, ImageLockMode::ReadOnly, image->PixelFormat);		
	unsigned char* image_data = (unsigned char*)(void*) Shared::FromBitmapToRaw(image_width, image_height, bitmap_data->Stride, bitmap_data->Scan0);

	seg_rec_coords *finger_boxes;
	int return_value = segment_fingers(image_data, image->Width, image->Height, &finger_boxes, segment_count, (int) options->TargetFingers);


	array<Segment^>^ segments = gcnew array<Segment^>(segment_count);
	for (int i = 0; i < segment_count; i++)	{
		segments[i] = gcnew Segment;
		segments[i]->Height  = finger_boxes[i].sh;
		segments[i]->Width   = finger_boxes[i].sw;
		segments[i]->OriginX = finger_boxes[i].sx;
		segments[i]->OriginY = finger_boxes[i].sy;
		segments[i]->Rotation = finger_boxes[i].theta;
	}
	image->UnlockBits(bitmap_data);
	free(finger_boxes);

	free(image_data);

	return segments;
};

#define XYT_NULL ((struct xyt_struct *) NULL)
#define XYTQ_NULL ((struct xytq_struct *) NULL)

array<Minutia^>^ DetectMinutiae::FromBitmap(Bitmap ^image, int pixels_per_inch) {
	int image_width = image->Width;
	int image_height = image->Height;
	int image_depth = Shared::DetermineImageDepth(image);
		
	Rectangle^ rect = gcnew Rectangle(0, 0, image_width, image_height);
	BitmapData^ bitmap_data = image->LockBits(*rect, ImageLockMode::ReadOnly, image->PixelFormat);		
	unsigned char* image_data = (unsigned char*)(void*) Shared::FromBitmapToRaw(image_width, image_height, bitmap_data->Stride, bitmap_data->Scan0);

	minutiae *minutiae_out;
	int *direction_map, *low_contrast_map, *low_flow_map, *high_curve_map, *quality_map;
	int map_width, map_height;
	unsigned char *bdata;
	int bw, bh, bd;

	float ippmm = (float)(pixels_per_inch / 25.4);

	get_minutiae(&minutiae_out, &quality_map, &direction_map,
                         &low_contrast_map, &low_flow_map, &high_curve_map,
                         &map_width, &map_height, &bdata, &bw, &bh, &bd,
                         image_data, image->Width, image->Height, image_depth, ippmm, &lfsparms_V2);

	lfs2nist_format(minutiae_out, image->Width, image->Height);

	xytq_struct xytq_s;
	xytq_s.nrows = minutiae_out->num;

	for (int i = 0; i < minutiae_out->num; i++) {
		minutia* m = minutiae_out->list[i];

		xytq_s.xcol[i] = m->x;
		xytq_s.ycol[i] = m->y;
		xytq_s.thetacol[i] = m->direction;
		xytq_s.qualitycol[i] = (int)m->reliability;
	}
	
	xyt_struct * xyt_s = bz_prune(&xytq_s, 0);
	
	array<Minutia^>^ minutia = gcnew array<Minutia^>(xyt_s->nrows);
	for (int i = 0; i < xyt_s->nrows; i++) {
		minutia[i] = gcnew Minutia;
		minutia[i]->X = xyt_s->xcol[i];
		minutia[i]->Y = xyt_s->ycol[i];
		minutia[i]->Direction = xyt_s->thetacol[i];
	}

	// Clean up after ourselves
	image->UnlockBits(bitmap_data);
	free(minutiae_out);
    free(quality_map);
    free(direction_map);
    free(low_contrast_map);
    free(low_flow_map);
    free(high_curve_map);
	free(image_data);
    free(bdata);
	free(xyt_s);

	return minutia;
}

	#define MIN(A, B) (A<B)?A:B

	int Matcher::Compare(array<Minutia^>^ probe, array<Minutia^>^ gallery) {
		xyt_struct probe_struct;
		xyt_struct gallery_struct;
	
		probe_struct.nrows = probe->Length;

		for (int i = 0; i < probe_struct.nrows; i++) {
			probe_struct.xcol[i] = probe[i]->X;
			probe_struct.ycol[i] = probe[i]->Y;
			probe_struct.thetacol[i] = probe[i]->Direction;
		}

		gallery_struct.nrows = gallery->Length;

		for (int i = 0; i < gallery_struct.nrows; i++) {
			gallery_struct.xcol[i] = gallery[i]->X;
			gallery_struct.ycol[i] = gallery[i]->Y;
			gallery_struct.thetacol[i] = gallery[i]->Direction;
		}

		int result = bozorth_main(&probe_struct, &gallery_struct);

		return result;
	}	
};