#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <sys/timeb.h>

#if defined(__APPLE__) || defined(__UCLIBC__)
#include <sys/time.h>
int ftime(struct timeb *tp)
{
	struct timeval tv;

	gettimeofday(&tv, NULL);
	tp->time = tv.tv_sec;
	tp->millitm = tv.tv_usec / 1000;
	tp->timezone = 0;
	tp->dstflag = 0;

	return 0;
}
#endif

#include <FncExtractor.h>
#include <FncMatcher.h>
#include <NFRecord.h>

#include "pgm.h"
#include "version.h"


int DPI = 500;
#define DB_LIST "db_list.txt"
#define IDENT_LIST "ident_list.txt"


typedef struct record_t_ {
	char *name;
	int features_size;
	NByte *features;
} record_t;

int db_size = 0;
int db_slots = 0;
record_t *db = NULL;
record_t record;

struct timeb T1, T2; /* structs for measuring time */

NSizeType maxRecordSize = 0;
void * extraction_buff = NULL;

int do_extraction(HFncExtractor hExtractor, char* fileName, record_t *res)
{
	HNFRecord hRecord;
	NResult result;
	int milisecs = 0;
	NSizeType recordSize = 0;

	// Extract template from raw image (1 byte per 1 gray pixel)
	{
		NByte *image = NULL;
		int width = 0;
		int height = 0;
		int stride;

		/* Load image from file in PGM format.
		 * load_pgm_file() uses interface of Linux scanner driver, so images
		 * acquired with scanner->read() can be used the same way */
		result = load_pgm_file(fileName, &image, &width, &height);
		if (result != 0)
		{
			printf("%s: failed to load image\n", fileName);
			return -1;
		}
		// load_pgm_file() returns image without padding (so stride == width)
		stride = width;

		ftime(&T1);
		result = FnceExtractUnpacked(hExtractor, width, height, stride, DPI, DPI,
				image, nfpUnknown, nfitLiveScanPlain, &hRecord);
		ftime(&T2);
	}


	milisecs = ((T2.time - T1.time) * 1000) + T2.millitm - T1.millitm;
	printf("%s,\t %d sec, %d millisec\t",
		fileName, milisecs / 1000, milisecs % 1000);

	if (result != N_OK)
	{
		printf (" ERROR! An error occured when extracting (%d).\n", result);
		return -1;
	}

	if (hRecord == NULL)
	{
		printf (" ERROR! Image is low quality.\n");
		return -1;
	}

	// Show some info about template (template info can be used to deside
	// about template quality in real application)
	{
		NByte quality = 0;
		NByte G = 0;
		NInt minutiaCount = 0;

		result = NFRecordGetMinutiaCount(hRecord, &minutiaCount);
		if (result != N_OK)
		{
			printf("NFRecordGetMinutiaCount() failed: %d\n", result);
		}

		result = NFRecordGetQuality(hRecord, &quality);
		if (result != N_OK)
		{
			printf("NFRecordGetQuality() failed: %d\n", result);
		}

		result = NFRecordGetG(hRecord, &G);
		if (result != N_OK)
		{
			printf("NFRecordGetG() failed: %d\n", result);
		}

		printf(" [quality: %3i, minutia count: %3i, G: %3i, ", quality, minutiaCount, G);
	}

	// Templates must be packed before matching
	result = NFRecordSaveToMemory(hRecord, extraction_buff, maxRecordSize, 0, &recordSize);
	NFRecordFree(hRecord);
	if (result != N_OK)
	{
		printf("NFRecordSaveToMemory() failed: %d\n", result);
		return -1;
	}

	printf("template size: %lu bytes]\n", (long unsigned int)recordSize);

	res->features_size = recordSize;
	res->features = NULL;
	res->name = NULL;
	res->name = strdup(fileName);
	res->features = malloc(recordSize);
	if (!res->features || !res->name)
	{
		printf("%s: low mem\n", fileName);
		free(res->name);
		free(res->features);
		return -1;
	}
	memcpy(res->features, extraction_buff, recordSize);

	return 0;
}

