#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <iobuffer.h>
#include <crc32.h>
#include <hal_interface_flash.h>
#include "hal_env.h"
#include "hlog.h"
#define TAG "TAG_ENV"
#define BACKUP_MAGIC  0x5A5AA5A5


static int8_t   g_actflag		= -1;
static int8_t   g_modifyed_flag = 0;
static int8_t   g_inited 		= 0;
static OS_MUTEX g_env_mutex 	= {0};

static Env_Header g_env_header  = {0};
static char *	  g_env_values[ENV_CFG_MAX] = {0};        //all string are hv_malloc-ed


static inline int32_t hal_env_is_valid(int32_t index) {
    if ( index <= ENV_CFG_START || index >= ENV_CFG_MAX) {
        hloge("Invalid env index : %d\n", index);
        return 0;
    }
    return 1;
 }

static void hal_env_header_print(const char * prefix, const Env_Header*  header)
{
    hlogd("%s crc:%x age:%d version:%d\n", prefix,header->crc, header->age, header->version);
}

int32_t hal_env_mutex_lock()
{
    return os_mutex_lock(&g_env_mutex);
}

int32_t hal_env_mutex_unlock()
{
	return os_mutex_unlock(&g_env_mutex);
}

//load header and g_env_values from envbuff
static int32_t hal_env_load_head_values(const void * envbuff, Env_Header * header)
{
    //backup headers.
    memcpy(header, envbuff, sizeof(Env_Header));
    //hv_free old value
	int32_t i = 0;
    for (; i < ENV_CFG_MAX; i++) {
        if ( g_env_values[i]) {
            hv_free(g_env_values[i]);
            g_env_values[i] = NULL;
        }
    }

    //extract/parse all values.
    ENV_CFG e;
    const unsigned char * p = (const unsigned char *)envbuff + sizeof(Env_Header);
    int32_t index;
    int32_t values = 0;
    //envbuff is sth like header+3hello\02world\0(the end of valid data)+\0
    while ( (e = (ENV_CFG)*p) != ENV_CFG_START) {
        index = e;
        p++;                                    //p now point to string value start
        if (hal_env_is_valid(index)) {
            g_env_values[index] = strdup((const char *)p);            //clone a string for easy use
            values++;
        }
        while(*p) p++;  //to string end
        p+=1;           //skip '\0', p now is next ENV_CFG
    }
    return values;
}

int32_t hal_env_dump(){
	int32_t i = 1;
    for (; i < ENV_CFG_MAX; i++) {
        const char * value = g_env_values[i];
        if ( value && value[0]) {
            hlogd("#%d=%s\n", i, value);
        }
        else {
            hlogd("#%d not set.\n", i);
        }
    }
    return 0;
}
int32_t hal_env_save()
{
    iobuffer buf;
    iobuffer_init(&buf);
    unsigned char  key;
    unsigned char  zero = 0;
    int32_t hasZero = 0;

    if (!g_modifyed_flag)
       return 0;
    hal_env_mutex_lock();
    g_env_header.age++;
    //Note: the crc is not right at this time, will update later.
   iobuffer_appendData(&buf, &g_env_header, sizeof(g_env_header));
   int32_t i = 0;
    for (; i < ENV_CFG_MAX; i++) {
        key = i;
        char * value = g_env_values[i];
        if ( value && value[0] ) {
            iobuffer_appendData(&buf, &key, 1);
            iobuffer_appendData(&buf,value, strlen(value));
            iobuffer_appendData(&buf, &zero, 1);
            hasZero = 1;
        }
    }

    iobuffer_appendData(&buf,&zero, 1);
    if (!hasZero) {
        //write 0 again as no any value has written
        iobuffer_appendData(&buf,&zero, 1);
    }

    Env_Header* header = (Env_Header*)buf.iodata;
    //update crc, exclude crc(4 bytes), already contains last 00 2 bytes.
    g_env_header.crc = header->crc = crc32(0, buf.iodata + 4, buf.size - 4);

    if (0 == g_actflag) {
       hlogd("update env1 addr : %p, size=%d,age=%d\n", ENV_CFG_1, buf.size, g_env_header.age);
        hal_flash_write(ENV_CFG_1, buf.iodata, buf.size);
        g_actflag = 1;
    }
    else {
       hlogd("update env0 addr : %p, size=%d,age=%d\n", ENV_CFG_0, buf.size, g_env_header.age);
        hal_flash_write(ENV_CFG_0, buf.iodata, buf.size);
        g_actflag = 0;
    }
    g_modifyed_flag = 0;
    hal_env_mutex_unlock();
	return g_modifyed_flag;
}


