/*'***************************************************************************
*   G0ETP WSPR encoder project
*
*   Written by G0ETP from first principles, based on WSPR_Coding_Process.pdf
*   by G4JNT
*
*       File:           wspr_g0etp_main.c
*
*       Parse command line params, build WSPR message and generate 16bit audio
*       samples via STDOUT
*
*       Pipe samples to audio device in real time with:
*       	wspr_g0etp -c g0etp -l jo02 -p 20 | aplay -r12000 -traw -c1 -fS16_LE
*
*****************************************************************************/

/*'***************************************************************************
 * INCLUDES
 ****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <getopt.h>
#include <stdbool.h>
#include <math.h>
#include <time.h>



/*'***************************************************************************
 * GLOBAL VARIABLES
 ****************************************************************************/

/*'***************************************************************************
 * PRIVATE VARIABLES
 ****************************************************************************/
#define SPLASH_STRING	"WSPR 4AFSK keyer for Linux V1.00, G0ETP April 2024\n"
#define SAMPRATE		12000
#define SYMBOL_SAMPS	8192
#define CARRIER_CENT	1500

static uint8_t hashed_data[11];		// 81 input bits (50 payload + 31 encoder tail bits)
static uint8_t conv_enc_data[21];	// 162 conv encoded bits
static uint8_t wspr_data[41];		// 162 2-bit WSPR symbols


/*'***************************************************************************
 * FUNCTION PROTOTYPES
 ****************************************************************************/
static int wspr_process_callsign(char *callsign);
static void wspr_build_payload(char *callsign, char *locator, int power, uint8_t *pData);
static void wspr_conv_encoder(uint8_t *pInput, uint8_t *pOutput);
static void wspr_interleave_sync(uint8_t *pInput, uint8_t *pOutput);
static void wspr_modulator(uint8_t *pSymbols, int32_t base_freq);
static void generate_tone(float freq);


/*'***************************************************************************
 * GLOBAL FUNCTIONS
 ****************************************************************************/

/**
  * @brief  Program entry point
  * @param  Standard C int argc, char **argv
  * @retval None
  */
int main (int argc, char **argv)
{
    int i;
	int keypress = 0;
	char callsign[8];
	char locator[8];
	int power;
	int freq_dither = 0;
	int centre_freq;
	bool debug = false;
	bool quiet = false;


	// Listen to ctrl+c and ASSERT
	//signal(SIGINT, handle_sig);


    // Parse the command line options
    opterr = 0;
    while ((keypress = getopt(argc, argv, "c:df:l:p:q")) != -1)
    {
        switch (keypress)
        {
            case 'c': // Callsign string
                strncpy(callsign,  optarg, 6+1); // Include NULL term
                break;

            case 'd': // Dump encoded symbol sequence to stderr
            	debug = true;
                break;

            case 'f': // Freq dither span (Rx band spans +-100Hz)
                sscanf(optarg, "%d", &freq_dither);
                if (freq_dither > 90) freq_dither = 90;
                break;

            case 'l': // Locator string
                strncpy(locator,  optarg, 4+1); // Include NULL term
                break;

            case 'p': // Power dBm
                sscanf(optarg, "%d", &power);
                break;

            case 'q': // Suppress chat on stderr
            	quiet = true;
                break;

            default:
                fprintf(stderr,  SPLASH_STRING);
                fprintf(stderr,  "Parameters:\n");
                fprintf(stderr,  "\t-c <callsign>    Up to 6 characters\n");
                fprintf(stderr,  "\t-d               Display encoded WSPR symbol sequence\n");
                fprintf(stderr,  "\t-f <Hz>          Span of random freq dither +- Hz about 1500 centre\n");
                fprintf(stderr,  "\t-l <locator>     Up to 4 characters\n");
                fprintf(stderr,  "\t-p <power>       Tx power in dBm, 0 to 61\n");
                fprintf(stderr,  "\t-q               Suppress text chatter on stdout\n");
                exit(EXIT_FAILURE);
                break;
        }
    }

    // Basic check of parameter ranges
    if (strlen(callsign) < 3)
    {
    	fprintf(stderr,  "Callsign length possibly incorrect. Exiting.\n");
    	exit(EXIT_FAILURE);
    }

    if (strlen(locator) != 4)
    {
    	fprintf(stderr,  "Locator must be 2 letters and 2 numbers. Exiting.\n");
    	exit(EXIT_FAILURE);
    }

    if (power < 0 || power > 60)
    {
    	fprintf(stderr,  "Power must be in range 0 - 60. Exiting.\n");
    	exit(EXIT_FAILURE);
    }


    // Pre-process callsign string
    if (wspr_process_callsign(callsign) != EXIT_SUCCESS)
    {
    	fprintf(stderr,  "Failure to place single callsign numeric at position 3. Exiting.\n");
    	exit(EXIT_FAILURE);
    }

	// Capitalise locator
	for (i=0; i<strlen(locator); i++)
	{
		locator[i] = toupper(locator[i]);
	}


	if (!quiet)
	{
		fprintf(stderr, SPLASH_STRING);

		fprintf(stderr, "Input parameters processed to [%s] [%s] [%d]\n", callsign, locator, power);
	}


	// Clear data arrays
	memset(hashed_data, 0, sizeof(hashed_data));
	memset(conv_enc_data, 0, sizeof(conv_enc_data));
	memset(wspr_data, 0, sizeof(wspr_data));

	// Build the WSPR symbol sequence
	wspr_build_payload(callsign, locator, power, hashed_data);
	wspr_conv_encoder(hashed_data, conv_enc_data);
	wspr_interleave_sync(conv_enc_data, wspr_data);

	if (debug)
	{
		// Print final symbol sequence
		for (i=0; i<162; i++)
		{
			fprintf(stderr, "%d ", (wspr_data[i/4] >> (6 - ((2*i) & 7))) & 3);
		}
		fprintf(stderr, "\n");
	}


	// Generate centre freq with random dither
	if (freq_dither)
	{
		srand((unsigned)time(NULL)); // Seed with current time
		centre_freq = rand() % (2 * freq_dither); // 1-sided
		centre_freq -= freq_dither; // symmetric
		centre_freq += CARRIER_CENT;
	}
	else centre_freq = CARRIER_CENT;

	//if (!quiet)
		fprintf(stderr, "Centre freq %d\n", centre_freq);

	// Output 4FSK samples to STDOUT (for piping to audio output device)
	wspr_modulator(wspr_data, centre_freq);


	return EXIT_SUCCESS;
}


