/* avr-libc includes */
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <util/twi.h>

/* uracoli includes */
#include <board.h>
#include <transceiver.h>
#include <ioutil.h>

/* application related includes */
#include "appcfg.h"
#include "muse.h"

#define WDT_ENABLE_SYSRESET()	do{}while(0)
#define WDT_DISABLE_SYSRESET()	do{}while(0)

static uint8_t wdt_timeout = WDTO_8S;

static inline void wdt_setup(uint8_t timeout)
{
	WDTCSR = (1 << WDCE) | (1 << WDE); /* Enable configuration change */
	WDTCSR = (1 << WDIF) | (1 << WDIE) | /* Enable Watchdog Interrupt Mode */
			(timeout & 0x08 ? _WD_PS3_MASK : 0x00) | (timeout & 0x07);
}

ISR(WDT_vect) {
	wdt_setup(wdt_timeout);
}

void nwk_init()
{
}

void led_set(uint8_t state)
{
	if(state){		/* light on */
		LED_PORT |= _BV(LED_ANODE_bp);
		LED_PORT &= ~_BV(LED_CATHODE_bp);
	}else{			/* off */
		LED_PORT &= ~_BV(LED_ANODE_bp);
		LED_PORT |= _BV(LED_CATHODE_bp);
	}
}

/*
 * Measure ambient light
 *
 * method: 
 *   1. inverse charge the diode (cathode=positive, anode=negative)
 *   2. set cathode as input, leave anode as negative
 *   3. measure time until cathode goes low
 * 
 *
 */
uint8_t led_measure(void)
{
	uint8_t i=0xFF;
	
	/* both output */
	LED_DDR |= _BV(LED_ANODE_bp) | _BV(LED_CATHODE_bp);

	/* reverse charge */
	LED_PORT &= ~_BV(LED_ANODE_bp);
	LED_PORT |= _BV(LED_CATHODE_bp);

	_delay_us(100);
	/* charge time, t.b.d. */
	
	LED_DDR &= ~_BV(LED_CATHODE_bp);	/* cathode as input */
	LED_PORT &= ~_BV(LED_CATHODE_bp);	/* immediately switch off pullup */

	do{
		_delay_ms(1);
	}while( (--i) && (LED_PIN & _BV(LED_CATHODE_bp)));

	return i;
}

void i2c_init(void)
{
	TWSR |= _BV(TWPS0);
	TWBR = 32;
}

uint8_t acc_regrd(uint8_t addr)
{
	uint8_t ret;

	TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);
	while(!(TWCR & _BV(TWINT)));
	
	if(TW_START == TW_STATUS){
	
		TWDR = BOARD_ACC_MMA7455_I2CADDR | TW_WRITE;
		TWCR = _BV(TWINT) | _BV(TWEN);
		while(!(TWCR & _BV(TWINT)));

		if(TW_MT_SLA_ACK == TW_STATUS){
			
			TWDR = addr;					/* register address */
			TWCR = _BV(TWINT) | _BV(TWEN);
			while(!(TWCR & _BV(TWINT)));
		
			if(TW_MT_DATA_ACK == TW_STATUS){
			
				/* repeated Start condition */
				TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);
				while(!(TWCR & _BV(TWINT)));

				if(TW_REP_START == TW_STATUS){

					TWDR = BOARD_ACC_MMA7455_I2CADDR | TW_READ;
					TWCR = _BV(TWINT) | _BV(TWEN);
					while(!(TWCR & _BV(TWINT)));
					
					if(TW_MR_SLA_ACK == TW_STATUS){
						TWCR = _BV(TWINT) | _BV(TWEN);	/* auto-acknowledge bit NOT set-> receive only one byte */
						while(!(TWCR & _BV(TWINT)));
						
						if(TW_MR_DATA_NACK == TW_STATUS){
							ret = TWDR;
						}
					}
				}
			}
		}
	}

	TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);

	return ret;
}