int do_fast_extraction_start(HFncExtractor hExtractor, char *fileName, record_t * res)
{
	HNFRecord hRecord;
	NResult result;
	int milisecs = 0;
	NSizeType recordSize = 0;

	// Extract template from raw image (1 byte per 1 gray pixel)
	{
		NByte *image = NULL;
		int width = 0;
		int height = 0;
		int stride;

		/* Load image from file in PGM format.
		 * load_pgm_file() uses interface of Linux scanner driver, so images
		 * acquired with scanner->read() can be used the same way */
		result = load_pgm_file(fileName, &image, &width, &height);
		if (result != 0)
		{
			printf("%s: failed to load image\n", fileName);
			return -1;
		}
		// load_pgm_file() returns image without padding (so stride == width)
		stride = width;

		ftime(&T1);
		result = FnceFastExtractStartUnpacked(hExtractor, width, height, stride, DPI, DPI,
				image, nfpUnknown, nfitLiveScanPlain, &hRecord);
		ftime(&T2);
	}

	milisecs = ((T2.time - T1.time) * 1000) + T2.millitm - T1.millitm;
	printf("%s,\t %d sec, %d millisec\t",
		fileName, milisecs / 1000, milisecs % 1000);

	if (result != N_OK)
	{
		printf (" ERROR! An error occured when extracting (%d).\n", result);
		return -1;
	}

	if (hRecord == NULL)
	{
		printf (" ERROR! Image is low quality.\n");
		return -1;
	}

	// Show some info about template (template info can be used to deside
	// about template quality in real application)
	{
		NByte quality = 0;
		NByte G = 0;
		NInt minutiaCount = 0;

		result = NFRecordGetMinutiaCount(hRecord, &minutiaCount);
		if (result != N_OK)
		{
			printf("NFRecordGetMinutiaCount() failed: %d\n", result);
		}

		result = NFRecordGetQuality(hRecord, &quality);
		if (result != N_OK)
		{
			printf("NFRecordGetQuality() failed: %d\n", result);
		}

		result = NFRecordGetG(hRecord, &G);
		if (result != N_OK)
		{
			printf("NFRecordGetG() failed: %d\n", result);
		}

		printf(" [quality: %3i, minutia count: %3i, G: %3i, ", quality, minutiaCount, G);
	}

	// Templates must be packed before matching
	result = NFRecordSaveToMemory(hRecord, extraction_buff, maxRecordSize, 0, &recordSize);
	NFRecordFree(hRecord);
	if (result != N_OK)
	{
		printf("NFRecordSaveToMemory() failed: %d\n", result);
		return -1;
	}

	printf("template size: %lu bytes]\n", (long unsigned int)recordSize);

	res->features_size = recordSize;
	res->features = NULL;
	res->name = NULL;
	res->name = strdup(fileName);
	res->features = malloc(recordSize);
	if (!res->features || !res->name)
	{
		printf("%s: low mem\n", fileName);
		free(res->name);
		free(res->features);
		return -1;
	}
	memcpy(res->features, extraction_buff, recordSize);

	return 0;
}