/*'***************************************************************************
 * PRIVATE FUNCTIONS
 ****************************************************************************/

/**
  * @brief	Capitalise callsign, pin numeric digit to position 3 and pad
  *         with spaces
  * @param	-> raw callsign (result via same pointer)
  * @retval EXIT_SUCCESS or EXIT_FAILURE
  */
static int wspr_process_callsign(char *callsign)
{
	int i, num_pos=-1;
	char call_temp[8];


	// Capitalise callsign, make a copy and find numeric digits
	for (i=0; i<strlen(callsign); i++)
	{
		call_temp[i] = toupper(callsign[i]);

		// Remember position of the *last* numeric in the string
		if (isdigit(callsign[i])) num_pos = i;
	}
	call_temp[i] = 0; // Add NULL

	if (num_pos == -1) return EXIT_FAILURE; // No numeric digit found


	// Lock the last numeric digit to string position 3 and pad both ends with spaces
	// to make a length-6 string

	// Start with a fully padded, null terminated string
	memset(callsign, ' ', 6);
	callsign[6] = 0;

	// Paste the callsign chars across with the required offset
	for (i=0; i<strlen(call_temp); i++)
	{
		callsign[i+2-num_pos] = call_temp[i];
	}


	return EXIT_SUCCESS;
}


// Callsign character encoding helper function
static uint8_t call_ch_to_wspr(uint8_t ch)
{
	uint8_t ret;

	if (ch == ' ') ret = 36;
	else if (ch <= '9') ret = ch - '0';
	else if (ch <= 'Z') ret = ch - 55;
	else ret = 0; // Default value on error

	return ret;
}

// Locator character encoding helper function
static uint8_t locator_ch_to_wspr(uint8_t ch)
{
	uint8_t ret;

	if (ch <= '9') ret = ch - '0';
	else if (ch <= 'R') ret = ch - 'A';
	else ret = 0; // Default value on error

	return ret;
}


/**
  * @brief	Generate 50bit WSPR message payload + 31 encoder tail bits
  * @param	-> input data arrays + power
  * 		-> 81 but output array
  * @retval	None
  */
