//
//    This file is part of Dire Wolf, an amateur radio packet TNC.
//
//    Copyright (C) 2019  John Langner, WB2OSZ
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 2 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#include "direwolf.h"

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>

#include "fx25.h"
#include "fcs_calc.h"
#include "textcolor.h"
#include "audio.h"
#include "gen_tone.h"


//#define FXTEST 1		// To build unit test application.


#ifndef FXTEST
static void send_bytes (int chan, unsigned char *b, int count);
static void send_bit (int chan, int b);
#endif
static int stuff_it (unsigned char *in, int ilen, unsigned char *out, int osize);


static int number_of_bits_sent[MAX_CHANS];		// Count number of bits sent by "fx25_send_frame" or "???"


#if FXTEST
static unsigned char preload[] = {
	'T'<<1, 'E'<<1, 'S'<<1, 'T'<<1, ' '<<1, ' '<<1, 0x60,
	'W'<<1, 'B'<<1, '2'<<1, 'O'<<1, 'S'<<1, 'Z'<<1, 0x63,
	0x03, 0xf0,
	'F', 'o', 'o', '?' , 'B', 'a', 'r', '?' ,   //  '?' causes bit stuffing
	0, 0, 0		// Room for FCS + extra
}; 

int main ()
{
	text_color_set(DW_COLOR_ERROR);
	dw_printf("fxsend - FX.25 unit test.\n");
	dw_printf("This generates 11 files named fx01.dat, fx02.dat, ..., fx0b.dat\n");
	dw_printf("Run fxrec as second part of test.\n");

	fx25_init (3);
	for (int i = CTAG_MIN; i <= CTAG_MAX; i++) {
	  fx25_send_frame (0, preload, (int)sizeof(preload)-3, i);
	}
	exit(EXIT_SUCCESS);
} // end main
#endif


/*-------------------------------------------------------------
 *
 * Name:	fx25_send_frame
 *
 * Purpose:	Convert HDLC frames to a stream of bits.
 *
 * Inputs:	chan	- Audio channel number, 0 = first.
 *
 *		fbuf	- Frame buffer address.
 *
 *		flen	- Frame length, before bit-stuffing, not including the FCS.
 *
 *		fx_mode	- Normally, this would be 16, 32, or 64 for the desired number
 *			  of check bytes.  The shortest format, adequate for the 
 *			  required data length will be picked automatically.
 *			  0x01 thru 0x0b may also be specified for a specific format
 *			  but this is expected to be mostly for testing, not normal
 *			  operation.
 *
 * Outputs:	Bits are shipped out by calling tone_gen_put_bit().
 *
 * Returns:	Number of bits sent including "flags" and the
 *		stuffing bits.  
 *		The required time can be calculated by dividing this
 *		number by the transmit rate of bits/sec.
 *		-1 is returned for failure.
 *
 * Description:	Generate an AX.25 frame in the usual way then wrap
 *		it inside of the FX.25 correlation tag and check bytes.
 *
 * Assumptions:	It is assumed that the tone_gen module has been
 *		properly initialized so that bits sent with 
 *		tone_gen_put_bit() are processed correctly.
 *
 * Errors:	If something goes wrong, return -1 and the caller should
 *		fallback to sending normal AX.25.
 *
 *		This could happen if the frame is too large.
 *
 *--------------------------------------------------------------*/

