#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <mtd/mtd-user.h>
#include "hal.h"
#include "ratelimit.h"
#include "hal_interface_flash.h"

#define TAG "TAG_FLASH"
#include "hlog.h"


#define FLASH_MTD_PARTITION_HEAD_SIZE 32
#define FLASH_MTD_BUFFER_SIZE (16*1024)  //16K
#define FLASH_MTD_SECTOR_SIZE (64*1024)

#define min(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == &_y); _x < _y ? _x : _y; })

static int flash_mtd_open(const char *name, int flags)
{
    FILE *fp;
    char dev[64] = { 0 };
    int i,ret;
    
    if ((fp = fopen("/proc/mtd", "r")))
    {
        while (fgets(dev,sizeof(dev),fp))
        {
            if (sscanf(dev,"mtd%d:",&i) && strstr(dev, name))
            {
                snprintf(dev, sizeof(dev), "/dev/mtd/%d", i);
                if ((ret = open(dev, flags)) < 0)
                {
                    snprintf(dev, sizeof(dev), "/dev/mtd%d", i);
                    ret = open(dev, flags);
                }
                fclose(fp);
                ratelimit2(1, 1, hlogd("find mtd%d name %s,ret:%d",i,name,ret));
                return ret;
            }
        }
        fclose(fp);
    }
    return -1;
}


/*flash init interface define,should be implement if needed*/
uint32_t hal_flash_init(){
	return 0;
}

/*flash deinit interface define,should be implement if needed*/
uint32_t hal_flash_deinit(){
	return 0;
}

uint32_t hal_flash_read(const int8_t *path, int8_t *data, uint32_t size){
    struct mtd_info_user info = {0};
	int fd = -1, ret = -1;
    fd = flash_mtd_open(path, O_RDONLY);
    if (fd < 0)
    {
        hloge("Could not open mtd device:%s for read.",path);
        goto END;
    }

    if (ioctl(fd, MEMGETINFO, &info))
    {
        hloge("Could not get mtd device info");
       	goto END;
    }

    if (size > info.size)
    {
        hloge("Too many bytes - %d > %d bytes\n",size,info.erasesize);
		goto END;
    }

    lseek(fd, 0, SEEK_SET);
    ret = read(fd, data, size);
    if (ret != size)
    {
        hloge("Reading from mtd failed\n");
        goto END;
    }
END:
   	if (fd != -1 ){
		close(fd);
   	}
    return ret;
}

uint32_t hal_flash_write(const int8_t *path, int8_t *data, uint32_t size){
	int fd = -1, ret = -1;
	struct mtd_info_user info;
	struct erase_info_user erase;
	uint32_t isEraseAll = true;
	char *buffer = (char *)calloc(1, FLASH_MTD_BUFFER_SIZE);

	fd = flash_mtd_open(path, O_RDWR | O_SYNC);
	if (fd < 0)
	{
		hloge("Could not open mtd device:%s for write.", path);
		goto err;
	}

	if (ioctl(fd, MEMGETINFO, &info))
	{
		hloge("Could not get mtd device info.");
		goto err;
	}

	if (info.size != FLASH_MTD_SECTOR_SIZE)
	{
		hloge("only support mtd with one sector!!!");
		goto err;
	}

	if (size > FLASH_MTD_SECTOR_SIZE)
	{
		hloge("too much data!!!");
		goto err;
	}

	if (!isEraseAll)
	{
		lseek(fd,0,SEEK_SET);
		ret = read(fd,buffer,FLASH_MTD_SECTOR_SIZE);
		if (ret < 0)
		{
			hloge("read sector fail!!!");
			goto err;
		}
		memcpy(buffer, data, size);
	}

	erase.start = 0;
	erase.length = info.size;
	hlogd("start to erase MTD.");
	if (ioctl (fd,MEMERASE,&erase) < 0)
	{
		hloge("failed to erase MTD.");
		goto err;
	}
	hlogd("erase MTD complete.");

	if (!isEraseAll)
	{
		lseek(fd,0,SEEK_SET);
		ret = write(fd,buffer,FLASH_MTD_SECTOR_SIZE);
	}
	else
	{
		lseek(fd, 0, SEEK_SET);
		ret = write(fd, data, size);
	}
	if (ret < 0)
	{
		hloge("write sector fail,ret = %d!!!",ret);
	}
err:
	if (fd!=-1){
		close(fd);
	}
	hv_free(buffer);
	hloge("----------------------------> nv write end!\n");
	return ret;
}

