#include "adc.h"
#include "defines.h"
#include "interrupts.h"
#include "m95128.h"
#include "max31329.h"
#include "ndef.h"
#include "power_mgmt.h"
#include "rfm69.h"
#include "st25dv.h"
#include "states.h"

#if DO_UART
#include "uart.h"
#endif

#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <stdbool.h>
#include <stdio.h>
#include <util/atomic.h>
#include <util/delay.h>

#if ITERATING
#define SEND_INTERVAL 1
#else
#define SEND_INTERVAL 15
#endif

#define WHEEL_COUNT_SLOTS 15

// Erased EEPROM reads back as 0xFF; anything else is a real page count.
#define EEPROM_LAST_PAGE_UNINIT 0xFF

uint16_t self_value;
volatile uint8_t is_debouncing = 0;
volatile bool increment_minute_index = false;
volatile bool increment_wheel_count = false;
volatile uint8_t index_wheel_count = 0;
volatile uint16_t total_wheel_counts[WHEEL_COUNT_SLOTS];

RTC_RFM69_STATUS rtc_rfm69_status;

ISR(INT0_vect) {
    // The RTC holds INTB low until its flag registers are read, and this is a
    // level-triggered interrupt, so mask it here and let main re-arm it once
    // the RTC has released the line.
    EIMSK &= ~(1 << INT0);
#if DO_UART
    uart_sendString("\t\t\t\tMINUTE INTERRUPT\n");
#endif
    increment_minute_index = true;
}

ISR(INT1_vect) {

#if ITERATING
    increment_minute_index = true;
#endif

#if DO_UART
    uart_sendString("\t\t\t\tREED INTERRUPT\n");
#endif

    if (!is_debouncing) {
        if (index_wheel_count < WHEEL_COUNT_SLOTS) {
            total_wheel_counts[index_wheel_count]++;
        }
        is_debouncing = 1;
        // Mask INT1 for the debounce window: the magnet holds the reed closed
        // (and the pin low) for far longer than one revolution's worth of
        // bounce, and a level-triggered interrupt would retrigger continuously.
        EIMSK &= ~(1 << INT1);
        wdt_isr_enable();
    }
}

ISR(WDT_vect) {
    is_debouncing = 0;
    wdt_isr_disable();
    reed_interrupt_enable();
}

void start_sleeping(void) {
    spi_eeprom_select(false);
    spi_rfm69_select(false);
    rfid_set_low_power_down(true);
    rfid_set_i2c_power(false);
    ldo_set_state(false);
    _delay_ms(10);

    set_sleep_mode(SLEEP_MODE_PWR_DOWN);

    cli();
    // Don't sleep through work that arrived while we were dropping the rails.
    // Testing the flag with interrupts off, then sei() immediately before
    // sleep_cpu(), is the avr-libc idiom that closes that race -- and
    // sleep_bod_disable() is a timed sequence, so it belongs here and not
    // before sleep_enable() where it had no effect at all.
    if (!increment_minute_index) {
        sleep_enable();
        sleep_bod_disable();
        sei();
        sleep_cpu();
        sleep_disable();
    }
    sei();
}

uint16_t get_battery_reading(void) {
    adc_Enable();
    adc_GetConversion(14);
    adc_GetConversion(14);
    self_value = adc_GetConversion(14);
    adc_Disable();
    return self_value;
}

// i2c Addresses

// RTC
// 0x68 (0xD0 W) (0xD1 R)

// NFC
// 0x2D (0x5A W) (0x5B R)
// 0x53 (0xA6 W) (0xA7 R)
// 0x57 (0xAE W) (0xAF R)

