/******************************************************************************/
/* SlunkCrypt, by LoRd_MuldeR <MuldeR2@GMX.de>                                */
/* This work has been released under the CC0 1.0 Universal license!           */
/******************************************************************************/

/* Internal */
#include "pwgen.h"
#include "utils.h"

/* Library */
#include <slunkcrypt.h>

/* CRT */
#include <ctype.h>
#include <errno.h>

// ==========================================================================
// Constants
// ==========================================================================

static const size_t MIX_ROUNDS = 131U;

static const char PASSWD_SYMBOLS[] =
{
	'!', '#', '$', '%', '&', '(', ')', '*', '+', ',', '-', '.', '/', '0', '1',
	'2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@',
	'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
	'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', ']', '^', '_',
	'a', 'b', 'c', 'd',	'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
	'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~'
};

// ==========================================================================
// Read passphrase from file
// ==========================================================================

#define PASSPHRASE_BUFFSIZE (SLUNKCRYPT_PWDLEN_MAX + 2U)

static void trim_end_of_line(char *const buffer)
{
	size_t length;
	for (length = strlen(buffer); length > 0U; --length)
	{
		const char last = buffer[length - 1U];
		if ((last != '\r') && (last != '\n') && (last != '\f'))
		{
			break;
		}
	}
	buffer[length] = '\0';
}

char *read_passphrase(const CHR *const file_name)
{
	FILE *passphrase_file = NULL;
	char *buffer = NULL;

	if ((!file_name) || (!file_name[0U]))
	{
		FPUTS(T("Error: The passphrase input file name must not be empty!\n\n"), stderr);
		goto finish;
	}

	passphrase_file = STRICMP(file_name, T("-")) ? FOPEN(file_name, "rb") : stdin;
	if (!passphrase_file)
	{
		FPRINTF(stderr, T("Error: Failed to open passphrase file \"%") T(PRISTR) T("\" for reading!\n\n%") T(PRISTR) T("\n\n"), file_name, STRERROR(errno));
		goto finish;
	}

	buffer = (char*) calloc(PASSPHRASE_BUFFSIZE, sizeof(char));
	if (!buffer)
	{
		FPUTS(T("Error: Failed to allocate the passphrase buffer!\n\n"), stderr);
		goto finish;
	}

	do
	{
		if (!fgets(buffer, (int)PASSPHRASE_BUFFSIZE, passphrase_file))
		{
			buffer[0U] = '\0';
			goto finish;
		}
		trim_end_of_line(buffer);
	}
	while (!buffer[0U]);

finish:

	if (passphrase_file && (passphrase_file != stdin))
	{
		fclose(passphrase_file);
	}

	return buffer;
}

// ==========================================================================
// Passphrase generator
// ==========================================================================

#define NEXT_RANDOM(OUTVAR,LIMIT) do \
{ \
	uint64_t _rnd; \
	if (slunkcrypt_generate_nonce(&_rnd) != SLUNKCRYPT_SUCCESS) \
	{ \
		FPUTS(T("Error: Failed to generate next random number!\n\n"), stderr); \
		goto clean_up; \
	} \
	OUTVAR = (size_t) (_rnd % (LIMIT)); \
} \
while (0)

int weak_passphrase(const char *str)
{
	unsigned int flags = 0U;
	while ((*str) && (flags != 0xF))
	{
		const int c = *str++;
		if (isalpha(c))
		{
			flags |= isupper(c) ? 0x1 : 0x2;
		}
		else
		{
			flags |= isdigit(c) ? 0x4 : 0x8;
		}
	}
	return (flags != 0xF);
}

int generate_passphrase(const size_t length)
{
	int result = EXIT_FAILURE;
	char *buffer = NULL, *passwd_symbols = NULL;
	size_t i, j, n, m;
	const size_t passwd_len = BOUND(SLUNKCRYPT_PWDLEN_MIN, length, SLUNKCRYPT_PWDLEN_MAX);

	if (!(buffer = (char*) malloc((passwd_len + 1U) * sizeof(char))))
	{
		FPUTS(T("Error: Failed to allocate memory buffer!\n\n"), stderr);
		goto clean_up;
	}

	if (!(passwd_symbols = (char*) malloc(ARRAY_SIZE(PASSWD_SYMBOLS) * sizeof(char))))
	{
		FPUTS(T("Error: Failed to allocate memory buffer!\n\n"), stderr);
		goto clean_up;
	}

	for (i = 0U; i < ARRAY_SIZE(PASSWD_SYMBOLS); ++i)
	{
		NEXT_RANDOM(j, i + 1U);
		if (j != i)
		{
			passwd_symbols[i] = passwd_symbols[j];
		}
		passwd_symbols[j] = PASSWD_SYMBOLS[i];
	}

	do
	{
		for (n = 0U; n < passwd_len; ++n)
		{
			for (m = 0U; m < MIX_ROUNDS; ++m)
			{
				for (i = ARRAY_SIZE(PASSWD_SYMBOLS) - 1U; i > 0; --i)
				{
					NEXT_RANDOM(j, i + 1U);
					if (j != i)
					{
						const char tmpval = passwd_symbols[i];
						passwd_symbols[i] = passwd_symbols[j];
						passwd_symbols[j] = tmpval;
					}
				}
			}
			buffer[n] = passwd_symbols[0U];
		}
		buffer[passwd_len] = '\0';
	}
	while ((!isalnum((int)buffer[0U])) || (!isalnum((int)buffer[passwd_len - 1U])) || weak_passphrase(buffer));

	FPRINTF(stdout, T("%") T(PRIstr) T("\n\n"), buffer);
	fflush(stdout);
	result = EXIT_SUCCESS;

clean_up:

	if (buffer)
	{
		slunkcrypt_bzero(buffer, passwd_len * sizeof(char));
		free(buffer);
	}

	if (passwd_symbols)
	{
		slunkcrypt_bzero(passwd_symbols, ARRAY_SIZE(PASSWD_SYMBOLS) * sizeof(char));
		free(passwd_symbols);
	}

	return result;
}
