/******************************************************************************
  File store to flash

Copyright (C) 1997-2017 AudioScience, Inc. All rights reserved.

This software is provided 'as-is', without any express or implied warranty.
In no event will AudioScience Inc. be held liable for any damages arising
from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
   claim that you wrote the original software. If you use this software
   in a product, an acknowledgment in the product documentation would be
   appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
   misrepresented as being the original software.
3. This copyright notice and list of conditions may not be altered or removed
   from any source distribution.

AudioScience, Inc. <support@audioscience.com>

( This license is GPL compatible see http://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses )

*******************************************************************************/
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/stat.h>

#include "hpi_internal.h"
#include "hpimsginit.h"
#include "hpidspcd.h"
#include "hpidebug.h"
#include "hpinet.h"

#include "hpifilestore.h"

#ifdef WIN32
#define stat _stat
#endif

#if defined HPI_OS_LINUX
#include <linux/limits.h>
#endif

/*=========================================================================*/
/** \defgroup filestore File store operations

Some devices have file storage capabiity. This API supports reading and
writing the file store.

There can be at most one file store object per adapter.

@{
*/

/** strftime format for ISO8601 date+time
Produces e.g. "2011-09-23T14:35:31"
Can't add %Z for timezone because string gets too long in Windows.
No "T" in the middle for readability.

Wikipedai states.....

"The date and time representations may appear in proximity to each other,
often separated by a space or sometimes by other characters. In these cases
they occupy two separate fields in a data system, rather than a single combined
representation. This is usually done for human readability. Unlike the previous
examples, "2007-04-05 14:30" is considered two separate, but acceptable,
representations—one for date and the other for time. It is then left to the reader
to interpret the two separate representations as meaning a single time point
based on the context."
*/
#define STRFTIME_FMT "%Y-%m-%d %H:%M:%S"

static uint32_t calc_checksum(uint32_t checksum, int count, uint32_t *pData)
{
	int i;
	for (i = 0; i < count; i++)
		checksum ^= pData[i];

	return checksum;
}

