#include "global-state.h"
#include "file-loading.h"
#include "hashing.h"
#include "data-structure.h"
//#include "thpool.h"
//#include "ds/threadpool.h"
#include "src/threadpool.h"

#include <stdio.h>
#include <string.h>
#include <regex.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>

typedef struct HashArg
{
	bool hash256;
	Imagefile * im;
} HashArg;

extern UIState Ui;
extern FilesState LoadedFiles;
extern ProcessingOptions ProcOptions;

static regex_t Image_regex;
static pthread_mutex_t CountLock;
static size_t PoolCount = 0;

static bool PUFL_check_if_image ( char * path );
static void PUFL_compare_files ( bool hash256 );
static void PUFL_decouple_files ( void );
static void PUFL_hash64 ( void * imraw );
static void PUFL_hash256 ( void * imraw );
static void PUFL_hash_files ( bool hash256 );
static void PUFL_load_directory ( string * str );
static void PUFL_revalidate_files ( bool group, bool hash );
static void PUFL_scan_files ( void );
static void * PUFL_gather_files ( void * );

static bool
PUFL_check_if_image ( char * path )
{
	int err_ret = regexec( &Image_regex, path, 0, NULL, 0 );
	switch ( err_ret )
	{
		case 0:
			return true;
		case REG_NOMATCH:
			return false;
		default:
			fputs( "regex error.", stderr );
			return false;
	}
}

static void
PUFL_load_directory ( string * dir )
{
	DIR * curdir;
	struct dirent * entry = NULL;
	string * str = SU_new(SU_MAX_PATH_LENGTH);

	fprintf( stderr, "Loading contents of %s...\n", dir->s );

	if ( ( curdir = opendir(dir->s) ) == NULL )
	{
		fprintf( stderr, "Cannot open %s, skipping...\n", dir->s );
		SU_free(str);
		return;
	}

	while ( ( entry = readdir(curdir) ) )
	{
		struct stat st;

		if ( strcmp( entry->d_name, "." ) == 0 || strcmp( entry->d_name, ".." ) == 0 )
			continue;

		SU_set( str, "%s/%s", dir->s, entry->d_name );

		if ( stat( str->s, &st ) == 0 )
		{
			if ( S_ISREG(st.st_mode) )
			{
				if ( PUFL_check_if_image(str->s) )
					PUDS_Add_imagefile_by_path(str->s);
			}

			if ( Ui.recursive && S_ISDIR(st.st_mode) )
				PUFL_load_directory(str);
		}
	}

	fprintf( stderr, "Finished loading %s\n", dir->s );
	SU_free(str);
	closedir(curdir);
}

static void
PUFL_compare_files ( bool hash256 )
{
	Ui.processingStage = hash256 ? STAGE_PROC_COMPARED256 : STAGE_PROC_COMPARED64;

	for ( int i = 0; i + 1 < LoadedFiles.images.length; ++i )
		for ( int j = i + 1; j < LoadedFiles.images.length; ++j )
		{
			while ( Ui.threadPaused )
				sleep(1);

			LoadedFiles.compareiter = i;

			if ( hash256 )
				PUHS_Compare_Hashes_256bit( LoadedFiles.images.data[i], LoadedFiles.images.data[j] );
			else
				PUHS_Compare_Hashes_64bit( LoadedFiles.images.data[i], LoadedFiles.images.data[j] );
		}
}

static void
PUFL_hash64 ( void * imraw )
{
	while ( Ui.threadPaused )
		sleep(1);

	Imagefile * im = (Imagefile *)imraw;
	PUHS_Hash_Dhash_64bit(im);

	pthread_mutex_lock(&CountLock);
	if ( im->hashed )
		LoadedFiles.hashedfiles++;
	else
		LoadedFiles.failedfiles++;

	PoolCount--;
	pthread_mutex_unlock(&CountLock);

//	return NULL;
}

static void
PUFL_hash256 ( void * imraw )
{
	while ( Ui.threadPaused )
		sleep(1);

	Imagefile * im = (Imagefile *)imraw;
	PUHS_Hash_Dhash_256bit(im);

	pthread_mutex_lock(&CountLock);
	if ( im->hashed )
		LoadedFiles.hashedfiles++;
	else
		LoadedFiles.failedfiles++;

	PoolCount--;
	pthread_mutex_unlock(&CountLock);

//	return NULL;
}

static void
PUFL_hash_files ( bool hash256 )
{
	Ui.processingStage = hash256 ? STAGE_PROC_HASHD256 : STAGE_PROC_HASHD64;
	LoadedFiles.hashedfiles = 0;

	PoolCount = LoadedFiles.images.length;
	threadpool_t * pool = threadpool_create( ProcOptions.threads, LoadedFiles.images.length, 0 );

	int i;
	Imagefile * item;
	vec_foreach( &LoadedFiles.images, item, i )
	{
		threadpool_add( pool, hash256 ? PUFL_hash256 : PUFL_hash64, (void *)(item), 0 );
	}

	size_t poolcount_copy = 1;
	while ( poolcount_copy > 0 )
	{
		pthread_mutex_lock(&CountLock);
		poolcount_copy = PoolCount;
		pthread_mutex_unlock(&CountLock);

		sleep(1);
	}

	threadpool_destroy( pool, 0 );
}