int fx25_send_frame (int chan, unsigned char *fbuf, int flen, int fx_mode)
{
	if (fx25_get_debug() >= 3) {
	  text_color_set(DW_COLOR_DEBUG);
	  dw_printf ("------\n");
	  dw_printf ("FX.25 send frame: chan = %d, FX.25 mode = %d\n", chan, fx_mode);
	  fx_hex_dump (fbuf, flen);
	}

	number_of_bits_sent[chan] = 0;

	// Append the FCS.

	int fcs = fcs_calc (fbuf, flen);
	fbuf[flen++] = fcs & 0xff;
	fbuf[flen++] = (fcs >> 8) & 0xff;

	// Add bit-stuffing.

	unsigned char data[FX25_MAX_DATA+1];
	const unsigned char fence = 0xaa;
	data[FX25_MAX_DATA] = fence;

	int dlen = stuff_it(fbuf, flen, data, FX25_MAX_DATA);

	assert (data[FX25_MAX_DATA] == fence);
	if (dlen < 0) {
	  text_color_set(DW_COLOR_ERROR);
	  dw_printf ("FX.25: Frame length of %d + overhead is too large to encode.\n", flen);
	  return (-1);
	}

	// Pick suitable correlation tag depending on
	// user's preference, for number of check bytes,
	// and the data size.

	int ctag_num = fx25_pick_mode (fx_mode, dlen);

	if (ctag_num < CTAG_MIN || ctag_num > CTAG_MAX) {
	  text_color_set(DW_COLOR_ERROR);
	  dw_printf ("FX.25: Could not find suitable format for requested %d and data length %d.\n", fx_mode, dlen);
	  return (-1);
	}

	uint64_t ctag_value = fx25_get_ctag_value (ctag_num);

	// Zero out part of data which won't be transmitted.
	// It should all be filled by extra HDLC "flag" patterns.

	int k_data_radio = fx25_get_k_data_radio (ctag_num);
	int k_data_rs = fx25_get_k_data_rs (ctag_num);
	int shorten_by = FX25_MAX_DATA - k_data_radio;
	if (shorten_by > 0) {
	  memset (data + k_data_radio, 0, shorten_by);
	}

	// Compute the check bytes.

	unsigned char check[FX25_MAX_CHECK+1];
	check[FX25_MAX_CHECK] = fence;
	struct rs *rs = fx25_get_rs (ctag_num);

	assert (k_data_rs + NROOTS == NN);

	ENCODE_RS(rs, data, check);
	assert (check[FX25_MAX_CHECK] == fence);

	if (fx25_get_debug() >= 3) {
	  text_color_set(DW_COLOR_DEBUG);
	  dw_printf ("FX.25: transmit %d data bytes, ctag number 0x%02x\n", k_data_radio, ctag_num);
	  fx_hex_dump (data, k_data_radio);
	  dw_printf ("FX.25: transmit %d check bytes:\n", NROOTS);
	  fx_hex_dump (check, NROOTS);
	  dw_printf ("------\n");
	}

#if FXTEST
	// Standalone text application.

	unsigned char flags[16] = { 0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e ,0x7e };
	char fname[32];
	snprintf (fname, sizeof(fname), "fx%02x.dat", ctag_num);
	FILE *fp = fopen(fname, "wb");
	fwrite (flags, sizeof(flags), 1, fp);
	//fwrite ((unsigned char *)(&ctag_value), sizeof(ctag_value), 1, fp);	// No - assumes little endian.
	for (int k = 0; k < 8; k++) {
	  unsigned char b = (ctag_value >> (k * 8)) & 0xff;	// Should be portable to big endian too.
	  fwrite (&b, 1, 1, fp);
	}
#if 1
	for (int j = 8; j < 16; j++) {	// Introduce errors.
	  data[j] = ~ data[j];
	}
#endif
	fwrite (data, k_data_radio, 1, fp);
	fwrite (check, NROOTS, 1, fp);
	fwrite (flags, sizeof(flags), 1, fp);
	fflush(fp);
	fclose (fp);
#else
	// Normal usage.  Send bits to modulator.

	for (int k = 0; k < 8; k++) {
	  unsigned char b = (ctag_value >> (k * 8)) & 0xff;
	  send_bytes (chan, &b, 1);
	}
	send_bytes (chan, data, k_data_radio);
	send_bytes (chan, check, NROOTS);
#endif
	
	return (number_of_bits_sent[chan]);
}


#ifndef FXTEST

static void send_bytes (int chan, unsigned char *b, int count)
{
	for (int j = 0; j < count; j++) {
	  unsigned char x = b[j];
	  for (int k = 0; k < 8; k++) {
	    send_bit (chan, x & 0x01);
	    x >>= 1;
	  }
	}
}

/*
 * NRZI encoding.
 * data 1 bit -> no change.
 * data 0 bit -> invert signal.
 */
static void send_bit (int chan, int b)
{
	static int output[MAX_CHANS];

	if (b == 0) {
	  output[chan] = ! output[chan];
	}
	tone_gen_put_bit (chan, output[chan]);
	number_of_bits_sent[chan]++;
}
#endif  // FXTEST


/*-------------------------------------------------------------
 *
 * Name:	stuff_it
 *
 * Purpose:	Perform HDLC bit-stuffing and add "flag" octets in
 *		preparation for the RS encoding.
 *
 * Inputs:	in	- Frame, including FCS, in.
 *
 *		ilen	- Number of bytes in.
 *
 *		osize	- Size of out area.
 *
 * Outputs:	out	- Location to receive result.
 *
 * Returns:	Number of bytes needed in output area including one trailing flag.
 *		-1 if it won't fit.
 *
 * Description:	Convert to stream of bits including:
 *			start flag
 *			bit stuffed data, including FCS
 *			end flag
 *		Fill remainder with flag octets which might not be on byte boundaries.
 * 
 *--------------------------------------------------------------*/

#define put_bit(value)  {						\
			if (olen >= osize) return(-1);			\
			if (value) out[olen>>3] |= 1 << (olen & 0x7);	\
			olen++;						\
			}

static int stuff_it (unsigned char *in, int ilen, unsigned char *out, int osize)
{
	const unsigned char flag = 0x7e;
	int ret = -1;
	memset (out, 0, osize);
	out[0] = flag;
	int olen = 8;			// Number of bits in output.
	osize *= 8;			// Now in bits rather than bytes.
	int ones = 0;

	for (int i = 0; i < ilen; i++) {
	  for (unsigned char imask = 1; imask != 0; imask <<= 1) {
	    int v = in[i] & imask;
	    put_bit(v);
	    if (v) {
	      ones++;
	      if (ones == 5) {
	        put_bit(0);
	        ones = 0;
	      }
	    }
	    else {
	      ones = 0;
	    }
	  }
	}
	for (unsigned char imask = 1; imask != 0; imask <<= 1) {
	  put_bit(flag & imask);
	}
	ret = (olen + 7) / 8;		// Includes any partial byte.

	unsigned char imask = 1;
	while (olen < osize) {
	  put_bit( flag & imask);
	  imask = (imask << 1) | (imask >> 7);	// Rotate.
	}

	return (ret);

} // end stuff_it

// end fx25_send.c