/*-
 * Copyright 2021 John-Mark Gurney.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include "stm32l1xx.h"
#include "stm32l1xx_hal.h"

/* LoRaMac headers */
#include <board.h>
#include <adc.h>
#include <radio.h>
#include <delay.h>

/* lora-irr headers */
#include <misc.h>
#include <strobe_rng_init.h>
#include <comms.h>

enum {
	CMD_TERMINATE = 1,
	CMD_WAITFOR = 2,
	CMD_RUNFOR = 3,
	CMD_PING = 4,
	CMD_SETUNSET = 5,
	CMD_ADV = 6,
	CMD_CLEAR = 7,
};

/*
 * rxpktavail is initialized to true meaning that the data in rxpkt
 * can be over written.  When a packet is received, the data is copied
 * to rxpkt, and then rxpktavail is set to false.  Once the packet has
 * been processed, it is set back to true.
 */
static uint8_t rxpkt[128];
static struct pktbuf rxpktbuf;
static volatile bool rxpktavail;

#include <shared_key.h>

static struct comms_state cs;

void
txdone(void)
{

	/* restart Rx when Tx done */
	Radio.Rx(0);
}

void
txtimeout(void)
{

	/* restart Rx when Tx done */
	Radio.Rx(0);
}

void
rxdone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr)
{

	if (rxpktavail && size <= sizeof rxpkt) {
		memcpy(rxpkt, payload, size);
		rxpktbuf = (struct pktbuf){
			.pkt = rxpkt,
			.pktlen = size,
		};
		rxpktavail = false;
	}
	Radio.Rx(0);
}

void
rxtimeout(void)
{

	Radio.Rx(0);
}

void
rxerr(void)
{

	Radio.Rx(0);
}

RadioEvents_t revents = {
	.TxDone = txdone,
	.TxTimeout = txtimeout,
	.RxDone = rxdone,
	.RxTimeout = rxtimeout,
	.RxError = rxerr,
};

/*
 * Seed the randomness from the radio.  This is not a great
 * seed, and is hard to gauge how much randomness is really
 * there.  Assuming about 1 bit per 8 bits looks pretty safe,
 * so add 256 * 8 / 32 words.
 */
static void
radio_seed_rng(void)
{
#if 1
	uint32_t v;
	int i;

	for (i = 0; i < 256 * 8 / 32; i++) {
		v = Radio.Random();
		strobe_seed_prng((uint8_t *)&v, sizeof v);
	}
#endif
}

static void
analog_seed_rng(void)
{
#if 1
	uint16_t v;
	int i;

	for (i = 0; i < 256 / 2; i++) {
		/*
		 * Capture some ADC data.  If pin is floating, 0xfff
		 * happens frequently, if pin is grounded, 0 happens
		 * frequently, filter these values out.
		 */
		do {
			v = AdcReadChannel(&Adc, ADC_CHANNEL_21);
		} while (v == 0 || v == 0xfff);
		strobe_seed_prng((uint8_t *)&v, sizeof v);
	}
#endif
}

static inline uint32_t
letoh_32(uint8_t *v)
{
	return v[0] | (v[1] << 8) | (v[2] << 16) | ((unsigned)v[3] << 24);
}

struct chaninfo {
	GPIO_TypeDef *bank;
	uint16_t pinnum;
	bool init;
	bool invert;
} chans[] = {
	[0] = { .bank = GPIOB, .pinnum = GPIO_PIN_5, .invert = true, },
	[1] = { .bank = GPIOB, .pinnum = GPIO_PIN_6, .invert = true, },
	[2] = { .bank = GPIOB, .pinnum = GPIO_PIN_7, .invert = true, },
	[3] = { .bank = GPIOB, .pinnum = GPIO_PIN_9, .invert = true, },
	/* Turn on LED at start */
	[4] = { .bank = GPIOB, .pinnum = GPIO_PIN_8, .init = true, },
};
#define nitems(x)	(sizeof(x) / sizeof *(x))

static void
set_chan(uint32_t chan, bool val)
{
	struct chaninfo ci;

	if (chan < nitems(chans)) {
		ci = chans[chan];
		HAL_GPIO_WritePin(ci.bank, ci.pinnum, val ^ ci.invert ?
		    GPIO_PIN_SET : GPIO_PIN_RESET);
	}
}

static void
setup_gpio()
{
	GPIO_InitTypeDef GPIO_InitStruct;
	int i;

	for (i = 0; i < nitems(chans); i++) {
		GPIO_InitStruct = (GPIO_InitTypeDef){
			.Pin = chans[i].pinnum,
			.Mode = GPIO_MODE_OUTPUT_PP,
			.Pull = GPIO_NOPULL,
			.Speed = GPIO_SPEED_FREQ_LOW,
		};
		HAL_GPIO_Init(chans[i].bank, &GPIO_InitStruct);
		set_chan(i, chans[i].init);
	}
}

static struct sched {
	uint32_t cmd;
	uint32_t end_wait_tick;	/* end if running, otherwise how long to wait */
	uint32_t chan;
} schedule[20];
static int schedpos;		/* position in schedule, % nitems(schedule)*/
static int schedcnt;		/* total items waiting */

#define SCHED_ITEM(x)	(schedule[(schedpos + x) % nitems(schedule)])
#define SCHED_HEAD	SCHED_ITEM(0)
#define SCHED_TAIL	SCHED_ITEM(schedcnt)