uint8_t acc_regwr(uint8_t addr, uint8_t val)
{
	uint8_t ret;

	TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);
	while(!(TWCR & _BV(TWINT)));
	
	if(TW_START == TW_STATUS){
	
		TWDR = BOARD_ACC_MMA7455_I2CADDR | TW_WRITE;
		TWCR = _BV(TWINT) | _BV(TWEN);
		while(!(TWCR & _BV(TWINT)));

		if(TW_MT_SLA_ACK == TW_STATUS){
			
			TWDR = addr;					/* register address */
			TWCR = _BV(TWINT) | _BV(TWEN);
			while(!(TWCR & _BV(TWINT)));
		
			if(TW_MT_DATA_ACK == TW_STATUS){
			
				TWDR = val;
				TWCR = _BV(TWINT) | _BV(TWEN);
				while(!(TWCR & _BV(TWINT)));

				if(TW_MT_DATA_ACK == TW_STATUS){
						/* do nothing */
				}
			}
		}
	}

	TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);

	return ret;
}

ISR(ADC_vect)
{
}

/**
  * \brief Measure the supply voltage
  *
  * Method: set 1.1V reference as input and AVCC as reference
  * 	this returns a negative result: AVCC = 1.1V - result
  *
  * \return The MCU internal voltage in [mV]
  */
uint16_t measure_vmcu(void)
{
	const uint8_t channel = 0x0E;		/* Bandgap 1.1V */
	const uint32_t vref_mV = 1100;
	const uint32_t adcscale = 1024;		/* 10-bit ADC */

	ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1);	/* PS 64 */
	ADMUX  = _BV(REFS0) | (channel & 0x0F); /* reference: AVCC */

	_delay_us(200);			/* settle ADC input to new channel value */

#if 1
	ADCSRA |= _BV(ADIF);		/* clear flag */
	ADCSRA |= _BV(ADIE);
	set_sleep_mode(SLEEP_MODE_ADC);	/* triggers ADC */
	sleep_mode();
#else
	ADCSRA |= _BV(ADSC);
	while(!(ADCSRA & _BV(ADIF)));
#endif
	/* sleeping wake up by ACD IRQ */

	ADCSRA &= ~_BV(ADEN);

	return ( (vref_mV*adcscale) / ADC );
}

#if 0
int main(void)
{
	volatile uint8_t tmp;

	WDTCSR = (1 << WDCE) | (1 << WDE); /* Enable configuration change */
	WDTCSR = (1 << WDIF); /* Disable */

	PORTB |= _BV(PB0) | _BV(PB2);
	DDRB |= _BV(PB0) | _BV(PB2) | _BV(PB3) | _BV(PB5);
	DDRB &= ~_BV(PB4);
	SPCR = _BV(SPE) | _BV(MSTR);

	asm volatile ("nop");

	SPDR = 0xB5;

	asm volatile ("nop");

	wdt_setup(WDTO_1S);
	set_sleep_mode( SLEEP_MODE_PWR_DOWN);
	sei();

	for (;;) {

		LED_PORT |= _BV(LED_ANODE_bp);
		_delay_ms(10);
		LED_PORT &= ~_BV(LED_ANODE_bp);

		/* regular case: watchdog is used as timer to continue application loop infinitely, do not reset */
		WDT_DISABLE_SYSRESET();
		sleep_mode();

		/* sleeping, wake up by watchdog */
	}

}
#else

ISR(TRX_IRQ_vect)
{

}

static muse_dataframe_t frm;