static void wspr_build_payload(char *callsign, char *locator, int power, uint8_t *pData)
{
	uint32_t callsign_hash;
	uint32_t locator_hash;


	// Generate the callsign string hash code
	// This varies per digit so is coded step by step
	callsign_hash = call_ch_to_wspr(callsign[0]);
	callsign_hash *= 36; callsign_hash += call_ch_to_wspr(callsign[1]);
	callsign_hash *= 10; callsign_hash += call_ch_to_wspr(callsign[2]);
	callsign_hash *= 27; callsign_hash += (call_ch_to_wspr(callsign[3]) - 10);
	callsign_hash *= 27; callsign_hash += (call_ch_to_wspr(callsign[4]) - 10);
	callsign_hash *= 27; callsign_hash += (call_ch_to_wspr(callsign[5]) - 10);

	//fprintf(stderr, "Callsign hash = %d\n", callsign_hash);


	// Generate the callsign string hash code
	// This varies per digit so is coded step by step
	locator_hash = 179 - (10 * locator_ch_to_wspr(locator[0]));
	locator_hash -= locator_ch_to_wspr(locator[2]);
	locator_hash *= 180;
	locator_hash += 10 * locator_ch_to_wspr(locator[1]);
	locator_hash += locator_ch_to_wspr(locator[3]);

	//fprintf(stderr, "Locator hash = %d\n", locator_hash);


	// Combine the power with the locator hash
	// TODO: Process power so that illegal powers are not allowed
	locator_hash *= 128;
	locator_hash += power + 64;


	// Bit packing
	// Pack 28 bits from callsign_hash, 22 bits from locator_hash (total 50)
	// and 31 0-padding bits (total now 81) into 11 byte (88 bit) array
	pData[0] = (uint8_t)(callsign_hash >> 20);
	pData[1] = (uint8_t)(callsign_hash >> 12),
	pData[2] = (uint8_t)(callsign_hash >> 4),
	pData[3] = (uint8_t)(((callsign_hash & 0x0f) << 4) | ((locator_hash >> 18) & 0x0f)),
	pData[4] = (uint8_t)(locator_hash >> 10),
	pData[5] = (uint8_t)(locator_hash >> 2),
	pData[6] = (uint8_t)((locator_hash & 0x03) << 6),
	pData[7] = pData[8] = pData[9] = pData[10] = 0;

}


// Parity of 32bit word helper function (0 for even, 1 for odd)
static uint8_t parity32(uint32_t data)
{
	// Repeatedly fold the word onto itself with XOR
	data ^= (data >> 16);
	data ^= (data >> 8);
	data ^= (data >> 4);
	data ^= (data >> 2);
	data ^= (data >> 1);
	return (uint8_t)data & 1;
}


/**
  * @brief	Rate 1/2 convolutionally-encode 81 input bits to produce 81 bit pairs.
  * 		Store these (interleaved) into 162 bit output array.
  * @detail	Coded for clarity not performance
  * @param	-> 81 bit input array
  *  		-> 162 bit output array
  * @retval	None
  */
static void wspr_conv_encoder(uint8_t *pInput, uint8_t *pOutput)
{
	int i;
	uint32_t reg0 = 0, reg1 = 0;

//	// Test parity function
//	fprintf(stderr, "%d\n", parity32(0x40506078));

	for (i=0; i<81; i++)
	{
		// Left shift both encoder registers
		reg0 <<= 1;	reg1 <<= 1;

		// Test input bit sequence (starting with MSbit)
		if (pInput[i/8] & (0x80 >> (i & 7)))
		{
			// Data bit is 1
			reg0 |= 1; reg1 |= 1;
		}
		// else data bit is 0; do nothing

		if (parity32(reg0 & 0xF2D05351))
		{
			// Parity is odd - store 1 in even output bit location
			pOutput[i/4] |= (0x80 >> ((2*i) & 7));
		}

		if (parity32(reg1 & 0xE4613C47))
		{
			// Parity is odd - store 1 in odd output bit location
			pOutput[i/4] |= (0x40 >> ((2*i) & 7));
		}
	}
}


// Length 162 bit-reversed interleaver table
//static const uint8_t interleaver[] =
//{
//	0, 128, 64, 32, 160, 96, 16, 144, 80, 48, 112, 8, 136, 72, 40, 104,
//	24, 152, 88, 56, 120, 4, 132, 68, 36, 100, 20, 148, 84, 52, 116, 12,
//	140, 76, 44, 108, 28, 156, 92, 60, 124, 2, 130, 66, 34, 98, 18, 146,
//	82, 50, 114, 10, 138, 74, 42, 106, 26, 154, 90, 58, 122, 6, 134, 70,
//	38, 102, 22, 150, 86, 54, 118, 14, 142, 78, 46, 110, 30, 158, 94, 62,
//	126, 1, 129, 65, 33, 161, 97, 17, 145, 81, 49, 113, 9, 137, 73, 41,
//	105, 25, 153, 89, 57, 121, 5, 133, 69, 37, 101, 21, 149, 85, 53, 117,
//	13, 141, 77, 45, 109, 29, 157, 93, 61, 125, 3, 131, 67, 35, 99, 19,
//	147, 83, 51, 115, 11, 139, 75, 43, 107, 27, 155, 91, 59, 123, 7, 135,
//	71, 39, 103, 23, 151, 87, 55, 119, 15, 143, 79, 47, 111, 31, 159, 95,
//	63, 127
//};

