#include "global-state.h"
#include "gui-loop.h"
#include "file-loading.h"
#include "data-structure.h"
#include "incbin.h"
#include "ui-layout.h"
#include "ui-strings.h"
#include "ui-style.h"
#include "raylib-tools.h"
#include "fileutils.h"

#pragma GCC diagnostic ignored "-Wunused-result"
#define RAYGUI_IMPLEMENTATION
#define RAYGUI_SUPPORT_ICONS
#include "raygui.h"
#pragma GCC diagnostic pop

typedef struct SortingImage
{
	unsigned grpIter;
	Imagefile * im;
	Texture2D tex;
	Vector2 texPos;
	Image img;
	Color colres;
	Color colsize;
	Color colftype;
	long fsize;
	long resmult;
	string * gid;
	string * fsizestr;
	string * res;
	string * fname;
	string * fnamefull;
	int ftype;
	char * ftypelong;
	bool filled;
} SortingImage;

typedef struct SortingData
{
	UILayout lay;
	SortingImage left;
	SortingImage right;
	ImageGroup * group;
	unsigned groupSize;
	bool waitingForInput;
	bool imagesReady;
} SortingData;

enum FTypeWeights
{
	BMP_WEIGHT = 0,
	JPG_WEIGHT = 1,
	PNG_WEIGHT = 2,
	GIF_WEIGHT = 3
};

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

static Image CheckersImage;
static Texture2D Checkers;
static Font Font_def;
static SortingData SortUi;
static string * ScreenLayoutsStr;

static char * PUG_long_ftype ( int ftype );
static void PUG_after_processing ( void );
static void PUG_fill_image ( SortingImage * si );
static void PUG_fit_image_into ( Image * img, int w, int h );
static void PUG_free_sorting_image ( SortingImage * si );
static void PUG_get_a_pair ( void );
static void PUG_init_sorting_image ( SortingImage * si );
static void PUG_inloopdw_processing ( void );
static void PUG_inloopdw_ready ( void );
static void PUG_inloopdw_sorting ( void );
static void PUG_inloopev_processing ( void );
static void PUG_inloopev_ready ( void );
static void PUG_inloopev_sorting ( void );
static void PUG_iterate_in_group ( void );
static void PUG_loop_drawing ( void );
static void PUG_loop_events ( void );
static void PUG_next_group ( void );
static void PUG_on_button_apply_options ( void );
static void PUG_on_button_del_both ( void );
static void PUG_on_button_del_left ( void );
static void PUG_on_button_del_right ( void );
static void PUG_on_button_launch ( void );
static void PUG_on_button_next ( void );
static void PUG_on_button_reset ( void );
static void PUG_on_button_stop_sorting ( void );
static void PUG_on_exhaust ( void );
static void PUG_request_refill ( void );
static void PUG_unload_a_pair ( void );
static void PUG_update_group_stats ( void );
static void PUG_validate_iteration ( void );

static char *
PUG_long_ftype ( int ftype )
{
	switch ( ftype )
	{
		default:
			return "???";
		case BMP_WEIGHT:
			return "BMP";
		case JPG_WEIGHT:
			return "JPG";
		case PNG_WEIGHT:
			return "PNG";
		case GIF_WEIGHT:
			return "GIF";
	}
}

static void
PUG_on_exhaust ( void )
{
	printf("That's all folks!\n");

	SortUi.group = NULL;

	PUG_unload_a_pair();
	PUDS_Dispose();
	Ui.stage = STAGE_READY;
	Ui.progress = 0;
	SU_set( Ui.dirPathCount, UiLineDragnDrop_d, LoadedFiles.dirs.length );
	SU_set( Ui.directoryPaths, "" );
	SU_set( Ui.statusline, UiLineStatusOnreset );
}

static void
PUG_request_refill ( void )
{
	if ( PUDS_Get_valid_group_entries( SortUi.group, SortUi.left.grpIter, SortUi.right.grpIter ) == 0 )
	{
		PUG_next_group();
		return;
	}

	PUG_iterate_in_group();
}

static void
PUG_update_group_stats ( void )
{
	PUFL_Refresh_counters();

	if ( SortUi.group == NULL )
		return;

	SU_set( Ui.totalDupes, "%lu", LoadedFiles.dupenum );
	SU_set( Ui.groupCount, "%lu", LoadedFiles.groups.length );
	SU_set( Ui.groupSize, "%lu", SortUi.groupSize );
}