int main(void)
{
	WDTCSR = (1 << WDCE) | (1 << WDE);
	WDTCSR = (1 << WDIF);

	LED_DDR |= _BV(LED_ANODE_bp) | _BV(LED_CATHODE_bp);
	LED_PORT |= _BV(LED_ANODE_bp);
	_delay_ms(200);
	LED_PORT &= ~_BV(LED_ANODE_bp);

	/* Init MCU peripherals */
	trx_io_init( SPI_RATE_1_2);

	/* Initialize the transceiver */
	TRX_RESET_LOW();
	TRX_SLPTR_LOW();
	DELAY_US( TRX_RESET_TIME_US);
	TRX_RESET_HIGH();
	trx_reg_write(RG_TRX_STATE, CMD_TRX_OFF);

	/* Setup transceiver */

	frm.FCF = 0x8801;
	frm.destpanid = 0x03;
	frm.destaddr = 0x00;
	frm.srcpanid = 0x03;
	frm.srcaddr = 0x21;

	trx_bit_write(SR_CHANNEL, DEFAULT_CHANNEL);
	trx_reg_write(RG_SHORT_ADDR_0, (uint8_t)frm.srcaddr);
	trx_reg_write(RG_SHORT_ADDR_1, (uint8_t)frm.srcaddr >> 8);
	trx_reg_write(RG_PAN_ID_0, (uint8_t)frm.srcpanid);
	trx_reg_write(RG_PAN_ID_1, (uint8_t)frm.srcpanid >> 8);
	trx_bit_write(SR_TX_AUTO_CRC_ON, 1);

	/* IRQs required to wake up MCU: PLL_LOCK, AWAKE_END (=CCA_ED), TRX_END */
	trx_reg_write(RG_IRQ_MASK, TRX_IRQ_TRX_END | TRX_IRQ_PLL_LOCK
			| TRX_IRQ_CCA_ED); /* shared with AWAKE_END */

	DI_TRX_IRQ();

	/* set transceiver to sleep */
	TRX_SLPTR_HIGH();

	TRX_IRQ_INIT();

	/* clear all pending flags
	 * TODO: add macro in board.h
	 */
	PCIFR = 0xFF;

	wdt_setup(wdt_timeout);
	set_sleep_mode( SLEEP_MODE_PWR_DOWN);
	sei();

	for (;;) {

		frm.avr_voltage = measure_vmcu();
		frm.led = led_measure();

		WDT_DISABLE_SYSRESET();

		TRX_SLPTR_LOW(); /* wake up transceiver */
		EI_TRX_IRQ(); /* enable transceiver interrupt */

		set_sleep_mode( SLEEP_MODE_IDLE);

		/* failure handling: if the MCU is not waken up by ACC IRQ,
		 * it will be reset (after awaken from watchdog)
		 */
		WDT_ENABLE_SYSRESET();
		sleep_mode();

		/* sleeping, wake up by AWAKE_END interrupt (TRX) */

		WDT_DISABLE_SYSRESET();
		trx_reg_read( RG_IRQ_STATUS);
		trx_reg_write(RG_TRX_STATE, CMD_TX_ARET_ON);

		/* failure handling: if the MCU is not waken up by ACC IRQ,
		 * it will be reset (after awaken from watchdog)
		 */
		WDT_ENABLE_SYSRESET();
		sleep_mode();

		/* sleeping, wake up by PLL_LOCK interrupt */
		WDT_DISABLE_SYSRESET();

		trx_reg_read(RG_IRQ_STATUS);

		TRX_SLPTR_HIGH();
		TRX_SLPTR_LOW();

		frm.seqnumber++;
		trx_frame_write(sizeof(muse_dataframe_t), (uint8_t*) &frm);

		/* failure handling: if the MCU is not waken up by ACC IRQ,
		 * it will be reset (after awaken from watchdog)
		 */
		WDT_ENABLE_SYSRESET();
		sleep_mode();

		/* sleeping, wake up by TRX_END interrupt */

		WDT_DISABLE_SYSRESET();

		trx_reg_read(RG_IRQ_STATUS);
		trx_reg_write(RG_TRX_STATE, CMD_FORCE_TRX_OFF);

		TRX_SLPTR_HIGH(); /* set transceiver to sleep */
		DI_TRX_IRQ();

		LED_PORT |= _BV(LED_ANODE_bp);
		_delay_ms(10);
		LED_PORT &= ~_BV(LED_ANODE_bp);

		/* regular case: watchdog is used as timer to continue application loop infinitely, do not reset */
		WDT_DISABLE_SYSRESET();
		set_sleep_mode( SLEEP_MODE_PWR_DOWN);
		sleep_mode();

		/* sleeping, wake up by watchdog */

	}

	/* this point is never reached */
}
#endif
/* EOF */