// Inverse of length 162 bit-reversed interleaver table
static const uint8_t inv_interleaver[] =
{
	0, 81, 41, 122, 21, 102, 61, 142, 11, 92, 51, 132, 31, 112, 71, 152,
	6, 87, 46, 127, 26, 107, 66, 147, 16, 97, 56, 137, 36, 117, 76, 157,
	3, 84, 44, 125, 24, 105, 64, 145, 14, 95, 54, 135, 34, 115, 74, 155,
	9, 90, 49, 130, 29, 110, 69, 150, 19, 100, 59, 140, 39, 120, 79, 160,
	2, 83, 43, 124, 23, 104, 63, 144, 13, 94, 53, 134, 33, 114, 73, 154,
	8, 89, 48, 129, 28, 109, 68, 149, 18, 99, 58, 139, 38, 119, 78, 159,
	5, 86, 45, 126, 25, 106, 65, 146, 15, 96, 55, 136, 35, 116, 75, 156,
	10, 91, 50, 131, 30, 111, 70, 151, 20, 101, 60, 141, 40, 121, 80, 161,
	1, 82, 42, 123, 22, 103, 62, 143, 12, 93, 52, 133, 32, 113, 72, 153,
	7, 88, 47, 128, 27, 108, 67, 148, 17, 98, 57, 138, 37, 118, 77, 158,
	4, 85
};

// Length 162 WSPR synchronisation pattern (in transmission order)
static const uint8_t wspr_sync[] =
{
    1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1,
    1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
    1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0,
    1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0,
    0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1,
    0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0
};

/**
  * @brief	Interleave (scramble) 162 bit encoded data vector, combine with 162 bit
  *         sync vector to create the final 162 bit-pair (4FSK symbol) vector.
  * @detail Process in output array order using inverted interleaver table to scramble data
  * @param	-> convolutionally encoded data
  *         -> output array
  * @retval	None
  */
static void wspr_interleave_sync(uint8_t *pInput, uint8_t *pOutput)
{
	int i, o;

	// Build the 162 bit-pair output array
	for (o=0; o<162; o++)
	{
		// In order to find which input data bit maps to a given output data bit we
		// need to use an inverted interleave table:
		i = inv_interleaver[o];

		// Put the data bit in the upper bit of each pair
		if (pInput[i/8] & (0x80 >> (i & 7))) pOutput[o/4] |= (0x80 >> ((2*o) & 7));

		// Put the sync bit into the lower bit of each pair
		if (wspr_sync[o]) pOutput[o/4] |= (0x40 >> ((2*o) & 7));
	}
}


/**
  * @brief	Transmit the 162 symbol message using 4FSK centred about base_freq
  * @param	-> symbols
  *         Centre audio freq in Hz
  * @retval	None
  */
static void wspr_modulator(uint8_t *pSymbols, int32_t base_freq)
{
	int i, symbol;
	float freq_dev;


	for (i=0; i<162; i++)
	{
		symbol = (pSymbols[i/4] >> (6 - (2*i & 7) )) & 3;
		//fprintf(stderr, "%d", symbol);

		// Couldn't find a definition for the 4FSK tone mapping so I tried
		// symbol 0 being base freq and 3 being +3*deviation. This works.
		freq_dev = (float)(SAMPRATE * symbol) / (float)SYMBOL_SAMPS;

		generate_tone((float)(base_freq - 2) + freq_dev); // -2 Hz approximately centres
	}
}


/**
  * @brief	Output an audio tone lasting 1 WSPR symbol duration
  * @detail Signed 16bit values, written to STDOUT
  * @param	Tone freq in Hz
  * @retval	None
  */
static void generate_tone(float freq)
{
	static float phase = 0.0;

	int i;
	int16_t sample;
	float phase_step = (freq * 2*M_PI) / SAMPRATE;

	for (i=0; i<SYMBOL_SAMPS; i++)
	{
		sample = 32000 * sinf(phase);
		fwrite(&sample, 2, 1, stdout);

		phase += phase_step;
		if (phase > 2*M_PI) phase -= 2*M_PI;
	}
}

// EOF
