/*
 * FileName: *.c
 * Date: 2023
 * Descripton: Program for Atiny13A controllers, controls DPDT relays and
 * has options to save settings in EEPROM.
 */

#ifndef MCU
#define MCU atiny13a
#endif

#ifndef F_CPU
#define F_CPU 4800000UL
#endif


#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/eeprom.h>

//These are just defined in case for compatability with Arduino
#ifndef HIGH
#define HIGH 1
#endif

#ifndef LOW
#define LOW 0
#endif

#ifndef UINT8_MAX
#define UINT8_MAX 256
#endif

//This is the bitpattern that should be at address 0x00
#define START_PATTERN 0xAA
#define END_PATTERN 0x55

//Addresses in the eeprom for the two bypasses
#define ROM_SP_ADR      0x0
#define ROM_BP1_ADR     0x1
#define ROM_BP2_ADR     0x2
#define ROM_EP_ADR      0x3

//Debounce check number.
#define MAX_CHECKS 10

// Define DISABLE_TEMPORARY_SWITCH to turn off the ability to hold
// the switch to temporarily engage/bypass.
//#define DISABLE_TEMPORARY_SWITCH

#define PIN_SW2     PINB3
#define PIN_BYPASS2 PB4
#define PIN_SW1     PINB2
#define PIN_BYPASS1 PB1
#define PIN_MUTE    PB0

//The BLINK_QTY can be edited from 0-255 blinks
#define BLINK_QTY   2
#define BLINK_CNT 2 * BLINK_QTY
#define BLINK_DELAY     200



/*The timing of "ticks" is dependent on the AVR timer's counter register
 * so for an 8bit register the maximum value is 256. Given we stick with
 * a 1Mhz cpu frequency can use this formula to calculate the number of
 * interrupts(timer overflows) per second:
 *
 * OVF_S = (F_CPU / DIV)/ (2^8)
 * 61 = (1000000 / 64) / 256
 *
 * This is important because the ATiny13/A only have a single timer built into
 * them.
 *
 * Ticks are used as our way of keep track of long button presses.
 * */
//original sent had 60
#define LONG_PRESS_TICKS 66


/*A structure to hold the button info*/
typedef struct {
    uint8_t is_pressed: 1;
    uint8_t is_bypassed: 1;
    uint8_t is_long_pressed: 1;
    uint8_t timer_enabled: 1;
    uint8_t pressed_ticks;
    uint8_t input_pin;
    uint8_t output_pin;
}btn_state; 


/*
 * ###############################
 * Global Variables 
 * ###############################
 */


/*Create two diffent instances of the button state structure*/
btn_state btn1;
btn_state btn2;

/*Some variables for managing the debouncing*/
volatile uint8_t debounced_state;
volatile uint8_t db_state[MAX_CHECKS];
volatile uint8_t idx;

volatile uint16_t tick_count;

/*
 * ###############################
 * FUNCTION PROTOTYPES
 * ###############################
 */

static void toggle_output(btn_state *b);
static inline void init_btn(btn_state *b, uint8_t input_pin, uint8_t output_pin);
static void clear_button_timer(btn_state *b);
static inline void start_button_timer(btn_state *b);
static inline void check_button_longpress(btn_state *b);
static void update_button_output(btn_state *b);
static void update_button_input(btn_state *b);

static inline void blink_bypass1(void);
static inline void blink_bypass2(void);

static inline void init_timer0();
static inline void debounce_switch();
/*
 * ###############################
 * SETUP AND LOOP
 * ###############################
 */
void init_prog()
{
    /*Set the debounced state to all high*/
    debounced_state = 0xFF;
  
    /*Configures the PINS for the input and output.*/
    init_btn(&btn1, PIN_SW1, PIN_BYPASS1);
    init_btn(&btn2, PIN_SW2, PIN_BYPASS2);
    
    /*Wait 5ms for pull-up resistors voltage to become stable.*/
    _delay_ms(5);

    /*check if the eeprom has info. */
    while(! eeprom_is_ready()) {} //Blocks until eeprom is ready.

    //Checks against a bit pattern we defined to represent the start of data.
    if(eeprom_read_byte((uint8_t *)ROM_SP_ADR) == START_PATTERN) {
        //Reads the two bytes representing the two states.       
        btn1.is_bypassed = eeprom_read_byte((uint8_t *)ROM_BP1_ADR);
        btn2.is_bypassed = eeprom_read_byte((uint8_t *)ROM_BP2_ADR);
    }
    else {
        //otherwise we write the init values for the start pattern and bypass states.
        eeprom_write_byte((uint8_t *)ROM_SP_ADR, START_PATTERN);
        eeprom_write_byte((uint8_t *)ROM_BP1_ADR, 0x0);
        eeprom_write_byte((uint8_t *)ROM_BP2_ADR, 0x0);
    }

    btn1.is_pressed = ((PINB & (1<<btn1.input_pin))  == 0);
    btn2.is_pressed = ((PINB & (1<<btn2.input_pin))  == 0);


    /*This is to read if the user wants to change the saved states.*/
    /*Manually read the current switch state*/
    if(btn1.is_pressed){
        btn1.is_bypassed = ! btn1.is_bypassed;
        eeprom_write_byte((uint8_t *)ROM_BP1_ADR, btn1.is_bypassed);
        blink_bypass1();
    }
    if(btn2.is_pressed){
        btn2.is_bypassed = ! btn2.is_bypassed;
        eeprom_write_byte((uint8_t *)ROM_BP2_ADR, btn2.is_bypassed);
        blink_bypass2();
    }    

    if(btn1.is_bypassed){PORTB |= (1<<btn1.output_pin);}
    if(btn2.is_bypassed){PORTB |= (1<<btn2.output_pin);}

    init_timer0(); 
}

