/* fields.c */

#include <stddef.h>
#include <string.h>
#include <avr/io.h>
#include <avr/pgmspace.h>

#include "common.h"
#include "fields.h"
#include "menu.h"
#include "config.h"

uint8_t field_row; // XXX ?
uint8_t field_values[DISPLAY_CHARS];

static uint8_t field_index(char c)
{
    return ((uint8_t)(c) & (uint8_t)15)-1;
}
//#define field(c) fields[((uint8_t)(c) & (uint8_t)15)-1]
#define field(c) fields[field_index(c)]

uint8_t field_length(char c)
{
    return field(c).length;
}

bool field_is_text(char c)
{
    return field(c).display == NULL;
}

PGM_P field_text(char c, uint8_t n)
{
    return get_string(&field(c).display[n]);
}

uint8_t field_display_entries(char c)
{
    return field(c).display_entries;
}

bool is_field(char c) {
    return (c > 0) && (c < 32);
}

bool is_editable(char c) {
    return (c > 16) && (c < 32);
}

PGM_P get_string(PGM_P const *addr)
{
    PGM_P ptr;
    memcpy_P(&ptr, addr, sizeof(PGM_P));
    return ptr;
}

uint8_t find_field(uint8_t n)
{
    char c;
    uint8_t index = 0;
    char type = 0;

    while ((c = menu_getchar(index)) != '\0') {
	index++;
	if (type && (type == c))
	    continue;
	if (is_field(c)) {
	    type = c;
	    if (n == 0)
		return index-1;
	    n--;
	} else {
	    type = 0;
	}
    }
    return 255;
}

uint8_t find_field_length_by_place(uint8_t index)
{
    uint8_t orig_index = index;
    char type = menu_getchar(index);
    uint8_t tlen = field_length(type);

    while (menu_getchar(index) == type)
	index += tlen;

    return index - orig_index;
}

uint8_t find_field_length(uint8_t index)
{
    return find_field_length_by_place(find_field(index));
}

uint8_t find_field_number(uint8_t index)
{
    uint8_t n, p;
    for (n = 0; (p = find_field(n)) != 255; n++)
    {
	if (p == index)
	    return n;
    }
    return 255;
}

uint8_t find_editable_field(uint8_t index, bool left)
{
    int8_t increment = left?(-1):1;
    char c;
    uint8_t len;

    if (index >= 255)
	return 255;

    len = strlen_P(get_string(&menu_current_p->text[field_row]));

    if (left && (index >= len))
	index = len-1;

    while ((c = menu_getchar(index)) != '\0') {
	if (is_editable(c))
	    return index;
	index += increment;
    }
    return 255;
}

void write_field_enum(uint8_t field, uint8_t val)
{
    uint8_t p = find_field(field);
    field_values[p] = val;
}

uint8_t read_field_enum(uint8_t field)
{
    uint8_t p = find_field(field);
    return field_values[p];
}

uint16_t read_field_uint16(uint8_t field)
{
    uint8_t i;
    uint16_t val = 0;

    uint8_t p = find_field(field);
    uint8_t l = find_field_length(field);

    for (i = 0; i < l; i++) {
	val = val * 10;
	val += field_values[p+i];
    }

    return val;
}

void write_field_uint16(uint8_t field, uint16_t val)
{
    uint8_t i;

    uint8_t p = find_field(field);
    uint8_t l = find_field_length(field);

    for (i = l; i; i--) {
	field_values[p+i-1] = val % 10;
	val = val / 10;
    }
}

void write_field_integer_part(uint8_t field, int32_t val)
{
    if (val < 0)
	val = -val;

    write_field_uint16(field, val >> 12);
}

void write_field_fractional_part(uint8_t field, int32_t val)
{
    uint8_t i;
    uint16_t v;

    uint8_t p = find_field(field);
    uint8_t l = find_field_length(field);

    if (val < 0)
	val = -val;

    v = val & 0xfff;

    for (i = 0; i < l; i++) {
	uint8_t digit;
	v = v * 10;
	digit = (v >> 12) % 10;
	v = v - (digit << 12);
	field_values[p+i] = digit;
    }
}

int32_t round_dp(int32_t n, uint8_t dp)
{
    int32_t rval = 0x800;
    int i;

    /* Round away from zero. Remove this bit
     * to round up
     */
    if (n < 0)
	rval = -rval;

    for (i = 0; i < dp; i++)
	rval = rval / 10;

    return n + rval;
}

uint32_t read_field_fracint(int8_t index, bool frac)
{
    int32_t val, v_int, v_frac;
    uint8_t sign = read_field_enum(index++);

    v_int = read_field_uint16(index++);

    if (frac) {
	uint8_t l_frac;
	v_frac = read_field_uint16(index);
	l_frac = find_field_length(index);

	v_frac = v_frac << 12;
	while (l_frac--)
	    v_frac = v_frac / 10;
    } else {
        v_frac = 0;
    }

    val = (v_int << 12) + v_frac;

    if (sign == FIELD_SIGN_NEGATIVE)
	val = -val;

    return val;
}

void write_field_fracint(uint8_t index, bool sign, bool frac, int32_t val)
{
    uint8_t l_frac;

    l_frac = find_field_length(index+1+(sign?1:0));

    val = round_dp(val, frac?l_frac:0);

    if (sign)
	write_field_enum(index++, (val<0)?FIELD_SIGN_NEGATIVE:FIELD_SIGN_POSITIVE);

    write_field_integer_part(index++, val);
    if (frac)
	write_field_fractional_part(index, val);
}


void write_field_temperature(uint8_t index, bool frac, temp_t k_temp)
{
    int32_t temp = temperature_from_kelvin(k_temp);
    write_field_fracint(index, TRUE, frac, temp);
    write_field_enum(index+2+(frac?1:0), config.units);
}