static void
PUG_unload_a_pair ( void )
{
	if ( !SortUi.imagesReady )
		return;

	SortUi.imagesReady = false;
	SortUi.waitingForInput = false;

	UnloadImage(SortUi.left.img);
	UnloadImage(SortUi.right.img);

	UnloadTexture(SortUi.left.tex);
	UnloadTexture(SortUi.right.tex);
}

static void
PUG_validate_iteration ( void )
{
	if ( SortUi.group->images.data[SortUi.left.grpIter]->disposed )
	{
		PUG_iterate_in_group();
		return;
	}

	if ( SortUi.group->images.data[SortUi.right.grpIter]->disposed )
	{
		PUG_iterate_in_group();
		return;
	}

	PUG_get_a_pair();
}

static void
PUG_iterate_in_group ( void )
{
	if ( SortUi.right.grpIter + 1 < SortUi.groupSize )
	{
		SortUi.right.grpIter++;
		PUG_validate_iteration();
		return;
	}

	if ( SortUi.left.grpIter + 2 < SortUi.groupSize )
	{
		SortUi.left.grpIter++;
		SortUi.right.grpIter = SortUi.left.grpIter + 1;
		PUG_validate_iteration();
		return;
	}

	PUG_next_group();
}

static void
PUG_free_sorting_image ( SortingImage * si )
{
	SU_free(si->fname);
	SU_free(si->fsizestr);
	SU_free(si->res);
	SU_free(si->gid);
	SU_free(si->fnamefull);
}

static void
PUG_init_sorting_image ( SortingImage * si )
{
	si->fname = SU_new(SortUi.lay.shortnamelen);
	si->fnamefull = SU_new(SU_MAX_PATH_LENGTH);
	si->fsizestr = SU_new(SU_MAX_PATH_LENGTH);
	si->res = SU_new(64);
	si->gid = SU_new(64);
}

static void
PUG_fill_image ( SortingImage * si )
{
	si->filled = false;
	si->im = SortUi.group->images.data[si->grpIter];

	if ( !FU_FileExists(si->im->path->s) )
	{
		fprintf( stderr, "File %s is missing, skipping...\n", si->im->path->s );
		return;
	}

//	si->fsize = PUFL_Get_file_size(si->im->path->s);
	si->fsize = FU_FileSize(si->im->path->s);

	si->img = LoadImage(si->im->path->s);
	SU_set( si->res, UiLineSortImgMetaRes_2lu, si->img.width, si->img.height );
	si->resmult = si->img.width * si->img.height;
	PUG_fit_image_into( &si->img, SortUi.lay.textureW, SortUi.lay.textureH );
	si->tex = LoadTextureFromImage(si->img);

	char ftype = GetFileExtension(si->im->path->s)[1];
	switch ( ftype )
	{
		default:
			si->ftype = 999;
			break;
		case 'j':
		case 'J':
			si->ftype = JPG_WEIGHT;
			break;
		case 'p':
		case 'P':
			si->ftype = PNG_WEIGHT;
			break;
		case 'g':
		case 'G':
			si->ftype = GIF_WEIGHT;
			break;
	}

	si->ftypelong = PUG_long_ftype(si->ftype);

	SU_set( si->fnamefull, "%s", GetFileName(si->im->path->s) );
	SU_fit_with_trunk( si->fname, si->fnamefull, "[.]" );
	SU_set( si->fsizestr, UiLineSortImgMetaFsize_f, si->fsize / (float)1024 );
	SU_set( si->gid, UiLineSortImgMetaIndex_u, si->grpIter );

	si->filled = true;
}

