#include <atmel_start.h>
#include <stdlib.h>

#include <COAST.h>
#include <i2c_slave.h>
#include <flash_utils.h>
#include <float_utils.h>
#include <usb_debug.h>

const char MFR_ID[] = "CERN (BE/CO)";
const char MFR_MDL[] = "DI/OT MoniMod";
#if defined MMRTSB
const char MFR_REV[] = "0.2p-RTSB";
#elif defined MMFANT
const char MFR_REV[] = "0.2p-FANT";
#elif defined MMRATO
const char MFR_REV[] = "0.2p-RATO";
#elif defined MMPROT
const char MFR_REV[] = "0.2p-PROT";
#else
const char MFR_REV[] = "0.2p-ERRO";
#error Exactly one of MMRTSB, MMFANT, MMRATO or MMPROT has to be defined
#endif
const char MFR_LOC[] = "Geneva";
const char MFR_DAT[] = "211020";
const char MFR_SER[] = "123456789";

#ifdef USE_LINEAR16
const uint8_t vout_mode = 0x16; // linear mode, fixed 2^10 exponent
#else
const uint8_t vout_mode = 0xe0; // not compliant to the standard, set mode bits to (invalid) 3'b111
#endif

void page_chk();
uint8_t page_tmp;
uint16_t page __xMR;

extern uint16_t use_pec __xMR;
uint8_t use_pec_tmp;
void set_pec();

void read_status_b();
void read_status_cml();
extern uint8_t status_b;
extern uint8_t status_cml;

void accvolt();
void acccurr();
void accpowr();
uint16_t curpage_volt;
uint16_t curpage_curr;
uint16_t curpage_powr;

void query_prp();
void query_cln();
uint8_t query_r;

#ifdef MMRTSB
#define MAX_PAGE 3
#else
#define MAX_PAGE 2
#endif

extern uint16_t volts_lin[MAX_PAGE+1];
extern uint16_t currs_lin[MAX_PAGE+1];
extern uint16_t powrs_lin[MAX_PAGE+1];
extern uint16_t temps_lin[3];
extern uint16_t frpms_lin[3];

extern uint32_t seconds_up;

uint32_t TMR_ERROR_CNT;

static uint8_t dummy_byte;

void set_frpms();
uint16_t setfrpms_lin[3];
extern uint16_t setfrpms[3] __xMR;

void set_tc_curve();
uint16_t temp_curve_points_data[7];
extern float temp_curve_points_x[3][3] __xMR;
extern float temp_curve_points_y[3][3] __xMR;

void set_tc_matrix();
void set_tc_onoff();
uint16_t temp_matrix_row[3]; // these rows mix the 3 temps separately for each fan
uint8_t tc_on;
extern float temp_matrix[3][3] __xMR;
extern uint16_t temp_control_on __xMR;

uint8_t fan_config_1_2;
uint8_t fan_config_3_4;
void fan_config();
void get_fan_configs();
extern uint16_t fan_installed[3] __xMR;
extern uint16_t fan_cmdrpm[3] __xMR;
extern uint16_t fan_ppr[3] __xMR;

void uc_reset();
void boot_new_fw();

static const int8_t cmd_data_lengths[] = {
        1,                         // 0x00
        -1,                        // 0x1A
        1,                         // 0x20
        1,                         // 0x78
        1,                         // 0x7E
        2,                         // 0x8B
        2,                         // 0x8C
        2,                         // 0x8D
        2,                         // 0x8E
        2,                         // 0x8F
        2,                         // 0x90
        2,                         // 0x91
        2,                         // 0x92
        2,                         // 0x96
        -(int8_t)sizeof(MFR_ID),   // 0x99
        -(int8_t)sizeof(MFR_MDL),  // 0x9A
        -(int8_t)sizeof(MFR_REV),  // 0x9B
        -(int8_t)sizeof(MFR_LOC),  // 0x9C
        -(int8_t)sizeof(MFR_DAT),  // 0x9D
        -(int8_t)sizeof(MFR_SER),  // 0x9E
        1,                         // 0x3A
        2,                         // 0x3B
        2,                         // 0x3C
        1,                         // 0x3D
        2};                        // 0x3E