int main()
{

  init_prog();
   
  while(1){   
    update_button_output(&btn1);
    update_button_output(&btn2);
  
    update_button_input(&btn1);
    update_button_input(&btn2); 
  }
  return 0;
}


/*
 * ###############################
 * FUNCTION DEFs
 * ###############################
 */

void inf_blink_test(){
    DDRB |= (1<<PB3);
    while(1){
        PORTB ^= (1<<PB3);
        _delay_ms(250);
    }
}


//Made heavy use of static inline functions to improve readability.

static inline void blink_bypass1(void)
{
    for(uint8_t i = 0; i < BLINK_CNT; i++) {
        PORTB ^= (1<<PIN_BYPASS1);
        _delay_ms(BLINK_DELAY);
    }
}

static inline void blink_bypass2(void)
{
    for(uint8_t i = 0; i < BLINK_CNT; i++) {
        PORTB ^= (1<<PIN_BYPASS2);
        _delay_ms(BLINK_DELAY);
    }
}

/*
 * ############################
 * BUTTON methods 
 * ############################
 */ 

/*This is kinda like our button constructor*/
static void init_btn(btn_state *b, uint8_t input_pin, uint8_t output_pin)
{
    b->is_long_pressed = 0;
    b->is_pressed = 0;
    b->is_bypassed = 0;
    b->pressed_ticks = 0;
    b->timer_enabled = 0;
    b->input_pin = input_pin;
    b->output_pin = output_pin;

    /*Configure the buttons inputs and outputs*/
    DDRB &= ~(1<<b->input_pin);
    PORTB |= (1<<b->input_pin);
    
    DDRB |= (1<<b->output_pin);
    PORTB &= ~(1<<b->output_pin);
}

/*This is the fancy function to toggle output pins*/
static void toggle_output(btn_state *b)
{
    if(!b->is_bypassed){
        b->is_bypassed = 1;
        PORTB |= (1<<b->output_pin);
    }
    else{
        b->is_bypassed = 0;
        PORTB &= ~(1<<b->output_pin);
    }
}

static void clear_button_timer(btn_state *b)
{
    b->timer_enabled = 0;
    b->is_long_pressed = 0;
    b->pressed_ticks = 0;
}

static void start_button_timer(btn_state *b)
{
    clear_button_timer(b);
    b->timer_enabled = 1;
}

static void check_button_longpress(btn_state *b)
{
    if(b->pressed_ticks >= LONG_PRESS_TICKS) {
        b->is_long_pressed = 1;
        b->timer_enabled = 0;
    }
}

static void update_button_input(btn_state *b)
{
    /*Check from the global debounced port input*/
    
    /*check for pin HIGH*/
    if(debounced_state & (1<<b->input_pin)) {
        b->is_pressed = 0;
    }
    /*otherwise assume pin LOW*/
    else{
        b->is_pressed = 1;
    }
}

static void update_button_output(btn_state *b)
{
    /*If the button is actually pressed.*/
    if(b->is_pressed){
        
        /*If this is a new event.*/
        if(!b->is_long_pressed && !b->timer_enabled){
            /*Then start the timer and update the output*/
            toggle_output(b);
            start_button_timer(b);
            return;
        }

        /*If the timer is already going.*/
        else if(b->timer_enabled){
            /*Then just check if it's hit the threshold.*/
            check_button_longpress(b);
            return;
        }

        else{
            return;
        }
    }

    /*Else the button was realeased*/
    else if(!b->is_pressed){
        /*If the button was released on a long press.*/
        if(b->is_long_pressed){
            toggle_output(b);
        }
        clear_button_timer(b);
    }
}


/*
 * ############################
 * DEBOUNCING CODE 
 * ############################
 */ 

/*
 * INPUT: The port to check.
 * OUTPUT: None
 * DESCRIPTION: Updates the global debounced state. This function
 * should be called in a ISR a set rate using the hardware timer.
 */
static inline void debounce_switch() {
    uint8_t i, j;
    db_state[idx] = PINB & 0xFF;
    idx+=1;
    j = 0xff;    
    /*Loop through a number of checks*/
    for(i = 0; i < MAX_CHECKS; i++) {
        j = j & db_state[i];
    }
    debounced_state = j;

    if(idx >= MAX_CHECKS) {
        idx = 0;
    }
}



/*Setup the timer0 on the AVR*/
static inline void init_timer0() { 
    /*Zero out the tick_count var*/
    tick_count = 0;

    /*config to normal mode.*/
    TCCR0A = 0x00;  //stop timer
    TCCR0B = 0x00;  //zero timer

    /*set prescaler*/
    //Set to div64
    TCCR0B |= (1<<CS01)|(1<<CS00);

    
    /*Enable global interrupts*/
    sei();

    /*Enabling timer0 interrupt*/
    TCNT0 = 0;
    TIMSK0 |= (1<<TOIE0);
}


//ISR(TIMER0_OVF_vect)

/*The interrupt service routine for the timer0*/
ISR(TIM0_OVF_vect)
{
    /*Disable global interrupts*/
    cli();

    /*Check the state of the switches*/
    debounce_switch();

    /*Update the tick_count*/
    if(btn1.timer_enabled && btn1.pressed_ticks <= UINT8_MAX){
        btn1.pressed_ticks += 1;
    }

    if(btn2.timer_enabled && btn2.pressed_ticks <= UINT8_MAX){
        btn2.pressed_ticks += 1;
    }


    /*Re-Enable global interrupts*/
    sei();
}
