/**
 * \file	persistentCounter.c
 * \author	Daniel Otte 
 * \brief	a persistent 24-bit counter in EEPROM for ATmega µC
 * 
 */

#include <stdint.h>
#include "../compat/interrupt.h" /* cli() & sei() */
#include "../compat/eeprom.h"
#include "../config.h"

#ifdef ERROR_HANDLING
	#include "error-handling.h"
	#define PERSISTENT_COUNTER_OVERFLOW		(void*)0, 2,4,1
	#define PERSISTENT_COUNTER_WRITER_ERROR	(void*)0, 2,4,2
#endif

#define RING_SIZE 168

uint8_t ring_idx = 0xff;

uint16_t  EEMEM B08_23;
uint8_t EEMEM B0_7[RING_SIZE];

#ifdef INIT_EEPROM
void init_buffer(void){
	uint8_t i;
	eeprom_busy_wait();
	eeprom_write_word(&B08_23, 0x0000);
	for(i=0; i<RING_SIZE; ++i){
		eeprom_busy_wait();
		eeprom_write_byte(&(B0_7[i]), 0x00);
	}
}
#endif

void percnt_init(void){
	uint8_t i;
	uint8_t maxidx=0;
	uint8_t t,max=eeprom_read_byte(&(B0_7[0]));
	#ifdef INIT_EEPROM
		if (eeprom_read_word(&B08_23)==0xFFFF){	/* test if the 2 MSB == 0xFFFF*/
			if (eeprom_read_word((uint16_t*)&(B0_7[0]))==0xFFFF) /* test the first to bytes of ringbuffer*/
				init_buffer();
		}
	#endif
	for(i=0; i<RING_SIZE; ++i){ /* this might be speed up, but such optimisation could lead to timing attacks */
		eeprom_busy_wait();
		t=eeprom_read_byte(&(B0_7[i]));
		if(t==max+1){
			max=t;
			maxidx=i;
		}
	}
	ring_idx = (maxidx==RING_SIZE)?0:maxidx;
}

uint32_t percnt_get(void){
	uint32_t ret=0;

	if(ring_idx==0xff)
		percnt_init();
	cli();
	eeprom_busy_wait();
	ret=eeprom_read_word(&B08_23)<<8;
	eeprom_busy_wait();
	ret |= eeprom_read_byte(&(B0_7[ring_idx]));
	sei();
	return ret;
}

void percnt_inc(void){
	/* we must make this resistant agaist power off while this is running ... */
	uint32_t u;
		
	if(ring_idx==0xff)
		percnt_init();

	u = percnt_get();
	cli();
	/* it's important to write msb first! */
	if((u&0x000000ff) == 0xff){
		if((u&0x0000ffff) == 0xffff){
			if((u&0x00ffffff) == 0xffffff){
				/* we can turn the lights off now. it's time to die */
			#ifdef ERROR_HANDLING	
				error(PERSISTENT_COUNTER_OVERFLOW);
			#endif
			}
			eeprom_busy_wait();
			eeprom_write_byte(&(((uint8_t*)&B08_23)[1]),((u+1)>>16)&0xff);
		}
		eeprom_busy_wait();
		eeprom_write_byte(&(((uint8_t*)&B08_23)[0]),((u+1)>>8)&0xff);
	}
	/* set least significant byte (in ringbuffer) */
	ring_idx = (ring_idx+1)%RING_SIZE;
	eeprom_busy_wait();
	eeprom_write_byte(&(B0_7[ring_idx]),(u+1)&0xff);
	eeprom_busy_wait();
	
	if(u+1 != percnt_get()){
	#ifdef ERROR_HANDLING	
		error(PERSISTENT_COUNTER_WRITER_ERROR);
	#endif 
	}
	
	sei();
}