/*
 * gat_i2c.c
 *
 * implements a shoddy i2c slave for the addon header.
 * maximum speed 100KHz tested.
 * note: not yet tested.
 *
 * data is segmented by page. read always starts from byte 0 of selected page.
 *
 * write begins with 1 byte page index, followed by offset / index, followed by data.
 *
 *
 * pages:
 *
 * - general: user input emulation and config functions.
 *   data size is 2 bytes.
 *   - 0: button input push mask. 0b0000_TL_TR_BR_BL.
 *   - 1: button release mask.
 *   - 2: nametag i2c clear timeout in frames. reverts to programmed name at 0. 0xff to disable.
 *   not yet implemented.
 *
 * - nametag: live update the nae shown in nametag mode.
 *   data size 16 bytes. byte 0 is length, bytes 1-15 are name data.
 *   this does not change the nametag in settings / nvram, only the live update.
 *   any update to this page will set general[1] to 0x20 if general[1] is not set to 0xff.
 *   if length == 0, normal name will be shown. for empty screen, send a space character with length = 1.
 *   example data:
 *   0x84 (addr), 0x01 (nametag), 0x00 (start at first byte), 0x06 (name is 6 chars long), "Hello!"
 *
 * - raw: send raw data to the oled. send in 128 byte chunks.
 *   not yet implemented.
 */

#include <CH59x_common.h>
#include "global.h"

#include "gat_gpio.h"

#include "comm/soft_i2c_slave.h"

#include <stdint.h>



#define GAT_ADDR_BASE           0x30
#define GAT_I2C_SPEED           100000

#define GAT_DATA_SIZE           128

#define GAT_PAGE_GENERAL        0
#define GAT_PAGE_NAMETAG_TEXT   1
#define GAT_PAGE_RAW1           2
#define GAT_PAGE_RAW2           3
#define GAT_PAGE_RAW3           4
#define GAT_PAGE_RAW4           5

#define GAT_PAGESIZE_GENERAL    3
#define GAT_PAGESIZE_NAMETAG    16



struct I2CSoftSlave addon_i2c;



enum {
    I2C_STATE_STARTING,
    I2C_STATE_READ,
    I2C_STATE_OFFSET,
    I2C_STATE_WRITE
};


static uint8_t i2c_state;

static uint8_t gat_page = 0;
static uint8_t gat_offset;

static uint8_t page_general[GAT_PAGESIZE_GENERAL];



// page functions
static uint8_t gat_read(uint8_t *page, uint8_t maxlen)
{
    if (gat_offset > maxlen) return 0xff;
    else return page[gat_offset++];
}

uint8_t gat_page_general_get(uint8_t idx)
{
    if (idx > sizeof(page_general)) return 0;
    else return page_general[idx];
}

uint8_t gat_page_general_set(uint8_t idx, uint8_t dat)
{
    if (idx >= sizeof(page_general)) return I2CSS_NACK;
    page_general[idx] = dat;
    return I2CSS_OK;
}


// i2c comms functions
void addon_i2c_start_cb()
{
    i2c_state = I2C_STATE_STARTING;
}

// master reading from addon
__HIGH_CODE
uint8_t addon_i2c_read_cb()
{
    switch (i2c_state) {
        case I2C_STATE_STARTING: {
            i2c_state = I2C_STATE_READ;
            gat_offset = 0;
        }
        // no break

        case I2C_STATE_READ: {
            // can't read past end of buffer; just send 0xff
            switch (gat_page) {
                case GAT_PAGE_GENERAL: {
                    return gat_read(page_general, GAT_PAGESIZE_GENERAL);
                }
                case GAT_PAGE_NAMETAG_TEXT: {
                    // to implement
                    break;
                }
                case GAT_PAGE_RAW1:
                case GAT_PAGE_RAW2:
                case GAT_PAGE_RAW3:
                case GAT_PAGE_RAW4: {
                    // to implement
                    break;
                }
            }

            // data wasn't sent above, so send empty byte
            return 0xff;
        }

        default: {
            return 0;
        }
    }
}

// master writing to addon
__HIGH_CODE
int8_t addon_i2c_write_cb(uint8_t dat)
{
    int8_t ret = I2CSS_OK;

    switch (i2c_state) {
        case I2C_STATE_STARTING: {
            // master wrote our address
            gat_page = dat;
            i2c_state = I2C_STATE_OFFSET;
            break;
        }

        case I2C_STATE_OFFSET: {
            gat_offset = dat;
            i2c_state = I2C_STATE_WRITE;

            if (gat_offset > GAT_DATA_SIZE) {
                gat_offset = GAT_DATA_SIZE;
                ret = I2CSS_NACK;
            }

            break;
        }

        case I2C_STATE_WRITE: {
            if (gat_offset >= GAT_DATA_SIZE) {
                ret = I2CSS_NACK;
                break;
            }

            switch (gat_page) {
                case GAT_PAGE_GENERAL: {
                    ret = gat_page_general_set(gat_offset, dat);
                    break;
                }
                case GAT_PAGE_NAMETAG_TEXT: {
                    // to implement
                    ret = I2CSS_NACK;
                    break;
                }
                case GAT_PAGE_RAW1:
                case GAT_PAGE_RAW2:
                case GAT_PAGE_RAW3:
                case GAT_PAGE_RAW4: {
                    // to implement
                    ret = I2CSS_NACK;
                    break;
                }
            }

            gat_offset++;
        }

        default: {
            return I2CSS_NACK;
        }
    }

    return ret;
}

void addon_i2c_err_cb(uint8_t error)
{

}


void addon_i2c_init()
{
    // determine I2C address from GP pin identification
    addon_i2c.addr = gat_id + GAT_ADDR_BASE;

    // roughly guess bit time length for expected speed
    addon_i2c.bit_time = GetSysClock() / GAT_I2C_SPEED / 2;

    // set up callbacks
    addon_i2c.start_cb = addon_i2c_start_cb;
    addon_i2c.rd_cb = addon_i2c_read_cb;
    addon_i2c.wr_cb = addon_i2c_write_cb;
    addon_i2c.err_cb = addon_i2c_err_cb;

    // configure i2c pins and pin interrupt
    i2css_init();
}

void addon_i2c_irq_cb()
{
    SetSysClock(SYSCLK_FREQ_USEI2C);
    i2css_start(&addon_i2c);
    SetSysClock(SYSCLK_FREQ_IDLE);
}
