/*
 * Plumbing Controller. Sensor board.
 *
 * External EEPROM storage driver.
 *
 * (C)2026 St(u)dio of Computer Games
 * Alexander Ozumenko <scg@stdio.ru>
 */

#include <avr/io.h>
#include <util/delay.h>

#include <stdint.h>
#include <string.h>

#include "eeprom.h"

// FIXME: for debug only
//#include <stdio.h>



static uint8_t cur_block_idx;
static uint16_t cur_block_cnt;
static eeprom_block_t cur_block;



//static void eeprom_block_dump(const eeprom_block_t *block_p)
//{
//    printf("    sign: 0x%04" PRIx16 "\r\n", block_p->sign);
//    printf("     cnt: 0x%04" PRIx16 "\r\n", block_p->cnt);
//    printf(" val_fs1: %" PRIu32 "\r\n", block_p->val_fs1);
//    printf(" val_fs2: %" PRIu32 "\r\n", block_p->val_fs2);
//    printf("    rsvd: %02x %02x %02x %02x\r\n",
//           block_p->rsvd0, block_p->rsvd1,
//           block_p->rsvd2, block_p->rsvd3);
//}


static void eeprom_block_read(uint8_t block_idx, eeprom_block_t *block_p)
{
    uint8_t address = block_idx * EEPROM_BLOCK_SIZE;
    uint8_t *data_p = (void *)block_p;
    int i;

    /* START */
    TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);
    while (!(TWCR & _BV(TWINT)));

    /* device address (0xA0) + write flag */
    TWDR = 0b10100000;
    TWCR = _BV(TWINT) | _BV(TWEN);
    while (!(TWCR & _BV(TWINT)));

    /* send block address */
    TWDR = address;
    TWCR = _BV(TWINT) | _BV(TWEN);
    while (!(TWCR & _BV(TWINT)));

    /* REPEATED START (to switch to reading mode) */
    TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);
    while (!(TWCR & _BV(TWINT)));

    /* device address (0xA0) + read flag */
    TWDR = 0b10100001;
    TWCR = _BV(TWINT) | _BV(TWEN);
    while (!(TWCR & _BV(TWINT)));

    /* read data */
    for (i = 0; i < EEPROM_BLOCK_SIZE; i++) {
        if (i < (EEPROM_BLOCK_SIZE - 1)) {
            TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWEA);
        } else {
            TWCR = _BV(TWINT) | _BV(TWEN);
        }
        while (!(TWCR & _BV(TWINT)));
        data_p[i] = TWDR;
    }

    /* STOP */
    TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);
}

void eeprom_block_write(uint8_t block_idx, eeprom_block_t *block_p)
{
    uint8_t address = block_idx * EEPROM_BLOCK_SIZE;
    uint8_t *data_p = (void *)block_p;
    int i;

    /* START */
    TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);
    while (!(TWCR & _BV(TWINT)));

    /* device address (0xA0) + write flag */
    TWDR = 0b10100000;
    TWCR = _BV(TWINT) | _BV(TWEN);
    while (!(TWCR & _BV(TWINT)));

    /* send block address */
    TWDR = address;
    TWCR = _BV(TWINT) | _BV(TWEN);
    while (!(TWCR & _BV(TWINT)));

    /* write data */
    for (i = 0 ; i < EEPROM_BLOCK_SIZE; i++) {
        TWDR = data_p[i];
        TWCR = _BV(TWINT) | _BV(TWEN);
        while (!(TWCR & _BV(TWINT)));
    }

    /* STOP */
    TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);
    _delay_ms(10);
}


static void eeprom_get_last_block()
{
    uint8_t block_idx;

    cur_block_idx = cur_block_cnt = 0;

    for (block_idx = 0; block_idx < EEPROM_BLOCKS_NUM; block_idx++) {
        eeprom_block_read(block_idx, &cur_block);
//        printf("block %d\r\n", block_idx);
//        eeprom_block_dump(&cur_block);

        if (cur_block.sign != EEPROM_BLOCK_SIGN + block_idx) {
            break;
        }

        if ((block_idx > 0) && (cur_block.cnt != cur_block_cnt + 1)) {
            break;
        }
        cur_block_idx = block_idx;
        cur_block_cnt = cur_block.cnt;
    }

    if (cur_block_idx == 0 && cur_block_cnt == 0) {
        /* empty EEPROM - init first block */
        memset((void *)&cur_block, 0, sizeof(cur_block));
        cur_block.sign = EEPROM_BLOCK_SIGN;
        cur_block.cnt = 0;
        eeprom_block_write(cur_block_idx, &cur_block);

        // TODO: check eeprom on write
    } else {
        /* read last block */
        eeprom_block_read(cur_block_idx, &cur_block);
    }

//    printf("LAST block\r\n");
//    eeprom_block_dump(&cur_block);
}

// TODO: test for block counter overflow (%32)
static void eeprom_update_block()
{
    cur_block_idx = (cur_block_idx + 1) % EEPROM_BLOCKS_NUM;
    cur_block_cnt++;

    cur_block.sign = EEPROM_BLOCK_SIGN + cur_block_idx;
    cur_block.cnt = cur_block_cnt;
    eeprom_block_write(cur_block_idx, &cur_block);

//    printf("write block %d\r\n", cur_block_idx);
//    eeprom_block_dump(&cur_block);
}



/*** Interface functions */

void eeprom_init()
{
    cur_block_idx = 0;

    /* set F_OSC/16 clock rate */
    TWBR = 0;
    TWSR = 0;

    // reset first block
//    if (0) {
//        cur_block.sign = 0xffff;
//        eeprom_block_write(cur_block_idx, &cur_block);
//    }

    eeprom_get_last_block();
}


uint32_t eeprom_get_fs1()
{
    return cur_block.val_fs1;
}


uint32_t eeprom_get_fs2()
{
    return cur_block.val_fs2;
}


void eeprom_set_fs1(uint32_t val)
{
    cur_block.val_fs1 = val;
    eeprom_update_block();
}


void eeprom_set_fs2(uint32_t val)
{
    cur_block.val_fs2 = val;
    eeprom_update_block();
}