static void
PUG_get_a_pair ( void )
{
	PUG_unload_a_pair();

	if ( SortUi.group == NULL )
	{
		fputs( "PUG_get_a_pair(): group is NULL!", stderr );
		exit(EXIT_FAILURE);
	}

	PUG_fill_image(&SortUi.left);
	PUG_fill_image(&SortUi.right);

	if ( !SortUi.left.filled || !SortUi.right.filled )
	{
		PUG_request_refill();
		return;
	}

	SU_set( Ui.fullPaths, "<:%s\n>:%s", SortUi.left.im->path->s, SortUi.right.im->path->s );

	long diffsize = SortUi.left.fsize - SortUi.right.fsize;
	if ( !Ui.biggerIsBetter )
		diffsize *= -1;

	if ( diffsize == 0 )
	{
		SortUi.left.colsize = LIGHTGRAY;
		SortUi.right.colsize = LIGHTGRAY;
	}
	else
	{
		SortUi.left.colsize = ( diffsize > 0 ) ? StyleDefault.compGood : StyleDefault.compBad;
		SortUi.right.colsize = ( diffsize > 0 ) ? StyleDefault.compBad : StyleDefault.compGood;
	}

	long diffres = SortUi.left.resmult - SortUi.right.resmult;
	if ( diffres == 0 )
	{
		SortUi.left.colres = LIGHTGRAY;
		SortUi.right.colres = LIGHTGRAY;
	}
	else
	{
		SortUi.left.colres = ( diffres > 0 ) ? StyleDefault.compGood : StyleDefault.compBad;
		SortUi.right.colres = ( diffres > 0 ) ? StyleDefault.compBad : StyleDefault.compGood;
	}

	int diffftype = SortUi.left.ftype - SortUi.right.ftype;
	if ( diffftype == 0 )
	{
		SortUi.left.colftype = GRAY;
		SortUi.right.colftype = GRAY;
	}
	else
	{
		SortUi.left.colftype = ( diffftype > 0 ) ? StyleDefault.compGood : StyleDefault.compBad;
		SortUi.right.colftype = ( diffftype > 0 ) ? StyleDefault.compBad : StyleDefault.compGood;
	}

	PUG_update_group_stats();

	SortUi.left.texPos.x = SortUi.lay.leftImage.x + ( SortUi.lay.textureW - SortUi.left.img.width ) / 2;
	SortUi.left.texPos.y = SortUi.lay.leftImage.y + ( SortUi.lay.textureH - SortUi.left.img.height ) / 2;
	SortUi.right.texPos.x = SortUi.lay.rightImage.x + ( SortUi.lay.textureW - SortUi.right.img.width ) / 2;
	SortUi.right.texPos.y = SortUi.lay.rightImage.y + ( SortUi.lay.textureH - SortUi.right.img.height ) / 2;

	SortUi.imagesReady = true;
	SortUi.waitingForInput = true;
}

static void
PUG_after_processing ( void )
{
	PUG_request_refill();
}

static void
PUG_next_group ( void )
{
	if ( SortUi.group != NULL )
	{
		PUDS_Remove_group(SortUi.group);
		SortUi.group = NULL;
	}

	if ( LoadedFiles.groups.length == 0 )
	{
		PUG_on_exhaust();
		return;
	}

	SortUi.group = vec_last(&LoadedFiles.groups);

	SortUi.left.grpIter = 0;
	SortUi.right.grpIter = 0;
	SortUi.groupSize = SortUi.group->images.length;
	PUG_request_refill();
}

static void
PUG_on_button_pause ( void )
{
	Ui.threadPaused = !Ui.threadPaused;
}

static void
PUG_on_button_apply_options ( void )
{
	LAYOUTSDEF;

	Ui.buttonApplyOptions = false;
	memcpy( &SortUi.lay, &(AvailableLayouts[Ui.resolution]), sizeof(UILayout) );
	printf( "Applied! res %d: %s\n", Ui.resolution, AvailableLayouts[Ui.resolution].layoutname );
	SetWindowSize( SortUi.lay.screenWidth, SortUi.lay.screenHeight );

	int mon = GetCurrentMonitor();
	int monw = GetMonitorWidth(mon);
	int monh = GetMonitorHeight(mon);

	SetWindowPosition( ( monw - SortUi.lay.screenWidth ) / 2, ( monh - SortUi.lay.screenHeight ) / 2 );
}

static void
PUG_on_button_del_both ( void )
{
	Ui.buttonDelBoth = false;
	if ( !SortUi.waitingForInput )
		return;

	PUFL_File_delete(SortUi.left.im);
	PUFL_File_delete(SortUi.right.im);
	PUG_request_refill();
}

static void
PUG_on_button_del_left ( void )
{
	Ui.buttonDelLeft = false;
	if ( !SortUi.waitingForInput )
		return;

	PUFL_File_delete(SortUi.left.im);
	PUG_request_refill();
}

