#include "global-state.h"
#include "hashing.h"
#include "data-structure.h"
#include "raylib.h"
#include <stdio.h>

const unsigned ResizedSide64 = 8;
const unsigned ResizedSq64 = ResizedSide64 * ResizedSide64;

const unsigned ResizedSide256 = 16;
const unsigned ResizedSq256 = ResizedSide256 * ResizedSide256;

extern ProcessingOptions ProcOptions;

void
PUHS_Compare_Hashes_256bit ( Imagefile * im1, Imagefile * im2 )
{
	if ( im1 == NULL || im2 == NULL )
	{
		fprintf( stderr, "ERROR, IMAGE IN ELEMENT IS NULL!\n" );
		return;
	}

	if ( im1->hash == NULL || im2->hash == NULL )
		return;

	if ( im1->group != NULL && im2->group != NULL )
		return;

	HASH64BIT similarity_hash[4];
	for ( unsigned i = 0; i < ResizedSq256; ++i )
		similarity_hash[i / 64] = im1->hash[i / 64] ^ im2->hash[i / 64];

	unsigned similar_bits = ResizedSq256;

	for ( unsigned i = 0; i < ResizedSq256 / 64; ++i )
		while ( similarity_hash[i] != 0 )
		{
			similar_bits--;
			similarity_hash[i] &= similarity_hash[i] - 1;
		}

	if ( similar_bits / (float)ResizedSq256 >= ProcOptions.threshold )
	{
		if ( im1->group == NULL && im2->group == NULL )
			PUDS_Create_group( im1, im2, sizeof(HASH64BIT) );
		else
		{
			if ( im1->group == NULL )
				PUDS_Add_to_group( im1, im2->group );
			else
				PUDS_Add_to_group( im2, im1->group );
		}
	}
}

void
PUHS_Compare_Hashes_64bit ( Imagefile * im1, Imagefile * im2 )
{
	if ( im1 == NULL || im2 == NULL )
	{
		fprintf( stderr, "ERROR, IMAGE IN ELEMENT IS NULL!\n" );
		return;
	}

	if ( im1->hash == NULL || im2->hash == NULL )
		return;

	if ( im1->group != NULL && im2->group != NULL )
		return;

	HASH64BIT similarity_hash = im1->hash[0] ^ im2->hash[0];
	unsigned similar_bits = ResizedSq64;

	while ( similarity_hash != 0 )
	{
		similar_bits--;
		similarity_hash &= similarity_hash - 1;
	}

	if ( similar_bits / (float)ResizedSq64 >= ProcOptions.threshold )
	{
		if ( im1->group == NULL && im2->group == NULL )
			PUDS_Create_group( im1, im2, sizeof(HASH64BIT) );
		else
		{
			if ( im1->group == NULL )
				PUDS_Add_to_group( im1, im2->group );
			else
				PUDS_Add_to_group( im2, im1->group );
		}
	}
}

void
PUHS_Hash_Dhash_256bit ( Imagefile * im )
{
	if ( im->hash != NULL )
		free(im->hash);

	if ( !FileExists(im->path->s) )
	{
		fprintf( stderr, "File %s is suddenly missing!\n", im->path->s );
		im->hashed = false;
		return;
	}

	Image img = LoadImage( im->path->s );
	if ( img.data == NULL )
	{
		fprintf( stderr, "Failed to load image %s\n", im->path->s );
		im->hashed = false;
		return;
	}

	ImageResize( &img, ResizedSide256 + 1, ResizedSide256 );
	ImageColorGrayscale(&img);

	im->hash = calloc( 4, sizeof(HASH64BIT) );

	uint8_t * data = img.data;
	for ( unsigned u = 0, i = 0, bt = 0; u < ResizedSide256; ++u )
	{
		for ( unsigned v = 0; v < ResizedSide256; ++v, ++i, ++bt )
			im->hash[bt / 64] |= (HASH64BIT)( (unsigned)( data[i] - data[i + 1] ) >> ( sizeof(int) * 8 - 1 ) ) << ( bt % 64 );

		i++;
	}

	UnloadImage(img);
	im->hashed = true;
}

void
PUHS_Hash_Dhash_64bit ( Imagefile * im )
{
	if ( im->hash != NULL )
		free(im->hash);

	if ( !FileExists(im->path->s) )
	{
		fprintf( stderr, "File %s is suddenly missing!\n", im->path->s );
		im->hashed = false;
		return;
	}

	Image img = LoadImage( im->path->s );
	if ( img.data == NULL )
	{
		fprintf( stderr, "Failed to load image %s\n", im->path->s );
		im->hashed = false;
		return;
	}

	ImageResize( &img, ResizedSide64 + 1, ResizedSide64 );
	ImageColorGrayscale(&img);

	im->hash = calloc( 1, sizeof(HASH64BIT) );

	uint8_t * data = img.data;
	for ( unsigned u = 0, i = 0, bt = 0; u < ResizedSide64; ++u )
	{
		for ( unsigned v = 0; v < ResizedSide64; ++v, ++i, ++bt )
			im->hash[0] |= (HASH64BIT)( (unsigned)( data[i] - data[i + 1] ) >> ( sizeof(int) * 8 - 1 ) ) << bt;

		i++;
	}

	UnloadImage(img);
	im->hashed = true;
}