// only block write-block read process calls modify the length
static int8_t cmd_data_length_query = -1;

static const int8_t ext_cmd_data_lengths[] = {
        1,  // 0x05
        1,  // 0x06
        4,  // 0x10
        4,  // 0xA0
        1,  // 0xB0
        13, // 0xC0
        7,  // 0xC1
        1}; // 0xC4

static const cmd_t cmds_cmds[] = (cmd_t[]){
        {0x0000, (int8_t *)&cmd_data_lengths[0], (uint8_t *)&page_tmp,     (fp_t)NULL, &page_chk,  (fp_t)NULL, QUERY_WR | QUERY_RD | QUERY_FMT_8B, 0},
        {0x001A, (int8_t *)&cmd_data_length_query, (uint8_t *)&query_r,  (fp_t)NULL, &query_prp, (fp_t)NULL, QUERY_SUP, 1},
        {0x0020, (int8_t *)&cmd_data_lengths[2], (uint8_t *)&vout_mode,  (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD, 1},
        {0x0078, (int8_t *)&cmd_data_lengths[3], (uint8_t *)&status_b, (fp_t)NULL, (fp_t)NULL, &read_status_b, QUERY_RD, 0},
        {0x007E, (int8_t *)&cmd_data_lengths[4], (uint8_t *)&status_cml, (fp_t)NULL, (fp_t)NULL, &read_status_cml, QUERY_RD, 0},
        {0x008B, (int8_t *)&cmd_data_lengths[5], (uint8_t *)&curpage_volt, &accvolt, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_LIN, 0},
        {0x008C, (int8_t *)&cmd_data_lengths[6], (uint8_t *)&curpage_curr, &acccurr, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_LIN, 0},
        {0x008D, (int8_t *)&cmd_data_lengths[7], (uint8_t *)&temps_lin[0], (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_LIN, 0},
        {0x008E, (int8_t *)&cmd_data_lengths[8], (uint8_t *)&temps_lin[1], (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_LIN, 0},
        {0x008F, (int8_t *)&cmd_data_lengths[9], (uint8_t *)&temps_lin[2], (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_LIN, 0},
        {0x0090, (int8_t *)&cmd_data_lengths[10], (uint8_t *)&frpms_lin[0], (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_LIN, 0},
        {0x0091, (int8_t *)&cmd_data_lengths[11], (uint8_t *)&frpms_lin[1], (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_LIN, 0},
        {0x0092, (int8_t *)&cmd_data_lengths[12], (uint8_t *)&frpms_lin[2], (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_LIN, 0},
        {0x0096, (int8_t *)&cmd_data_lengths[13], (uint8_t *)&curpage_powr, &accpowr, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_LIN, 0},
        {0x0099, (int8_t *)&cmd_data_lengths[14], (uint8_t *)&MFR_ID,   (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_NAN, 0},
        {0x009A, (int8_t *)&cmd_data_lengths[15], (uint8_t *)&MFR_MDL,  (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_NAN, 0},
        {0x009B, (int8_t *)&cmd_data_lengths[16], (uint8_t *)&MFR_REV,  (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_NAN, 0},
        {0x009C, (int8_t *)&cmd_data_lengths[17], (uint8_t *)&MFR_LOC,  (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_NAN, 0},
        {0x009D, (int8_t *)&cmd_data_lengths[18], (uint8_t *)&MFR_DAT,  (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_NAN, 0},
        {0x009E, (int8_t *)&cmd_data_lengths[19], (uint8_t *)&MFR_SER,  (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, QUERY_RD | QUERY_FMT_NAN, 0},
        {0x003A, (int8_t *)&cmd_data_lengths[20], (uint8_t *)&fan_config_1_2, &get_fan_configs, &fan_config, (fp_t)NULL, QUERY_WR | QUERY_FMT_NAN, 0},
        {0x003B, (int8_t *)&cmd_data_lengths[21], (uint8_t *)&setfrpms_lin[0], (fp_t)NULL, &set_frpms, (fp_t)NULL, QUERY_WR | QUERY_FMT_LIN, 0},
        {0x003C, (int8_t *)&cmd_data_lengths[22], (uint8_t *)&setfrpms_lin[1], (fp_t)NULL, &set_frpms, (fp_t)NULL, QUERY_WR | QUERY_FMT_LIN, 0},
        {0x003D, (int8_t *)&cmd_data_lengths[23], (uint8_t *)&fan_config_3_4, &get_fan_configs, &fan_config, (fp_t)NULL, QUERY_WR | QUERY_FMT_NAN, 0},
        {0x003E, (int8_t *)&cmd_data_lengths[24], (uint8_t *)&setfrpms_lin[2], (fp_t)NULL, &set_frpms, (fp_t)NULL, QUERY_WR | QUERY_FMT_LIN, 0},
        {0xFF05, (int8_t *)&ext_cmd_data_lengths[0], (uint8_t *)&dummy_byte, (fp_t)NULL, &boot_new_fw, (fp_t)NULL, 0, 0},
        {0xFF06, (int8_t *)&ext_cmd_data_lengths[1], (uint8_t *)&dummy_byte, (fp_t)NULL, &uc_reset, (fp_t)NULL, 0, 0},
        {0xFF10, (int8_t *)&ext_cmd_data_lengths[2], (uint8_t *)&seconds_up, (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, 0, 0},
        {0xFFA0, (int8_t *)&ext_cmd_data_lengths[3], (uint8_t *)&TMR_ERROR_CNT, (fp_t)NULL, (fp_t)NULL, (fp_t)NULL, 0, 0},
        {0xFFB0, (int8_t *)&ext_cmd_data_lengths[4], (uint8_t *)&use_pec_tmp, (fp_t)NULL, &set_pec, (fp_t)NULL, 0, 1},
        {0xFFC0, (int8_t *)&ext_cmd_data_lengths[5], (uint8_t *)&temp_curve_points_data, (fp_t)NULL, &set_tc_curve, (fp_t)NULL, 0, 0},
        {0xFFC1, (int8_t *)&ext_cmd_data_lengths[6], (uint8_t *)&temp_matrix_row, (fp_t)NULL, &set_tc_matrix, (fp_t)NULL, 0, 0},
        {0xFFC4, (int8_t *)&ext_cmd_data_lengths[7], (uint8_t *)&tc_on, (fp_t)NULL, &set_tc_onoff, (fp_t)NULL, 0, 0}};

cmd_space_t cmds = {
        sizeof(cmds_cmds)/sizeof(cmd_t),
        cmds_cmds
};

static uint32_t fw_write_buf[FLASH_ROW_SIZE/4];

void uc_reset()
{
#ifdef USB_ENABLE
        usbdc_detach();
        usbdc_stop();
        usbdc_deinit();
#endif
        NVIC_SystemReset();
}


void boot_new_fw()
{
        uint32_t *char_user_flash = (uint32_t *)&user_flash;
        user_flash_t *tmp_user_flash = (user_flash_t *)&fw_write_buf;

        /* backup existing user data */
        for (uint8_t i = 0; i < FLASH_ROW_SIZE/4; ++i)
                fw_write_buf[i] = char_user_flash[i];

        /* set 0xBEC0ABCD copy FW code */
        tmp_user_flash->copy_fw = 0xbec0abcd;

        /* copy modified block back to the flash */
        flash_write_row((uint32_t *)&user_flash, (uint32_t *)fw_write_buf);

        /* reset the uC */
        uc_reset();
}


void __xMR page_chk()
{
        if (page_tmp > MAX_PAGE)
                page = MAX_PAGE;
        else
                page = page_tmp;
}


void __xMR accvolt()
{
        curpage_volt = volts_lin[page];
}


void __xMR acccurr()
{
        curpage_curr = currs_lin[page];
}


void __xMR accpowr()
{
        curpage_powr = powrs_lin[page];
}


void query_prp()
{
        int16_t cmd_idx = in_addr_space(&cmds, query_r);
        if (cmd_idx != -1)
                query_r = QUERY_SUP | cmds.cmds[cmd_idx].query_byte;
        else
                query_r = 0;
}


void __xMR set_pec()
{
        use_pec = use_pec_tmp;
}

void read_status_b()
{
        status_b &= 0xFE;
}

void read_status_cml()
{
        status_cml = 0;
}

#if defined(MMFANT) || defined(MMPROT)

void __xMR fan_config()
{
        fan_installed[0] = (fan_config_1_2 >> 7) & 0x1;
        fan_cmdrpm[0] = (fan_config_1_2 >> 6) & 0x1;
        fan_ppr[0] = (fan_config_1_2 >> 4) & 0x3;

        fan_installed[1] = (fan_config_1_2 >> 3) & 0x1;
        fan_cmdrpm[1] = (fan_config_1_2 >> 2) & 0x1;
        fan_ppr[1] = fan_config_1_2 & 0x3;

        fan_installed[2] = (fan_config_3_4 >> 7) & 0x1;
        fan_cmdrpm[2] = (fan_config_3_4 >> 6) & 0x1;
        fan_ppr[2] = (fan_config_3_4 >> 4) & 0x3;

        uint32_t *char_user_flash = (uint32_t *)&user_flash;
        user_flash_t *tmp_user_flash = (user_flash_t *)&fw_write_buf;

        /* backup existing user data */
        for (uint8_t i = 0; i < FLASH_ROW_SIZE/4; ++i)
                fw_write_buf[i] = char_user_flash[i];

        /* set 0xBEC0ABCD copy FW code */
        for (uint8_t i = 0; i < 3; ++i) {
                tmp_user_flash->fan_installed[i] = fan_installed[i];
                tmp_user_flash->fan_cmdrpm[i] = fan_cmdrpm[i];
                tmp_user_flash->fan_ppr[i] = fan_ppr[i];
        }

        /* copy modified block back to the flash */
        flash_write_row((uint32_t *)&user_flash, (uint32_t *)fw_write_buf);
}

void __xMR get_fan_configs()
{
        fan_config_1_2 = (fan_installed[0] << 7) | (fan_cmdrpm[0] << 6) | (fan_ppr[0] << 4) |
                (fan_installed[1] << 3) | (fan_cmdrpm[1] << 2) | (fan_ppr[1]);
        fan_config_3_4 = (fan_installed[2] << 7) | (fan_cmdrpm[2] << 6) | (fan_ppr[2] << 4);
}


void __xMR set_frpms()
{
        setfrpms[0] = linear_to_float(setfrpms_lin[0]);
        setfrpms[1] = linear_to_float(setfrpms_lin[1]);
        setfrpms[2] = linear_to_float(setfrpms_lin[2]);

        uint32_t *char_user_flash = (uint32_t *)&user_flash;
        user_flash_t *tmp_user_flash = (user_flash_t *)&fw_write_buf;

        /* backup existing user data */
        for (uint8_t i = 0; i < FLASH_ROW_SIZE/4; ++i)
                fw_write_buf[i] = char_user_flash[i];

        /* set 0xBEC0ABCD copy FW code */
        for (uint8_t i = 0; i < 3; ++i)
                tmp_user_flash->setfrpms[i] = setfrpms[i];

        /* copy modified block back to the flash */
        flash_write_row((uint32_t *)&user_flash, (uint32_t *)fw_write_buf);
}


void __xMR set_tc_curve()
{
        uint8_t fan_n = temp_curve_points_data[0];
        for (uint8_t i = 0; i < 3; ++i) {
                temp_curve_points_x[fan_n][i] = linear_to_float(temp_curve_points_data[1 + 2*i]);
                temp_curve_points_y[fan_n][i] = linear_to_float(temp_curve_points_data[1 + 2*i + 1]);
        }
}


void __xMR set_tc_matrix()
{
        uint8_t fan_n = temp_matrix_row[0];
        for (uint8_t i = 0; i < 3; ++i) {
                temp_matrix[fan_n][i] = linear_to_float(temp_matrix_row[1 + i]);
        }
}


void __xMR set_tc_onoff()
{
        temp_control_on = tc_on;
}

#else

void fan_config()
{
}

void get_fan_configs()
{
}

void set_frpms()
{
}

void set_tc_curve()
{
}

void set_tc_matrix()
{
}

void set_tc_onoff()
{
}

#endif