static void
PUFL_revalidate_files ( bool group, bool hash )
{
	Ui.processingStage = STAGE_PROC_VALIDATING;

	int i;
	Imagefile * item;
	vec_foreach_rev( &LoadedFiles.images, item, i )
	{
		if ( group && item != NULL && item->group == NULL )
			PUDS_Remove_imagefile_main(item);
		else
		if ( hash && item != NULL && !item->hashed )
			PUDS_Remove_imagefile_main(item);
	}

	vec_compact(&LoadedFiles.images);
}

static void
PUFL_scan_files ( void )
{
	Ui.processingStage = STAGE_PROC_SCANNING;

	int i;
	string * item;
	vec_foreach( &LoadedFiles.dirs, item, i )
	{
		while ( Ui.threadPaused )
			sleep(1);

		PUFL_load_directory(item);
	}
}

static void
PUFL_decouple_files ( void )
{
	Ui.processingStage = STAGE_PROC_DECOUPLING;

	for ( int i = 0; i < LoadedFiles.images.length; ++i )
		LoadedFiles.images.data[i]->group = NULL;

	{
		int i;
		ImageGroup * item;
		vec_foreach_rev( &LoadedFiles.groups, item, i )
			PUDS_Remove_group_only(item);
	}
}

static void *
PUFL_gather_files ( void * v )
{
	fputs( "Thread started!", stderr );

	PUFL_scan_files();
	if ( LoadedFiles.images.length == 0 )
	{
		fputs( "No files were found, stopping...", stderr );
		Ui.processingStage = STAGE_PROC_NONE;
		return NULL;
	}

	puts("Starting main pass DHASH 64");

	PUFL_hash_files(false);
	PUFL_revalidate_files( false, true );
	PUFL_compare_files(false);
	PUFL_revalidate_files( true, true );

	puts("Finishing main pass DHASH 64");

	if ( ProcOptions.extraPassD256 )
	{
		puts("Starting extra pass DHASH 256");

		PUFL_decouple_files();
		PUFL_hash_files(true);
		PUFL_revalidate_files( false, true );
		PUFL_compare_files(true);
		PUFL_revalidate_files( true, true );

		puts("Finishing extra pass DHASH 256");
	}

	Ui.processingStage = STAGE_PROC_FINISHED;

	pthread_detach(pthread_self());
	return v;
}

void
PUFL_Init ( void )
{
	const char * regexstring = "(jpe?g?|gif|png)$";
	int err_ret = regcomp( &Image_regex, regexstring, REG_ICASE | REG_EXTENDED | REG_NOSUB );
	if ( err_ret != 0 )
	{
		fputs( "Failed to compile regexp, exitting...", stderr );
		exit(EXIT_FAILURE);
	}

	puts("Regexp is cooked!");

	vec_init(&LoadedFiles.groups);
	vec_init(&LoadedFiles.dirs);
	vec_init(&LoadedFiles.images);

	pthread_mutex_init( &CountLock, NULL );
}

void
PUFL_Close ( void )
{
	PUDS_Dispose();
	vec_deinit(&LoadedFiles.groups);
	vec_deinit(&LoadedFiles.dirs);
	vec_deinit(&LoadedFiles.images);
	regfree(&Image_regex);

	pthread_mutex_destroy(&CountLock);
}

void
PUFL_Process_directories ( void )
{
	puts("Start processing...");
	pthread_t worker;
	if ( pthread_create( &worker, NULL, PUFL_gather_files, NULL ) )
	{
		fputs( "THREAD FAILURE!\n", stderr );
		return;
	}
}

bool
PUFL_Add_directory ( char * path )
{
	for ( int i = 0; i < LoadedFiles.dirs.length; ++i )
	{
		if ( !strcmp( path, LoadedFiles.dirs.data[i]->s ) )
		{
			fprintf( stderr, "Path %s is already added, skipping.\n", path );
			return false;
		}
	}

	string * str = SU_simple(path);
	if ( vec_push( &LoadedFiles.dirs, str ) )
		fprintf( stderr, "Failed vec_push() in PUFL_Add_directory(%s)!\n", path );

	return true;
}

void
PUFL_File_delete ( Imagefile * im )
{
	printf( "Deleting %s\n", im->path->s );
	remove(im->path->s);
	im->disposed = true;
}

void
PUFL_Refresh_counters ( void )
{
	LoadedFiles.dupenum = 0;

	for ( int i = 0; i < LoadedFiles.groups.length; ++i )
		LoadedFiles.dupenum += PUDS_Get_valid_group_entries( LoadedFiles.groups.data[i], -1, -1 );
}