int do_fast_extraction_finish(HFncExtractor hExtractor, char *fileName, record_t * res)
{
	HNFRecord hRecord;
	NResult result;
	int milisecs = 0;
	NSizeType recordSize = 0;

	// Extract template from raw image (1 byte per 1 gray pixel)
	{
		NByte *image = NULL;
		int width = 0;
		int height = 0;
		int stride;

		/* Load image from file in PGM format.
		 * load_pgm_file() uses interface of Linux scanner driver, so images
		 * acquired with scanner->read() can be used the same way */
		result = load_pgm_file(fileName, &image, &width, &height);
		if (result != 0)
		{
			printf("%s: failed to load image\n", fileName);
			return -1;
		}
		// load_pgm_file() returns image without padding (so stride == width)
		stride = width;

		ftime(&T1);
		result = FnceFastExtractFinishUnpacked(hExtractor, width, height, stride, DPI, DPI,
				image, &hRecord);
		ftime(&T2);
	}

	milisecs = ((T2.time - T1.time) * 1000) + T2.millitm - T1.millitm;
	printf("%s,\t %d sec, %d millisec\t",
		fileName, milisecs / 1000, milisecs % 1000);

	if (result != N_OK)
	{
		printf (" ERROR! An error occured when extracting (%d).\n", result);
		return -1;
	}

	if (hRecord == NULL)
	{
		printf (" ERROR! Image is low quality.\n");
		return -1;
	}

	// Show some info about template (template info can be used to deside
	// about template quality in real application)
	{
		NByte quality = 0;
		NByte G = 0;
		NInt minutiaCount = 0;

		result = NFRecordGetMinutiaCount(hRecord, &minutiaCount);
		if (result != N_OK)
		{
			printf("NFRecordGetMinutiaCount() failed: %d\n", result);
		}

		result = NFRecordGetQuality(hRecord, &quality);
		if (result != N_OK)
		{
			printf("NFRecordGetQuality() failed: %d\n", result);
		}

		result = NFRecordGetG(hRecord, &G);
		if (result != N_OK)
		{
			printf("NFRecordGetG() failed: %d\n", result);
		}

		printf(" [quality: %3i, minutia count: %3i, G: %3i, ", quality, minutiaCount, G);
	}

	// Templates must be packed before matching
	result = NFRecordSaveToMemory(hRecord, extraction_buff, maxRecordSize, 0, &recordSize);
	NFRecordFree(hRecord);
	if (result != N_OK)
	{
		printf("NFRecordSaveToMemory() failed: %d\n", result);
		return -1;
	}

	printf("template size: %lu bytes]\n", (long unsigned int)recordSize);

	res->features_size = recordSize;
	res->features = NULL;
	res->name = NULL;
	res->name = strdup(fileName);
	res->features = malloc(recordSize);
	if (!res->features || !res->name)
	{
		printf("%s: low mem\n", fileName);
		free(res->name);
		free(res->features);
		return -1;
	}
	memcpy(res->features, extraction_buff, recordSize);

	return 0;
}

int do_fast_extraction_end(HFncExtractor hExtractor)
{
	NResult result;

	result = FnceFastExtractEnd(hExtractor);
	if (result != N_OK)
	{
		printf("Fast Extract: An error occured (code %d). Exiting.\n", result);
		return -1;
	}
	return 0;
}

void do_identify(HFncMatcher hMatcher)
{
	NInt score;
	int result;
	int i;
	int milisecs = 0;

	ftime (&T1);
	FncmIdentifyStart(hMatcher, record.features, record.features_size, NULL);

	for (i=0; i < db_size; i++)
	{
		result = FncmIdentifyNext(hMatcher, db[i].features, db[i].features_size, NULL, &score);
		if (result != N_OK)
		{
			printf("An error occured when verifying \"%s\" with \"%s\" (%d).\n",
					record.name, db[i].name, result);
			continue;
		}

		if (score > 0)
		{
#ifndef HIDE_SCORE
			printf("%s matched with score: %d\n", db[i].name, score);
#endif
			// If only first match is needed search can be interrupted here:
			// break;
		}
	}
	FncmIdentifyEnd(hMatcher);
	ftime (&T2);

	milisecs = ((T2.time - T1.time) * 1000) + T2.millitm - T1.millitm;
	printf ("\n"
		"Performed 1:%d identification\n"
		"  matching time:\t %d sec, %d millisec\n"
		"  matching speed:\t %d\n\n",
		db_size,
		milisecs / 1000, milisecs % 1000,
		(db_size)*1000/(milisecs ? milisecs : 1));
}