static void
start_sched(struct sched *sched)
{

	sched->end_wait_tick += uwTick;

	if (sched->cmd == CMD_RUNFOR)
		set_chan(sched->chan, 1);
}

static bool
canproc_sched()
{

	/* nothing to do? */
	if (schedcnt == 0)
		return false;

	/* not yet expired */
	if (uwTick < SCHED_HEAD.end_wait_tick)
		return false;

	return true;
}

static void
process_sched()
{

	if (!canproc_sched())
		return;

	if (SCHED_HEAD.cmd == CMD_RUNFOR)
		set_chan(SCHED_HEAD.chan, 0);

	/* we are done, advance */
	schedpos++;
	schedcnt--;

	if (schedcnt)
		start_sched(&SCHED_HEAD);
}

static void
enqueue_sched(uint32_t cmd, uint32_t ticks, uint32_t chan)
{

	if (schedcnt >= nitems(schedule))
		return;

	SCHED_TAIL = (struct sched){
		.cmd = cmd,
		.end_wait_tick = ticks,
		.chan = chan,
	};

	if (schedcnt == 0)
		start_sched(&SCHED_HEAD);

	schedcnt++;
}

static void
procmsg(struct pktbuf inbuf, struct pktbuf *outbuf)
{
	uint32_t args[5];
	int i, apos, cnt;

	i = 1;
	apos = 0;
	while (i < inbuf.pktlen) {
		if (i + 4 <= inbuf.pktlen)  {
			args[apos++] = letoh_32(&inbuf.pkt[i]);
			i += 4;
		}
	}

	outbuf->pkt[0] = inbuf.pkt[0];

	switch (inbuf.pkt[0]) {
	case CMD_WAITFOR:
		if (apos == 1)
			enqueue_sched(CMD_WAITFOR, args[0], -1);
		break;

	case CMD_RUNFOR:
		if (apos == 2)
			enqueue_sched(CMD_RUNFOR, args[0], args[1]);
		break;

	case CMD_PING:
		break;

	case CMD_SETUNSET:
		if (apos == 2)
			set_chan(args[0], args[1]);
		break;

	case CMD_ADV:
		cnt = 1;
		if (apos == 1)
			cnt = args[0];

		for (i = 0; i < cnt && i < schedcnt; i++)
			SCHED_ITEM(i).end_wait_tick = 0;
		break;

	case CMD_CLEAR:
		if (schedcnt)
			schedcnt = 1;
		break;

	default:
		outbuf->pkt[0] = 0;
		break;
	}

	outbuf->pktlen = 1;
}

int
main()
{

	strobe_rng_init();

	BoardInitMcu();

	Radio.Init(&revents);

	analog_seed_rng();

	radio_seed_rng();

	strobe_rng_save();

	setup_gpio();

	/* turn on LED */
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);

	Radio.SetModem(MODEM_LORA);
	Radio.SetChannel(914350 * 1000);

	/* RX/TX parameters */
	const uint8_t modem = MODEM_LORA;
	const uint8_t bandwidth = 0 /* 128 kHz */;
	const uint8_t datarate = 7 /* 128 chips */;
	const uint8_t coderate = 1 /* 4/5 */;
	const uint8_t preambleLen = 8 /* symbols */;
	const uint8_t fixLen = 0 /* variable */;
	const uint8_t crcOn = 1 /* on */;
	const uint8_t freqHopOn = 0 /* off */;
	const bool iqInverted = false /* not inverted */;

	Radio.SetRxConfig(modem, bandwidth, datarate, coderate, 0/*afc*/,
	    preambleLen, 5/*symTimeout*/, fixLen, 0/*payloadlen*/, crcOn,
	    freqHopOn, 0/*hopPeriod*/, iqInverted, true/*rxcont*/);
	Radio.SetTxConfig(modem, 11/*power*/, 0/*fdev*/, bandwidth, datarate,
	    coderate, preambleLen, fixLen, crcOn, freqHopOn, 0/*hopPeriod*/,
	    iqInverted, 1000/*timeout*/);

	/* blink led */
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
	DelayMs(300);
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);

	Radio.Rx(0);

	comms_init(&cs, procmsg, &shared_key_buf, NULL, NULL);

	uint8_t txbuf[128] = "i'mhere";
	struct pktbuf txpktbuf;

	txpktbuf = (struct pktbuf){
		.pkt = txbuf,
		.pktlen = 8,
	};
	Radio.Send(txpktbuf.pkt, txpktbuf.pktlen);

	rxpktavail = true;

	//Radio.Rx(0);

loop:
	while (canproc_sched())
		process_sched();

	BoardLowPowerHandler();
	if (Radio.IrqProcess != NULL)
		Radio.IrqProcess();

	if (!rxpktavail) {
		txpktbuf = (struct pktbuf){
			.pkt = txbuf,
			.pktlen = sizeof txbuf,
		};
		/* process available packet */
		comms_process(&cs, rxpktbuf, &txpktbuf);

		rxpktavail = true;

		if (txpktbuf.pktlen) {
			int i;
			for (i = 0; i < 1; i++) {
				DelayMs(20);
				Radio.Send(txpktbuf.pkt, txpktbuf.pktlen);
			}

#if 0
			/* blink led */
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
			DelayMs(300);
			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
			DelayMs(300);
#endif
		}

#if 0
		/* blink led */
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
		DelayMs(300);
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_SET);
#endif
	}
	goto loop;
}