static void
PUG_on_button_del_right ( void )
{
	Ui.buttonDelRight = false;
	if ( !SortUi.waitingForInput )
		return;

	PUFL_File_delete(SortUi.right.im);
	PUG_request_refill();
}

static void
PUG_on_button_next ( void )
{
	Ui.buttonNext = false;
	if ( !SortUi.waitingForInput )
		return;

	PUG_request_refill();
}

static void
PUG_on_button_launch ( void )
{
	Ui.buttonLaunch = false;

	if ( LoadedFiles.dirs.length == 0 )
	{
		SU_set( Ui.statusline, "Aborting launch because there are no directories added!" );
		return;
	}

	SU_set( Ui.statusline, "Launch!" );
	PUFL_Process_directories();

	Ui.stage = STAGE_PROCESSING;
	Ui.processingStage = STAGE_PROC_NONE;
}

static void
PUG_on_button_reset ( void )
{
	Ui.buttonReset = false;

	PUDS_Reset_directories();

	SU_set( Ui.dirPathCount, UiLineDragnDrop_d, LoadedFiles.dirs.length );
	SU_set( Ui.directoryPaths, "" );
	SU_set( Ui.statusline, "Directory list reset" );
	Ui.progress = 0;
}

static void
PUG_on_button_stop_sorting ( void )
{
	Ui.buttonStopSorting = false;
	if ( !SortUi.waitingForInput )
		return;

	SortUi.group = NULL;

	PUDS_Dispose();
	Ui.stage = STAGE_READY;
	PUG_on_button_reset();
}

static void
PUG_inloopev_processing ( void )
{
	switch ( Ui.processingStage )
	{
		default:
		case STAGE_PROC_NONE:
		case STAGE_PROC_FINISHED:
			SU_set( Ui.processingExplained, "" );
			break;
		case STAGE_PROC_SCANNING:
			SU_set( Ui.processingExplained, "SCANNING" );
			break;
		case STAGE_PROC_VALIDATING:
			SU_set( Ui.processingExplained, "VALIDATING IMAGE ENTRIES" );
			break;
		case STAGE_PROC_HASHD64:
			SU_set( Ui.processingExplained, "MAIN HASHING DHASH64 STAGE" );
			break;
		case STAGE_PROC_HASHD256:
			SU_set( Ui.processingExplained, "EXTRA HASHING DHASH256 STAGE" );
			break;
		case STAGE_PROC_COMPARED64:
			SU_set( Ui.processingExplained, "COMPARE DHASH64 HASHES" );
			break;
		case STAGE_PROC_COMPARED256:
			SU_set( Ui.processingExplained, "COMPARE DHASH256 HASHES" );
			break;
		case STAGE_PROC_DECOUPLING:
			SU_set( Ui.processingExplained, "PREPARING FOR EXTRA STAGE" );
			break;
	}

	switch ( Ui.processingStage )
	{
		default:
		case STAGE_PROC_NONE:
			break;
		case STAGE_PROC_SCANNING:
			SU_set( Ui.statusline, "Found image files: %lu", LoadedFiles.images.length );
			Ui.progress = 0;
			break;
		case STAGE_PROC_VALIDATING:
			SU_set( Ui.statusline, "Validating files..." );
			break;
		case STAGE_PROC_HASHD64:
		case STAGE_PROC_HASHD256:
			SU_set( Ui.statusline, "Hashed image files: %lu/%lu, invalid image files: %lu", LoadedFiles.hashedfiles, LoadedFiles.images.length, LoadedFiles.failedfiles );
			Ui.progress = (float)LoadedFiles.hashedfiles / LoadedFiles.images.length;
			break;
		case STAGE_PROC_COMPARED64:
		case STAGE_PROC_COMPARED256:
			SU_set( Ui.statusline, "Comparing hashes, %lu/%lu files left, created %lu groups", LoadedFiles.compareiter, LoadedFiles.hashedfiles, LoadedFiles.groups.length );
			Ui.progress = (float)LoadedFiles.compareiter / LoadedFiles.hashedfiles;
			break;
		case STAGE_PROC_DECOUPLING:
			SU_set( Ui.statusline, "Decoupling groups and images for extra hashing step..." );
			Ui.progress = 0;
			break;
		case STAGE_PROC_FINISHED:
			PUFL_Refresh_counters();
			SU_set( Ui.statusline, "Processing finished, found %lu groups with %lu similar files", LoadedFiles.groups.length, LoadedFiles.dupenum );
			Ui.progress = 1;
			Ui.stage = STAGE_SORTING;
			PUG_after_processing();
			break;
	}

	if ( Ui.buttonPause )
		PUG_on_button_pause();
}