int do_set_parameters(HFncExtractor hExtractor, HFncMatcher hMatcher)
{
	int	show_quality_threshold = 0;
	int	change_quality_threshold = 0;
	int	change_matching_threshold = 0;
	int	use_small_templates = 0;
	int	result;

	// Uncomment to show default quality treshold value:
	// show_quality_threshold = 1;
	if (show_quality_threshold)
	{
		NByte byte;
		NBool boool;

		/* FNCEP_USE_QUALITY */
		FnceGetParameter(hExtractor, FNCEP_USE_QUALITY, &boool);
		printf("default FNCEP_USE_QUALITY: %s\n", (boool) ? "yes" : "no");

		/* FNCEP_QUALITY_THRESHOLD */
		FnceGetParameter(hExtractor, FNCEP_QUALITY_THRESHOLD, &byte);
		// round
		result = ((double)byte * 100 / 255) + 0.5;
		printf("default FNCEP_QUALITY_THRESHOLD: %d (%d %%)\n", byte, result);
	}

	// Uncomment to change default quality treshold value:
	// change_quality_threshold = 1;
	if (change_quality_threshold)
	{
		NByte byte;

		byte = 80;
		result = FnceSetParameter(hExtractor, FNCEP_QUALITY_THRESHOLD, &byte);
		if (result != N_OK)
		{
			printf("FnceSetParameter(FNCEP_QUALITY_THRESHOLD, %d) failed: %d\n", byte, result);
		}
		else
		{
			// round
			result = ((double)byte * 100 / 255) + 0.5;
			printf("new FNCEP_QUALITY_THRESHOLD: %d (%d %%)\n", byte, result);
		}
	}

	// Uncomment to change default matching treshold value:
	// change_matching_threshold = 1;
	if (change_matching_threshold)
	{
		NInt mthresh;


		/*
		 * Matching threshold value must be not less than zero
		 *   if FAR is expressed as ratio from 0 to 1.0:  -12 * log10(FAR_ratio)
		 *   if FAR is expressed as percentage 0 to 100%:  12 * (-log10(FAR_percent) + 2)
		 * for example, 48 for FAR == 0.01%.
		 */
		mthresh = 60; // FAR 0.001 %
		result = FncmSetParameter(hMatcher, FNCMP_MATCHING_THRESHOLD, &mthresh);
		if (result != N_OK)
		{
			printf("FncmSetParameter(FNCMP_MATCHING_THRESHOLD, %d) failed: %d\n", mthresh, result);
		}
		else
		{
			printf("new FNCMP_MATCHING_THRESHOLD: %d\n", mthresh);
		}
	}

	// Uncomment to use smaller template sizes (with lower quality):
	// use_small_templates = 1;
	if (use_small_templates)
	{
		NInt tmpl_size;

		tmpl_size = fncetsSmall;
		result = FnceSetParameter(hExtractor, FNCEP_TEMPLATE_SIZE, &tmpl_size);
		if (result != N_OK)
		{
			printf("FnceSetParameter(FNCEP_TEMPLATE_SIZE, %d) failed: %d\n", tmpl_size, result);
		}
		else
		{
			char * tmpl_size_str = "INVALID size";
			switch (tmpl_size) {
				case fncetsSmall:
						tmpl_size_str = "fncetsSmall";
						break;
				case fncetsLarge:
						tmpl_size_str = "fncetsLarge";
						break;
			}
			printf("using FNCEP_TEMPLATE_SIZE: %d (%s)\n", tmpl_size, tmpl_size_str);
		}
	}

	return 0;
}