uint32_t flash_mtd_erase_partition(const char* to_partition)
{
    int fd,ret = 0;
    struct mtd_info_user info;
    struct erase_info_user erase;

    hlogd("flash_mtd_erase_partition.");
    fd = flash_mtd_open(to_partition, O_RDWR | O_SYNC);
    if (fd < 0)
    {
        hloge("Could not open mtd device:%s for write.",to_partition);
        ret = -1;
        goto exit;
    }

    if (ioctl(fd, MEMGETINFO, &info))
    {
        hloge("Could not get mtd device info.");
        ret = -1;
        goto exit;
    }

    hlogd("mtd size = 0x%x",info.size);
    erase.start = 0;
    erase.length = info.size;
    if (ioctl (fd,MEMERASE,&erase) < 0)
    {
        hloge("failed to erase MTD.");
        ret = -1;
        goto exit;
    }

    hlogd("erase ok.");

exit:
    if (fd)
    {
        close(fd);
    }
    return ret;
}


uint32_t flash_mtd_write_partition(const char* to_partition, const char* from_file)
{
    int to_partition_mtd = 0;
    int from_file_fd = 0;
    int ret = 0;
    struct mtd_info_user mtd_info;
    struct stat filestat;
    char *buffer = (char *)calloc(1, FLASH_MTD_BUFFER_SIZE);
    int readed = 0;
    int writed = 0;
    int dataSize = 0;
    int progressPercentage = 0;

    hlogd("flash_mtd_write_partition.");
    to_partition_mtd = flash_mtd_open(to_partition,O_RDWR | O_SYNC);
    if (to_partition_mtd < 0)
    {
        hloge("Could not open mtd device:%s for write.",to_partition);
         ret = -1;
         goto exit;
    }

    if (ioctl(to_partition_mtd, MEMGETINFO, &mtd_info))
    {
        hloge("Could not get mtd device info.");
        ret = -1;
        goto exit;
    }

    from_file_fd = open(from_file,O_RDWR | O_SYNC);
    if (from_file_fd < 0)
    {
        hloge("failed to open %s.",from_file);
        ret = -1;
        goto exit;
    }

    if (fstat (from_file_fd,&filestat) < 0)
    {
        hloge("failed to get file status.");
        ret = -1;
        goto exit;
    }

    hlogd("mtd size = 0x%x, file size = 0x%x", (uint32_t)mtd_info.size, (uint32_t)filestat.st_size);
    if (mtd_info.size < filestat.st_size)
    {
        hloge("file size is too big!!!");
        ret = -1;
        goto exit;
    }

    lseek(from_file_fd,FLASH_MTD_PARTITION_HEAD_SIZE,SEEK_SET);
    lseek(to_partition_mtd,FLASH_MTD_PARTITION_HEAD_SIZE,SEEK_SET);
    while(1)
    {
        readed = read(from_file_fd,buffer,FLASH_MTD_BUFFER_SIZE);
        if (readed)
        {
            writed = write(to_partition_mtd,buffer,readed);
            if (writed != readed)
            {
                hloge("write flash failed!writed = 0x%x,readed=0x%x", writed, readed);
                ret = -1;
                goto exit;
            }
            else
            {
                dataSize += writed;
                if ((dataSize*10)/filestat.st_size != progressPercentage)
                {
                    progressPercentage = (dataSize*10)/filestat.st_size;
                    hlogd("write flash progress %d%%.",progressPercentage*10);
                }
            }
        }
        else
        {
            ret = 0;
            break;
        }
    }

    lseek(from_file_fd,0,SEEK_SET);
    lseek(to_partition_mtd,0,SEEK_SET);
    readed = read(from_file_fd,buffer,FLASH_MTD_PARTITION_HEAD_SIZE);
    if (FLASH_MTD_PARTITION_HEAD_SIZE == readed)
    {
        writed = write(to_partition_mtd,buffer,readed);
        if (writed != readed)
        {
            hloge("write flash failed!writed = 0x%x,readed=0x%x",writed,readed);
            ret = -1;
            goto exit;
        }
        else
        {
            dataSize += writed;
            hlogd("write flash progress %u.", (uint32_t)((dataSize*100)/filestat.st_size));
        }
    }
    else
    {
        hloge("write flash failed!writed = 0x%x,readed=0x%x", writed, readed);
        ret = -1;
        goto exit;
    }

    hlogd("write flash complete!!");

exit:
    if (to_partition_mtd)
    {
        close(to_partition_mtd);
    }
    if (from_file_fd)
    {
        close(from_file_fd);
    }
	hv_free(buffer);
    return ret;
}