static void
PUG_inloopev_ready ( void )
{
	if ( IsFileDropped() )
	{
		int count;
		char ** dropped = GetDroppedFiles(&count);
		for ( int i = 0; i < count; ++i )
		{
			if ( dropped[i] == NULL )
			{
				SU_set( Ui.statusline, "NULL pointer" );
				continue;
			}

			if ( !DirectoryExists(dropped[i]) )
			{
				SU_set( Ui.statusline, "Not a directory: %s", dropped[i] );
				continue;
			}

			if ( PUFL_Add_directory(dropped[i]) )
				SU_set( Ui.statusline, "Added directory: %s", dropped[i] );
			else
				SU_set( Ui.statusline, "Failed to add directory!" );

			SU_set( Ui.dirPathCount, UiLineDragnDrop_d, LoadedFiles.dirs.length );
			SU_set( Ui.directoryPaths, "" );
			if ( LoadedFiles.dirs.length > 0 )
				for ( int i = 0; i < LoadedFiles.dirs.length; ++i )
					SU_cat( Ui.directoryPaths, "%s; ", LoadedFiles.dirs.data[i]->s );
		}

		ClearDroppedFiles();
	}

	if ( Ui.buttonLaunch )
		PUG_on_button_launch();

	if ( Ui.buttonReset )
		PUG_on_button_reset();

	if ( Ui.buttonApplyOptions )
		PUG_on_button_apply_options();
}

static void
PUG_on_button_mouse_middle ( void )
{
	if ( !SortUi.waitingForInput )
		return;

	Vector2 pos = GetMousePosition();

	if ( IsInRect( SortUi.lay.leftImage, pos ) )
	{
		SetClipboardText(SortUi.left.im->path->s);
		puts("Copied filename to clipboard");
	}

	if ( IsInRect( SortUi.lay.rightImage, pos ) )
	{
		SetClipboardText(SortUi.right.im->path->s);
		puts("Copied filename to clipboard");
	}
}

static void
PUG_inloopev_sorting ( void )
{
	if ( Ui.buttonDelLeft || IsKeyPressed(KEY_KP_7) || IsKeyPressed(KEY_ONE) )
		PUG_on_button_del_left();

	if ( Ui.buttonDelRight || IsKeyPressed(KEY_KP_9) || IsKeyPressed(KEY_THREE) )
		PUG_on_button_del_right();

	if ( Ui.buttonDelBoth || IsKeyPressed(KEY_KP_DIVIDE) || IsKeyPressed(KEY_TWO) )
		PUG_on_button_del_both();

	if ( Ui.buttonStopSorting )
		PUG_on_button_stop_sorting();

	if ( Ui.buttonNext || IsKeyPressed(KEY_KP_0) || IsKeyPressed(KEY_SPACE) )
		PUG_on_button_next();

	if ( IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON) )
		PUG_on_button_mouse_middle();
}

static void
PUG_loop_events ( void )
{
	if ( IsKeyPressed(KEY_F1) )
		Ui.helpScreen = !Ui.helpScreen;

	switch ( Ui.stage )
	{
		default:
		case STAGE_NONE:
			break;
		case STAGE_READY:
			PUG_inloopev_ready();
			break;
		case STAGE_PROCESSING:
			PUG_inloopev_processing();
			break;
		case STAGE_SORTING:
			PUG_inloopev_sorting();
			break;
	}
}

static void
PUG_inloopdw_processing ( void )
{
	GuiProgressBar( SortUi.lay.progressbar, "", "", Ui.progress, 0, 1 );
	GuiDrawText( Ui.processingExplained->s, SortUi.lay.progreessdesc, GUI_TEXT_ALIGN_CENTER, StyleDefault.text );
	Ui.buttonPause = GuiButton( SortUi.lay.buttonPause, Ui.threadPaused ? "RESUME" : "PAUSE" );
}