static hpi_err_t flash_start(
		hpi_handle_t hFileStore,
		uint32_t size_in_bytes,
		uint32_t key
)
{
	struct hpi_msg_adapter_flash hmsf;
	struct hpi_res_adapter_flash hrsf;
	hpi_err_t error;

	HPI_InitMessageResponse(
		(HPI_MESSAGE *)&hmsf,
		(HPI_RESPONSE *)&hrsf,
		HPI_OBJ_ADAPTER,
		HPI_ADAPTER_START_FLASH);

	HPI_HandleToIndexes(hFileStore, &hmsf.h.wAdapterIndex, &hmsf.h.wObjIndex);

	hmsf.dwKey = key;
	hmsf.dwOffset = 0;
	hmsf.dwLength = size_in_bytes/sizeof(uint32_t); // in words

	hrsf.h.wError = 0;
	hrsf.h.wSize = sizeof(hrsf);

	HPI_MessageUDP((HPI_MESSAGE *)&hmsf, (HPI_RESPONSE *)&hrsf, 10000);	// wait up to 10 sec for flash erase
	error = hrsf.h.wError;
	HpiOs_DelayMicroSeconds(10 * 1000);

	return error;
}
static hpi_err_t flash_read(
		hpi_handle_t hFileStore,
		struct hpi_res_adapter_read_flash *hrrf,
		uint32_t seq,
		uint32_t key
)
{
	struct hpi_msg_adapter_read_flash hmrf;

	HPI_InitMessageResponse(
		(HPI_MESSAGE *)&hmrf,
		(HPI_RESPONSE *)hrrf,
		HPI_OBJ_ADAPTER,
		HPI_ADAPTER_READ_FLASH);

	HPI_HandleToIndexes(hFileStore, &hmrf.h.wAdapterIndex, &hmrf.h.wObjIndex);

	hmrf.dwKey = key;
	hmrf.wSequence = (uint16_t)seq;
	hmrf.wLength = (uint16_t)(sizeof(hrrf->data)/sizeof(uint32_t));	// len in WORDs

	hrrf->h.wError = 0;
	hrrf->h.wSize = sizeof(*hrrf);

	if (!hFileStore){
		hrrf->h.wError = HPI_ERROR_INVALID_HANDLE;
		return hrrf->h.wError;
	}

	HPI_MessageUDP((HPI_MESSAGE *)&hmrf, (HPI_RESPONSE *)hrrf, 2000);

	return hrrf->h.wError;
}
static hpi_err_t flash_program(
		hpi_handle_t hFileStore,
		void *data,
		size_t size_in_words,
		uint32_t *sequence
)
{
	struct  hpi_msg_adapter_program_flash hm;
	struct  hpi_res_adapter_program_flash hr;
	hpi_err_t error = 0;
	uint32_t checksum;
	uint32_t retries;

	checksum = calc_checksum(0, size_in_words, data);

	HPI_InitMessageResponse(
		(HPI_MESSAGE *)&hm,
		(HPI_RESPONSE *)&hr,
		HPI_OBJ_ADAPTER,
		HPI_ADAPTER_PROGRAM_FLASH);

	HPI_HandleToIndexes(hFileStore, &hm.h.wAdapterIndex, &hm.h.wObjIndex);
	hm.p.wSequence = (uint16_t)*sequence;
	hm.p.dwChecksum = checksum;
	hm.p.wLength = (uint16_t)size_in_words;
	hm.p.wOffset = offsetof(struct hpi_msg_adapter_program_flash, data);
	hm.h.wSize = hm.p.wOffset + size_in_words * sizeof(uint32_t);

	memcpy(	&hm.data, data, size_in_words * sizeof(uint32_t));

	retries = 5;

	while (retries) {
		hr.h.wError = 0;
		hr.h.wSize = sizeof(hr);
		HPI_MessageUDP((HPI_MESSAGE *)&hm, (HPI_RESPONSE *)&hr, 3000);
		HpiOs_DelayMicroSeconds(10 * 1000);

		retries--;
		error = hr.h.wError;

		if (error==HPI_ERROR_FLASH_PROGRAM)
			return error;

		/* if ((sequence==3) && (retries == 4)) error = 1; // Test that repeated packet works  */
		if (!error)
			break;
		/*printf("Retry %d, seq %d\n",retries,sequence);*/
	}
	*sequence = *sequence + 1;

	return error;
}

#if defined HPI_OS_LINUX || defined HPI_OS_OSX
#include <fnmatch.h>

static hpi_err_t flash_filename_to_type(
		const char *filename,
		uint32_t *filetype,
		/** pointer to relative part of filename */
		const char **relname
		)
{
	if (0 == fnmatch("*.luac", filename, 0))
		*filetype = HPI_FLASH_KEY_LUA_BYTECODE;
	else if (0 == fnmatch("*.lua", filename, 0))
		*filetype = HPI_FLASH_KEY_LUA_SOURCE;
	else
		return HPI_ERROR_INVALID_OBJ;

	*relname = strrchr(filename, '/');
	if (!*relname)
		*relname = filename;

	return 0;
}
#else
static hpi_err_t flash_filename_to_type(
	     const char *filename,
	     uint32_t *filetype,
	     const char **filename_part
)
{
	char drive[_MAX_DRIVE];
	char dir[_MAX_DIR];
	char fname[_MAX_FNAME];
	char ext[_MAX_EXT];

	// split the path
	_splitpath(filename, drive, dir, fname, ext);

	// check file extension is supported
	if (strstr(ext,".luac"))
		*filetype = HPI_FLASH_KEY_LUA_BYTECODE;
	else if (strstr(ext,".lua"))
		*filetype = HPI_FLASH_KEY_LUA_SOURCE;
	else {
		return HPI_ERROR_INVALID_OBJ;
	}
	*filename_part = strstr(filename, fname);
	return 0;
}
#endif