int32_t hal_env_set_string(ENV_CFG e, const char * value)
{
    int32_t index = (int32_t)e;
    if (!hal_env_is_valid(index))
        return 0;
   
    //set both value and current not null for strcmp
    const char * current = hal_env_get_string(e, "");
    value = value ? value : "";

    if ( strcmp(current, value) == 0)
        return 0;    //no change

    g_modifyed_flag = 1;
        
    hv_free(g_env_values[index]);
    if ( *value != '\0' ) {
        g_env_values[index] = strdup(value);
    }else {
        g_env_values[index] = NULL;
    }
    return 1;
}

const char*  hal_env_get_string(ENV_CFG e, const char * def_val)
{
    int32_t index = (int32_t)e;
    if (!hal_env_is_valid(index))
        return def_val;

    const char * value = g_env_values[index];
    if (!value || value[0] == '\0')
        return def_val;

    return value;
}

int32_t hal_env_is_same_value(ENV_CFG e, const char * value){
  const char * val = hal_env_get_string(e, NULL);
  if (val == NULL || value == NULL)
  	return 0;
  return !strcmp(val, value);
}

int32_t hal_env_get_int(ENV_CFG e, int32_t def_val)
{
    const char * str = hal_env_get_string(e, NULL);
    if (str == NULL)
    {
		//hlogd("%s, e:%d, str NULL\n", __func__, e);
        return def_val;
    }
	//hlogd("%s, e:%d, str:%s\n", __func__, e, str);
    char *endptr;
    long ret = strtol(str, &endptr, 10);
    if ( *endptr != '\0' )
    {
        hlogd("env(%d) contains a invalid int32_t: %s\n", e, str);
        return def_val;
    }
    return (int32_t)ret;
}

int32_t hal_env_set_int(ENV_CFG e, int32_t value)
{
    char buf[64];
    sprintf(buf, "%d", value);
    return hal_env_set_string(e, buf);
}

//used by cli_command.cpp only, so no definition in .h file
extern void hal_env_clear() {
	int32_t i = ENV_CFG_START+1;
    for (; i < ENV_CFG_MAX; i++)
    {
        if ( g_env_values[i])
        {
            hv_free(g_env_values[i]);
            g_env_values[i] = NULL;
        }
    }
    // no need u-boot as well  
    g_modifyed_flag = 1;
}

int32_t hal_env_active_age_load(Env_Header*  header)
{
    const char* start = (const char*)header;
    const char* tail = start + ENV_SIZE - 1;

    //找到两个连续?，知道env有效长度的结?
    const char * p = start + sizeof(Env_Header);
	while( (p < tail) && (p[0] || p[1]))
        p++;

    if (p >= tail)
    {
        hloge("env data not found end,age=%d\n", header->age);
        return -1;
    }

    //p指向第一个结尾的0, 加上最后一0也参与crc验证，需要加2，然后去掉最前面的crc本身
    int32_t avalid = p - start + 2 - 4;
    uint32_t crc = crc32(0, &header->age, avalid);
    if ( crc != header->crc)
    {
        hloge("env data crc error, age:%d, len:%d, crc %X!=%X\n", header->age, avalid, crc, header->crc);
        return -1;
    }
    //Good
  // hlogd("env data len:%d,crc:%X,age:%d\n", avalid, crc, header->age);
    return avalid;
}