int main(int argc, char *argv[])
{
	FILE *fdb, *fident;
	char name[1024];
	HFncExtractor hExtractor;
	HFncMatcher hMatcher;
	int	result;
	int	i;

	if (argc == 2)
	{
		DPI = atoi(argv[1]);
		if (DPI <= 0)
		{
			printf("usage: %s [DPI]\n", argv[0]);
			return 1;
		}
	}

	result = FnceCreate(&hExtractor);
	if (result != N_OK)
	{
		printf("FnceCreate returns %d\n", result);
		return 1;
	}

	result = FncmCreate(&hMatcher);
	if (result != N_OK)
	{
		printf("FncmCreate returns %d\n", result);
		goto finalize_extractor;
	}

	result = print_version(hExtractor, hMatcher);
	if (result < 0)
	{
		goto finalize_matcher;
	}

	do_set_parameters(hExtractor, hMatcher);

	FnceGetMaxTemplateSize(hExtractor, &maxRecordSize);
	extraction_buff = malloc(maxRecordSize);
	if (extraction_buff == NULL)
	{
		printf("low memory\n");
		return 1;
	}

	printf("Demo will enroll fingerprint images listed in %s file to memory.\n"
		"Next fingerprint images listed in %s file will be identified against enrolled.\n"
		"Image resolution must be %d dpi.\n"
		"Features extraction and identification timings will be shown\n"
		"\n",
		DB_LIST, IDENT_LIST, DPI);

	fdb = fopen(DB_LIST, "r");
	if (fdb == NULL)
	{
		printf("unable to open file with image list (%s): %s\n", DB_LIST, strerror(errno));
		goto finish;
	}

	db_slots = 100;
	db_size = 0;
	db = (record_t*) malloc(sizeof(record_t)*db_slots);
	if (!db)
	{
		printf("low memory. Exiting\n");
		goto finish;
	}

	printf("Image file,\timage processing (feature extraction) time\n");
	while (fgets(name, sizeof(name), fdb) != NULL)
	{
		i = strlen(name) - 1;
		while(isspace(name[i]))
		{
			i--;
		}
		name[i+1] = 0;
		if (name[0] == 0)
		{
			continue;
		}

		if (db_size >= db_slots)
		{
			record_t *tmp;
			db_slots = db_size + db_size / 10;
			tmp = (record_t*) realloc(db, db_slots * sizeof(record_t));
			if (tmp != NULL)
			{
				db = tmp;
			}
			else
			{
				printf("low memory. No more images will be extracted\n");
				break;
			}
		}

		if (do_extraction(hExtractor, name, &db[db_size]) == 0)
		{
			db_size++;
		}
	}
	fclose(fdb);

	if (db_size < 1)
	{
		printf("Nothing enrolled. Exiting\n");
		goto finish;
	}

	printf("\n"
		"Now fingerprints listed in %s file will be identified against enrolled.\n"
		"\n", IDENT_LIST);

	fident = fopen(IDENT_LIST, "r");
	if (fident == NULL)
	{
		printf("unable to open file with image list (%s): %s\n", IDENT_LIST, strerror(errno));
		goto finish;
	}

	while (fgets(name, sizeof(name), fident) != NULL)
	{
		i = strlen(name) - 1;
		while(i >=0 && isspace(name[i]))
		{
			i--;
		}
		name[i+1] = 0;
		if (name[0] == 0)
		{
			continue;
		}

		printf("Image file,\timage processing (fast feature extraction) time\n");
		if (do_fast_extraction_start(hExtractor, name, &record) == 0)
		{
			do_identify(hMatcher);
			free(record.name);
			free(record.features);
		}

		printf("Image file,\timage processing (full feature extraction) time\n");
		if (do_fast_extraction_finish(hExtractor, name, &record) == 0)
		{
			do_identify(hMatcher);
			free(record.name);
			free(record.features);
		}
	}
	fclose(fident);

	for (i=0; i < db_size; i++)
	{
		free(db[i].name);
		free(db[i].features);
	}
	free(db);

 finish:
 	free(extraction_buff);
 finalize_matcher:
	FncmFree(hMatcher);
 finalize_extractor:
	FnceFree(hExtractor);

	return 0;
}

#ifdef __cplusplus
}
#endif