/**
Send end flash message
\return_hpierr
*/
static hpi_err_t flash_end(hpi_handle_t hFileStore, uint32_t sequence)
{
	struct  hpi_msg_adapter_program_flash hm;
	struct  hpi_res_adapter_program_flash hr;

	// send end flash message
	HPI_InitMessageResponse(
		(HPI_MESSAGE *)&hm,
		(HPI_RESPONSE *)&hr,
		HPI_OBJ_ADAPTER,
		HPI_ADAPTER_END_FLASH);

	HPI_HandleToIndexes(hFileStore, &hm.h.wAdapterIndex, &hm.h.wObjIndex);
	hm.p.wSequence = (uint16_t)sequence;
	hr.h.wError = 0;
	hr.h.wSize = sizeof(hr);
	HPI_MessageUDP((HPI_MESSAGE *)&hm, (HPI_RESPONSE *)&hr, 2000);
	return hr.h.wError;
}

/**
Open a file store and obtain a handle to it.
* \return_hpierr
*/
hpi_err_t HPI_FileStoreOpen(
	uint16_t wAdapterIndex,	///< Adapter index.
	const char *security,	///< Reserved for future use. Call with NULL.
	hpi_handle_t * phFileStore	///< Returned file store handle.
)
{
	hpi_err_t err;
	uint16_t dummy16;
	uint32_t dummy32;
	char *listing=NULL;

	err = HPI_AdapterGetInfo(NULL, wAdapterIndex,
		&dummy16, &dummy16, &dummy16,
		&dummy32, &dummy16);
	if (err)
		return err;

	*phFileStore = HPI_IndexesToHandle(
				HPI_OBJ_ADAPTER,
				wAdapterIndex,
				0);

	err = HPI_FileStoreAllocDir(*phFileStore, NULL, &listing);
	if (err)
		return err;

	if (listing)
		HpiOs_MemFree(listing);

	return 0;
}
/**
Fetch a directory listing. Format of the listing is filename, timestamp, metadata (if any).
\return_hpierr
*/
hpi_err_t HPI_FileStoreAllocDir(
	hpi_handle_t hFileStore,	///< File store handle.
	const char *name, ///< Name of the directory or file - may be set to NULL
	char **listing ///< List of all files. This is allocated during the call, so caller needs to free it.
)
{
	struct hpi_res_adapter_read_flash hrrf;
	uint32_t keys[2] = {HPI_FLASH_KEY_LUA_SOURCE, HPI_FLASH_KEY_LUA_BYTECODE};
	char single[256];
	const uint32_t lst_size=1024;
	int i;
	char *flist;

	*listing = HpiOs_MemAlloc(lst_size);
	if (!*listing)
		return HPI_ERROR_MEMORY_ALLOC;

	flist = *listing;

	flist[0] = 0;	// null terminate for empty string

	/*
	 * For this version name must be one of NULL | *.lua | *.luac
	 */
	if (name) {
		if ( strstr(name,".luac") )
			keys[0] = 0;
		else if ( strstr(name,".lua") )
			keys[1] = 0;
	}

	for (i=0; i<2; i++) {
		struct hpi_flash_file_header_v1 *pload;
		if (!keys[i])
			continue;

		flash_read(hFileStore, &hrrf, 0, keys[i]);

		if (hrrf.h.wError==HPI_ERROR_FLASH_READ_NO_FILE)
			continue;

		if (hrrf.h.wError)
			break;

		/* checksum evaluation (implementation deferred) */

		pload = (void *)&hrrf.data[0];

		if (pload->version_of_this_header!=HPI_FLASH_FILE_HEADER_V1) {
			strncat(flist, "invalid flash file header detected\n", lst_size - 1 - strlen(flist) );
			break;
		}
		if (pload->metadata[0]=='\xff')
			pload->metadata[0]=0;
		snprintf(single, sizeof(single), "%s, %s, %s\n", pload->filename, pload->timestamp, pload->metadata);
		strncat(flist, single, lst_size - 1 - strlen(flist) );

	}
	if (strlen(flist)==0)
		strcpy(flist,"no files\n");

	/* no file is an acceptable return */
	if (hrrf.h.wError==HPI_ERROR_FLASH_READ_NO_FILE)
		hrrf.h.wError = 0;

	return hrrf.h.wError;
}