//find which address is the right env.
static int32_t find_right_env(char* buf0, char* buf1)
{
    int32_t actflag = -1;  //invalid value
    Env_Header*  header0 = (Env_Header* )buf0;
    Env_Header*  header1= (Env_Header* )buf1;

	hal_flash_read(ENV_CFG_0, buf0, ENV_SIZE);
	hal_flash_read(ENV_CFG_1, buf1, ENV_SIZE);

    if (header0->age >= header1->age)
    {
        if (hal_env_active_age_load(header0) > 0)
            actflag = 0;
        else if (hal_env_active_age_load(header1) > 0)
            actflag = 1;
    } else {
        if (hal_env_active_age_load(header1) > 0)
            actflag = 1;
        else if (hal_env_active_age_load(header0) > 0)
            actflag = 0;
    }
    return actflag;
}

int32_t hal_env_init()
{
	int32_t ret = -1;
	if (!g_inited)
	{
	    Env_Header*  env0 = NULL, * env1 = NULL;
	    Env_Header*  cur_env;
		os_mutex_create(&g_env_mutex);
	    env0 = (Env_Header*)hv_malloc(ENV_SIZE);
	    env1 = (Env_Header*)hv_malloc(ENV_SIZE);
	    if (!env0 || !env1 )
		{
	        hloge("memory alloc error!\n");
	        goto exit;
	    }

	    //read boot env
	    g_actflag = find_right_env((char *)env0, (char *)env1);
		if (g_actflag < 0)  
		{
		  memset(env0, 0, ENV_SIZE);
	      env0->age = 1;
	      env0->version = 1;

	      env0->crc = crc32(0, &env0->age, sizeof(Env_Header) - 4 + 2);   // exclude crc(4 bytes), plus 2 bytes for 00
	      hal_flash_write(ENV_CFG_1, (char *)env0,  sizeof(Env_Header) + 2);

	      env0->crc = crc32(0, &env0->age, sizeof(Env_Header) - 4 + 2);   // exclude crc(4 bytes), plus 2 bytes for 00
	      hal_flash_write(ENV_CFG_0,  (char *)env0, sizeof(Env_Header) + 2);

	      g_actflag = 0;
		}

		cur_env = (g_actflag == 0) ? env0 : env1;
	    int32_t valid_items = hal_env_load_head_values(cur_env, &g_env_header);


		char buf[128];
	    snprintf(buf, sizeof(buf), "Active env=%d items=%d", g_actflag, valid_items);
	    hal_env_header_print(buf, cur_env);

		ret = 0;
		g_inited = 1;
	exit:
	    hv_free(env0);
	    hv_free(env1);
   }else{
   		ret = 0;
   }
   return ret;
}

int32_t hal_env_deinit(){
	if (g_inited){
		g_inited = 0;
		os_mutex_delete(&g_env_mutex);
	}
	return 0;
}

static int32_t hal_env_cmd(int32_t argc, char ** argv)
{
    hal_env_mutex_lock();
	if ( argc == 1 ) {
		hal_env_dump();
	}
	else if ( !strcmp(argv[1], "get")) {
		const char * key = argv[2];
		if (!key) {
			hal_env_dump();
		}
		else {
			hlogd("%s\n", hal_env_get_string((ENV_CFG)atoi(key), ""));
		}
	}
	else if ( !strcmp(argv[1], "save")) {
		hal_env_save();
	}
	else if ( !strcmp(argv[1], "set")) {
		const char * key = argv[2];
		const char * value = argv[3];
		if (key){
			hal_env_set_string((ENV_CFG)atoi(key), value);
		}
	}
	else if ( !strcmp(argv[1], "clear")) {
		hal_env_clear();
	}else if ( !strcmp(argv[1], "both")) {
		hal_env_dump();
	}
	else {
		hlogd("Unknown command: <%s>\n", argv[1]);
	}
    hal_env_mutex_unlock();
	return 0;
}