static void
PUG_inloopdw_ready ( void )
{
	SU_set( Ui.threshString, "%.2f  ", Ui.threshSlider );

	Ui.buttonLaunch = GuiButton( SortUi.lay.buttonLaunch, GuiIconText( RICON_PLAYER_PLAY, "LAUNCH" ) );
	Ui.buttonReset = GuiButton( SortUi.lay.buttonReset, GuiIconText( RICON_REPEAT, "RESET PATHS" ) );

	GuiDrawText( Ui.dirPathCount->s, SortUi.lay.dragndropprompt, GUI_TEXT_ALIGN_CENTER, StyleDefault.text );
	GuiTextBoxMulti( SortUi.lay.dragndropbox, Ui.directoryPaths->s, 15, false );

	/* Options group */
	GuiGroupBox( SortUi.lay.optiongroup, "OPTIONS" );
	ProcOptions.recursive = Ui.recursive = GuiCheckBox( SortUi.lay.optionrecursive, "RECURSIVE", Ui.recursive );
	ProcOptions.extraPassD256 = Ui.extraPassD256 = GuiCheckBox( SortUi.lay.optiondhash256, "EXTRA PASS (DHASH 256bit)", Ui.extraPassD256 );
	Ui.biggerIsBetter = GuiCheckBox( SortUi.lay.optionbiggerbetter, "BIGGER FILE IS BETTER", Ui.biggerIsBetter );
	ProcOptions.threshold = Ui.threshSlider = GuiSliderBar( SortUi.lay.optionthreshold, NULL, NULL, Ui.threshSlider, .7, 1 );
	GuiDrawText( "  MATCH THRESHOLD:", SortUi.lay.optionthreshold, GUI_TEXT_ALIGN_LEFT, StyleDefault.text );
	GuiDrawText( Ui.threshString->s, SortUi.lay.optionthreshold, GUI_TEXT_ALIGN_RIGHT, StyleDefault.text );
	Ui.resolution = GuiComboBox( SortUi.lay.optionresolution, ScreenLayoutsStr->s, Ui.resolution );
	Ui.buttonApplyOptions = GuiButton( SortUi.lay.optionapply, "APPLY" );
	GuiSpinner( SortUi.lay.optionthreads, "THREADS: ", &ProcOptions.threads, 1, MAX_HASH_THREADS, false );
}