int main(void) {
    ldo_set_state(true);
    _delay_ms(10);
    init_pins();
#if DO_UART
    uart_init();
    uart_sendString("---- STARTING ----\n");
    uart_wait_until_sent();
#endif

    i2c_init();

    init_spi();
    adc_Initialize();


    set_up_reed_interrupt();
    set_up_minute_interrupt();
#if DO_UART
    uart_sendString("Set up AVR interrupts\n");
#endif

    rtc_set_per_minute_alarm();
    rtc_set_alarm_config();
    rtc_enable_interrupts();
    rtc_read_interrupt_register();
    rtc_read_status_register();
#if DO_UART
    uart_sendString("Set up RTC interrupts\n");
#endif



    rfm69_init();
#if DO_UART
    uart_sendString("Initialized RFM69\n");
#endif

    // Only initialise the spool pointer when it has never been written --
    // clearing it unconditionally would discard every unsent message across a
    // reset.
    if (get_last_page() == EEPROM_LAST_PAGE_UNINIT) {
        write_last_page_value(0);
    }
#if DO_UART
    uart_sendString("Set up last page value for SPI flash\n");
#endif

    for (uint8_t c = 0; c < WHEEL_COUNT_SLOTS; c++) {
        total_wheel_counts[c] = 0;
    }



    // Get the nugget's name and wheel diameter
    IDENTIFIER = get_nugget_data();
#if DO_UART
    uart_sendString("Got nugget data from RFID\n");
#endif



    // Request time from radio
    rtc_rfm69_status = set_time_from_rfm69(IDENTIFIER);
    if (rtc_rfm69_status == RTC_RFM69_SET_TIME_SUCCESS) {
        for (int i = 0; i < 5; i++) {
            led_1_set_state(true);
            _delay_ms(90);
            led_1_set_state(false);
            _delay_ms(10);
        }
    } else {
        for (int i = 0; i < 5; i++) {
            led_1_set_state(true);
            _delay_ms(10);
            led_1_set_state(false);
            _delay_ms(90);
        }
    }
#if DO_UART
    if (rtc_rfm69_status == RTC_RFM69_SET_TIME_FAILED) {
        uart_sendString("Failed to get time \n");
    } else {
        uart_sendString("Success in get time \n");
    };

#endif


    get_battery_reading();
    while (1) {


        spi_rfm69_select(false);
        spi_eeprom_select(false);

        start_sleeping();

        // Short critical sections around the shared variables only. The old
        // blanket cli() stayed in force through the whole radio/EEPROM
        // sequence, so every reed pulse in that multi-second window was lost.
        bool minute_elapsed;
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
            minute_elapsed = increment_minute_index;
            increment_minute_index = false;
        }

        if (minute_elapsed) {
#if DO_UART
            uart_sendString("In minute index\n");
#endif
            uint8_t slot;
            ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
                if (index_wheel_count < WHEEL_COUNT_SLOTS) {
                    index_wheel_count += 1;
                }
                slot = index_wheel_count;
            }

            // The I2C rail has to be back up before we touch the RTC: sleeping
            // dropped both the LDO and the tag's supply.
            ldo_set_state(true);
            rfid_set_i2c_power(true);
            _delay_ms(1);

            rtc_read_interrupt_register();
            rtc_read_status_register();

            // Reading the flags releases INTB, so INT0 can safely be re-armed.
            minute_interrupt_enable();

            if (slot >= SEND_INTERVAL) {
                ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { index_wheel_count = 0; }
                rfm69_init();
                rfid_set_low_power_down(false);
                _delay_ms(1);
                IDENTIFIER = get_nugget_data();

                // Request time from radio if we don't have a good time stamp yet
                if (rtc_rfm69_status == RTC_RFM69_SET_TIME_FAILED) {
                    rtc_rfm69_status = set_time_from_rfm69(IDENTIFIER);
                }

                // Snapshot and clear the counters in one critical section so
                // a reed pulse landing mid-packet is neither lost nor double
                // counted.
                uint16_t counts_snapshot[WHEEL_COUNT_SLOTS];
                ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
                    for (uint8_t c = 0; c < WHEEL_COUNT_SLOTS; c++) {
                        counts_snapshot[c] = total_wheel_counts[c];
                        total_wheel_counts[c] = 0;
                    }
                }

                // Generate wheel counts message
                reset_txrx_struct(&TX_DATA);
                TX_DATA = generate_wheel_counts_message(
                        IDENTIFIER, rtc_read_time(), get_battery_reading(), counts_snapshot);

#if DO_UART
                uart_sendString("TX DATA Sent\n");
                uart_print_tx_rx_data(TX_DATA);
#endif


                DATA_SEND_STATUS result = send_message(TX_DATA);
                if (result == DATA_NOT_SENT) {
#if DO_UART
                    uart_sendString("       TX DATA not sent, writing to SPI\n");
#endif
                    write_struct_to_last_page(TX_DATA);
                }

                if ((result == DATA_SEND_SUCCESS) && (get_last_page() > 0)) {
                    reset_txrx_struct(&TX_DATA);
                    TX_DATA = read_struct_last_page();
                    TX_DATA.flags = MSG_RESENT_COUNTS;
                    _delay_ms(250);
                    result = send_message(TX_DATA);
#if DO_UART
                    uart_sendString("TX DATA From SPI Memory\n");
                    uart_print_tx_rx_data(TX_DATA);
#endif
                    // Only drop the spooled page once it is actually
                    // acknowledged, otherwise a failed retry loses the data.
                    if (result == DATA_SEND_SUCCESS) {
                        delete_last_page();
                    }
#if DO_UART
                    else {
                        uart_sendString("       SPI not sent\n");
                    }
#endif
                }
            }
        }
    }
}