/**
Delete the specific file.
\return_hpierr
*/
hpi_err_t HPI_FileStoreDelete(
	hpi_handle_t hFileStore,	///< File store handle.
	const char *filename ///< name of the directory or file - may be set to NULL to delete all files.
)
{
	struct hpi_res_adapter_read_flash hrrf;
	struct hpi_flash_file_header_v1 *pload;
	const char *shortname;
	uint32_t filetype;
	uint32_t sequence;
	hpi_err_t error;

	error = flash_filename_to_type(filename, &filetype, &shortname);
	if (error)
		return error;

	// to erase a file we read the file header and change a bit and write it back

	// read header
	flash_read(hFileStore, &hrrf, 0, filetype);

	if (hrrf.h.wError)
		return hrrf.h.wError;

	// change header to erased
	pload = (void *)&hrrf.data[0];
	pload->state &= ~HPI_FLASH_FILE_HEADER_NOT_ERASED_BIT;

	error = flash_start(hFileStore, HPI_SIZEOF_FLASH_FILE_HEADER_SIZE, filetype);
	if (error)
		return error;

	// write header
	sequence = 0;
	error = flash_program(
		hFileStore,
		&hrrf.data[0],
		HPI_SIZEOF_FLASH_FILE_HEADER_SIZE/sizeof(uint32_t),
		&sequence);

	if (!error)
		error = flash_end(hFileStore, sequence);

	return error;
}
/**
Get the specific file.
\return_hpierr
*/
hpi_err_t HPI_FileStoreGet(
	hpi_handle_t hFileStore,	///< File store handle.
	const char *name, ///< Name of the remote file to fetch (or get).
	const char *local_name ///< Completely specified local filename (including path) of file to write.
)
{
	struct hpi_res_adapter_read_flash hrrf;
	struct hpi_flash_file_header_v1 *pload;
	const char *shortname;
	uint32_t filetype;
	uint32_t remaining;
	uint32_t seq;
	hpi_err_t error = 0;
	FILE *f;

	error = flash_filename_to_type(name, &filetype, &shortname);
	if (error)
		return error;

	// open local file
	f = fopen(local_name, "wb");
	if (!f)
		return HPI_ERROR_INVALID_OBJ;

	// read the start of the file
	// read header
	flash_read(hFileStore, &hrrf, 0, filetype);

	if (hrrf.h.wError) {
		fclose(f);
		return hrrf.h.wError;
	}

	pload = (void *)&hrrf.data[0];

	// does filename match ?
	if (strstr(name,pload->filename)==0) {
		fclose(f);
		return HPI_ERROR_INVALID_OBJ;
	}

	seq = 0;
	remaining = pload->file_size_in_bytes;
	/* Loop through flash blocks in sequence saving the file's content */
	do {
		/* chunk_size: amount of data to save, default to the size of the block */
		size_t chunk_size = hrrf.wLength * sizeof(uint32_t);
		/* file_data_offset: offset of file data in block, default to the beginning of the block */
		int file_data_offset = 0;

		/* Handle the case when some file data follows the header in the first block */
		if (!seq) {
			chunk_size = hrrf.wLength * sizeof(uint32_t) -
							HPI_SIZEOF_FLASH_FILE_HEADER_SIZE;
			file_data_offset = HPI_SIZEOF_FLASH_FILE_HEADER_SIZE/sizeof(uint32_t);
		}
		/* Adjust chunck size to eliminate padding (implicitly, this is the last block) */
		if (chunk_size > remaining)
		    chunk_size = remaining;

		/* Bail out with an error if for some reason writing to disk fails */
		if (fwrite(&hrrf.data[file_data_offset], chunk_size, 1, f) != 1) {
			error = HPI_ERROR_PROCESSING_MESSAGE;
			break;
		}
		remaining -= chunk_size;

		/* Exit the loop when we are done */
		if (!remaining)
			break;

		/* Read the next flash block */
		seq++;
		error = flash_read(hFileStore, &hrrf, seq, filetype);
	} while (!error);

	fclose(f);
	return error;
}