static void
PUG_inloopdw_sorting ( void )
{
	/* Textures */
	DrawTextureRec( Checkers, SortUi.lay.leftImage, (Vector2){ SortUi.lay.leftImage.x, SortUi.lay.leftImage.y }, WHITE );
	DrawTextureRec( Checkers, SortUi.lay.rightImage, (Vector2){ SortUi.lay.rightImage.x, SortUi.lay.rightImage.y }, WHITE );

	if ( SortUi.imagesReady )
	{
		DrawTexture( SortUi.left.tex, SortUi.left.texPos.x, SortUi.left.texPos.y, WHITE );
		DrawTexture( SortUi.right.tex, SortUi.right.texPos.x, SortUi.right.texPos.y, WHITE );

		/* Metadata */
		GuiDrawText( SortUi.left.res->s, SortUi.lay.leftTopMeta, GUI_TEXT_ALIGN_LEFT, StyleDefault.text );
		GuiDrawText( SortUi.left.gid->s, SortUi.lay.leftTopMeta, GUI_TEXT_ALIGN_CENTER, StyleDefault.text );
		GuiDrawText( SortUi.left.fsizestr->s, SortUi.lay.leftTopMeta, GUI_TEXT_ALIGN_RIGHT, StyleDefault.text );
		GuiDrawText( SortUi.left.fname->s, SortUi.lay.leftBottomMeta, GUI_TEXT_ALIGN_LEFT, StyleDefault.text );

		GuiDrawText( SortUi.right.res->s, SortUi.lay.rightTopMeta, GUI_TEXT_ALIGN_LEFT, StyleDefault.text );
		GuiDrawText( SortUi.right.gid->s, SortUi.lay.rightTopMeta, GUI_TEXT_ALIGN_CENTER, StyleDefault.text );
		GuiDrawText( SortUi.right.fsizestr->s, SortUi.lay.rightTopMeta, GUI_TEXT_ALIGN_RIGHT, StyleDefault.text );
		GuiDrawText( SortUi.right.fname->s, SortUi.lay.rightBottomMeta, GUI_TEXT_ALIGN_RIGHT, StyleDefault.text );

		/* Comparison */
		GuiDrawText( "IMAGE RESOLUTION", SortUi.lay.compareRes, GUI_TEXT_ALIGN_CENTER, StyleDefault.text );
		DrawRectangleRec( SortUi.lay.compareResLeft, SortUi.left.colres );
		DrawRectangleRec( SortUi.lay.compareResRight, SortUi.right.colres );
		GuiDrawText( GuiIconText( RICON_CURSOR_SCALE_RIGHT_FILL, "" ), SortUi.lay.compareResLeft, GUI_TEXT_ALIGN_CENTER, BLACK );
		GuiDrawText( GuiIconText( RICON_CURSOR_SCALE_RIGHT_FILL, "" ), SortUi.lay.compareResRight, GUI_TEXT_ALIGN_CENTER, BLACK );

		GuiDrawText( "FILE SIZE", SortUi.lay.compareFsize, GUI_TEXT_ALIGN_CENTER, StyleDefault.text );
		DrawRectangleRec( SortUi.lay.compareFsizeLeft, SortUi.left.colsize );
		DrawRectangleRec( SortUi.lay.compareFsizeRight, SortUi.right.colsize );
		GuiDrawText( GuiIconText( RICON_CUBE, "" ), SortUi.lay.compareFsizeLeft, GUI_TEXT_ALIGN_CENTER, BLACK );
		GuiDrawText( GuiIconText( RICON_CUBE, "" ), SortUi.lay.compareFsizeRight, GUI_TEXT_ALIGN_CENTER, BLACK );

		GuiDrawText( "FILE TYPE", SortUi.lay.compareFtype, GUI_TEXT_ALIGN_CENTER, StyleDefault.text );
		GuiDrawText( PUG_long_ftype(SortUi.left.ftype), SortUi.lay.compareFtype, GUI_TEXT_ALIGN_LEFT, SortUi.left.colftype );
		GuiDrawText( PUG_long_ftype(SortUi.right.ftype), SortUi.lay.compareFtype, GUI_TEXT_ALIGN_RIGHT, SortUi.right.colftype );

		GuiTextBoxMulti( SortUi.lay.imagepaths, Ui.fullPaths->s, 12, false );

		/* Middle buttons */
		Ui.buttonDelLeft = GuiButton( SortUi.lay.buttonDelLeft, UiLineButtonDelLeft );
		Ui.buttonDelRight = GuiButton( SortUi.lay.buttonDelRight, UiLineButtonDelRight );
		Ui.buttonDelBoth = GuiButton(  SortUi.lay.buttonDelBoth, UiLineButtonDelBoth );
		Ui.buttonNext = GuiButton(  SortUi.lay.buttonNext, UiLineButtonNext );

		Ui.buttonStopSorting = GuiButton( SortUi.lay.buttonStopSorting, GuiIconText( RICON_UNDO, UiLineButtonBackToTitle ) );
	}


	/* Statistics */
	GuiDrawText( UiLineSortStatsGroupsize, SortUi.lay.statsGroupSize, GUI_TEXT_ALIGN_LEFT, StyleDefault.text );
	GuiDrawText( Ui.groupSize->s, SortUi.lay.statsGroupSize, GUI_TEXT_ALIGN_RIGHT, StyleDefault.text );
	GuiDrawText( UiLineSortStatsGroupcount, SortUi.lay.statsGroupCount, GUI_TEXT_ALIGN_LEFT, StyleDefault.text );
	GuiDrawText( Ui.groupCount->s, SortUi.lay.statsGroupCount, GUI_TEXT_ALIGN_RIGHT, StyleDefault.text );
	GuiDrawText( UiLineSortStatsTotaldupes, SortUi.lay.statsTotalDupes, GUI_TEXT_ALIGN_LEFT, StyleDefault.text );
	GuiDrawText( Ui.totalDupes->s, SortUi.lay.statsTotalDupes, GUI_TEXT_ALIGN_RIGHT, StyleDefault.text );
}

static void
PUG_loop_drawing ( void )
{
	ClearBackground(RAYWHITE);

	GuiStatusBar( SortUi.lay.statusbar, Ui.statusline->s );
	GuiLine( SortUi.lay.line, NULL );
	GuiLabel( SortUi.lay.helphint, GuiIconText( RICON_HELP, UiLineHelpHint ) );

	switch ( Ui.stage )
	{
		default:
		case STAGE_NONE:
			break;
		case STAGE_PROCESSING:
			PUG_inloopdw_processing();
			break;
		case STAGE_READY:
			PUG_inloopdw_ready();
			break;
		case STAGE_SORTING:
			PUG_inloopdw_sorting();
	}

	if ( Ui.helpScreen )
	{
		GuiPanel(SortUi.lay.helppanel);
		GuiTextBoxMulti( SortUi.lay.helppanel, UiLineHelpScreen, -1, false );
	}
}

static void
PUG_fit_image_into ( Image * img, int w, int h )
{
	float mult = (float)w / img->width;
	if ( img->height * mult > h )
		mult = (float)h / img->height;

	ImageResize( img, (int)( img->width * mult ), (int)( img->height * mult ) );
}

void
PUG_Init ( void )
{
	LAYOUTSDEF;

	Ui.stage = STAGE_READY;
	Ui.processingStage = STAGE_PROC_NONE;
	Ui.progress = 0;

	Ui.helpScreen = false;
	Ui.windowName = "picunic";

	ScreenLayoutsStr = SU_new(256);
	size_t layoutcount = sizeof(AvailableLayouts) / sizeof(AvailableLayouts[0]);
	for ( size_t i = 0; i < layoutcount; ++i )
		SU_cat( ScreenLayoutsStr, "%s%s", AvailableLayouts[i].layoutname, i + 1 < layoutcount ? ";" : "" );

	Ui.recursive = ProcOptions.recursive;
	Ui.threshSlider = ProcOptions.threshold;
	Ui.extraPassD256 = ProcOptions.extraPassD256;
	Ui.biggerIsBetter = true;
	Ui.resolution = 0;

	Ui.threshString = SU_new(8);
	Ui.directoryPaths = SU_new( 20 * SU_MAX_PATH_LENGTH );
	Ui.dirPathCount = SU_new(128);
	Ui.totalDupes = SU_new(128);
	Ui.groupCount = SU_new(128);
	Ui.groupSize = SU_new(128);
	Ui.processingExplained = SU_new(128);
	Ui.statusline = SU_new(PUG_STATUS_SIZE);
	Ui.fullPaths = SU_new( 2 * SU_MAX_PATH_LENGTH + 128 );

	SetConfigFlags( FLAG_VSYNC_HINT );
	InitWindow( 1, 1, Ui.windowName );
	PUG_on_button_apply_options();

	PUG_init_sorting_image(&SortUi.left);
	PUG_init_sorting_image(&SortUi.right);

	INCBIN( FontUni, "resources/font.ttf" );
	(void)gFontUniEnd;
	Font_def = LoadFontFromMemory( ".ttf", gFontUniData, gFontUniSize, 12, 0, 10000 );
	GuiSetFont(Font_def);

	GuiSetStyle(TEXTBOX, BORDER_WIDTH, 0);

	CheckersImage = GenImageChecked( 16, 16, 8, 8, GRAY, DARKGRAY );
	Checkers = LoadTextureFromImage(CheckersImage);

	SU_set( Ui.statusline, UiLineStatusReady );
	SU_set( Ui.dirPathCount, UiLineDragnDrop_d, 0 );
}

void
PUG_Loop ( void )
{
	while ( !WindowShouldClose() )
	{
		PUG_loop_events();

		BeginDrawing();
		PUG_loop_drawing();
		EndDrawing();
	}
}

void
PUG_Close ( void )
{
	UnloadTexture(Checkers);
	UnloadImage(CheckersImage);
	UnloadFont(Font_def);

	PUG_unload_a_pair();
	PUG_free_sorting_image(&SortUi.left);
	PUG_free_sorting_image(&SortUi.right);

	CloseWindow();

	SU_free(Ui.threshString);
	SU_free(Ui.directoryPaths);
	SU_free(Ui.dirPathCount);
	SU_free(Ui.totalDupes);
	SU_free(Ui.groupCount);
	SU_free(Ui.groupSize);
	SU_free(Ui.processingExplained);
	SU_free(Ui.statusline);
	SU_free(Ui.fullPaths);
	SU_free(ScreenLayoutsStr);
}