hpi_err_t HPI_FileStorePut(
	hpi_handle_t hFileStore,	///< File store handle.
	const char *name, ///< Name of the remote file (spaces are illegal).
	const char *filename, ///< Completely specified local filename (including path) of file to read from.
	const char *metadata ///< Metadata string to be stored with the file (can be set to NULL).
)
{
	struct  hpi_msg_adapter_program_flash hm;
	FILE *f;
	long fsize;
	struct stat stbuf;
	struct hpi_flash_file_header_v1 *hdr;
	struct tm *timeinfo;
	uint32_t filetype;
	char *flash_image;
	const char *shortname;
	uint32_t flash_image_size;
	uint32_t word_count, word_target, sequence;
	hpi_err_t error = 0;
	//uint32_t checksum = 0;
	//uint32_t total_checksum = 0;

	if (strstr(name, " "))
		return HPI_ERROR_INVALID_STRING;

	error = flash_filename_to_type(name, &filetype, &shortname);
	if (error)
		return error;

	// open local file
	f = fopen(filename, "rb");
	if (!f)
		return HPI_ERROR_INVALID_OBJ;

	// determine file stats
	stat(filename, &stbuf);

	// determine file size
	fseek(f, 0, SEEK_END);
	fsize = ftell(f);
	rewind(f);

	// convert last modified time to local time
	timeinfo = localtime( &stbuf.st_mtime );

	// create flash image on host
	flash_image_size = (HPI_SIZEOF_FLASH_FILE_HEADER_SIZE + fsize + 3) & ~3;
	flash_image = HpiOs_MemAlloc(flash_image_size);
	memset( flash_image, 0xff, flash_image_size);

	// fill in file header
	hdr = (void *)flash_image;
	hdr->version_of_this_header = HPI_FLASH_FILE_HEADER_V1;
	hdr->state &= ~HPI_FLASH_FILE_HEADER_NOT_INUSE_BIT;
	hdr->file_size_in_bytes = fsize;
	hdr->flash_entry_size_in_words = flash_image_size/sizeof(uint32_t);
	snprintf(hdr->filename, sizeof(hdr->filename),"%s", shortname );
	if (metadata)
		snprintf(hdr->metadata, sizeof(hdr->metadata),"%s", metadata );
	strftime(hdr->timestamp, sizeof(hdr->timestamp), STRFTIME_FMT, timeinfo);
	// printf("Header %s %s\n", hdr->filename, hdr->timestamp);
	if (hdr->timestamp[strlen(hdr->timestamp)-1]==0x0a) // conditionally remove nl
		hdr->timestamp[strlen(hdr->timestamp)-1] = 0x00;

	// read the file data into the flash image
	if (fread( &flash_image[HPI_SIZEOF_FLASH_FILE_HEADER_SIZE], fsize, 1, f)) {
		/* Can ignore return value of fread, because we obtained fsize earlier */
	}

	fclose(f);

	// send everything to flash
	error = flash_start(hFileStore, flash_image_size, filetype);
	if (error)
		return error;

	word_count = 0;
	word_target = flash_image_size/sizeof(uint32_t);
	sequence = 0;
	while ((word_count < word_target) && !error) {
		uint16_t wLength;
		uint32_t dwRemain = word_target - word_count;

		wLength = ARRAY_SIZE(hm.data);
		if (wLength > dwRemain)
			wLength = (unsigned short)dwRemain;

		error = flash_program(
			hFileStore,
			&flash_image[word_count * sizeof(uint32_t)],
			wLength,
			&sequence);

		word_count += wLength;
	}
	HpiOs_MemFree(flash_image);

	if (!error)
		error = flash_end(hFileStore, sequence);

	return error;
}
hpi_err_t HPI_FileStoreDeleteAll(
	hpi_handle_t hFileStore	///< File store handle.
)
{
	struct  hpi_msg_adapter_program_flash hm;
	struct  hpi_res_adapter_program_flash hr;

	// send end flash message
	HPI_InitMessageResponse(
		(HPI_MESSAGE *)&hm,
		(HPI_RESPONSE *)&hr,
		HPI_OBJ_ADAPTER,
		HPI_ADAPTER_FILESTORE_DELETE_ALL);

	HPI_HandleToIndexes(hFileStore, &hm.h.wAdapterIndex, &hm.h.wObjIndex);
	hr.h.wError = 0;
	HPI_MessageUDP((HPI_MESSAGE *)&hm, (HPI_RESPONSE *)&hr, 3000);
	return hr.h.wError;

}

/** @} */ /* defgroup filestore */
