/*
 * Copyright 2019, Cypress Semiconductor Corporation or a subsidiary of
 * Cypress Semiconductor Corporation. All Rights Reserved.
 * 
 * This software, associated documentation and materials ("Software")
 * is owned by Cypress Semiconductor Corporation,
 * or one of its subsidiaries ("Cypress") and is protected by and subject to
 * worldwide patent protection (United States and foreign),
 * United States copyright laws and international treaty provisions.
 * Therefore, you may use this Software only as provided in the license
 * agreement accompanying the software package from which you
 * obtained this Software ("EULA").
 * If no EULA applies, Cypress hereby grants you a personal, non-exclusive,
 * non-transferable license to copy, modify, and compile the Software
 * source code solely for use in connection with Cypress's
 * integrated circuit products. Any reproduction, modification, translation,
 * compilation, or representation of this Software except as specified
 * above is prohibited without the express written permission of Cypress.
 *
 * Disclaimer: THIS SOFTWARE IS PROVIDED AS-IS, WITH NO WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, NONINFRINGEMENT, IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Cypress
 * reserves the right to make changes to the Software without notice. Cypress
 * does not assume any liability arising out of the application or use of the
 * Software or any product or circuit described in the Software. Cypress does
 * not authorize its products for use in any products where a malfunction or
 * failure of the Cypress product may reasonably be expected to result in
 * significant property damage, injury or death ("High Risk Product"). By
 * including Cypress's product in a High Risk Product, the manufacturer
 * of such system or application assumes all risk of such use and in doing
 * so agrees to indemnify Cypress against all liability.
 */

/** @file
 *  Implementation of the WicedFS Read-Only file system.
 */

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "wicedfs.h"
#define INSIDE_WICEDFS_C_
#include "wicedfs_internal.h"

/*@access wicedfs_filesystem_t, wicedfs_file_t, wicedfs_dir_t@*/ /* Lint: permit access to abstract handle implementations */

/******************************************************
 *                      Macros
 ******************************************************/
#ifndef WICEDFS_ASSERT_ACTION
#define WICEDFS_ASSERT_ACTION( )
#endif /* ifndef WICEDFS_ASSERT_ACTION */

#ifndef WICEDFS_NO_CHECK_PARAMS
#define WICEDFS_CHECK_PARAMS_NO_RETURN_VAL( expr )  { if (expr) { WICEDFS_ASSERT_ACTION(); return; }}
#define WICEDFS_CHECK_PARAMS( expr, retval )        { if (expr) { WICEDFS_ASSERT_ACTION(); return retval; }}
#else /* ifndef WICEDFS_NO_CHECK_PARAMS */
#ifdef DEBUG
#define WICEDFS_CHECK_PARAMS_NO_RETURN_VAL( expr )  { if (expr) { WICEDFS_ASSERT_ACTION(); }}
#define WICEDFS_CHECK_PARAMS( expr, retval )        { if (expr) { WICEDFS_ASSERT_ACTION(); }}
#else /* ifdef DEBUG */
#define WICEDFS_CHECK_PARAMS_NO_RETURN_VAL( expr )
#define WICEDFS_CHECK_PARAMS( expr, retval )
#endif /* ifdef DEBUG */
#endif /* ifndef WICEDFS_NO_CHECK_PARAMS */

/******************************************************
 *                    Constants
 ******************************************************/

#define WICEDFS_CMP_FILENAME_BUFF_SIZE (32)
#define WICEDFS_DIRECTORY_SEPARATOR    '/'

/******************************************************
 *                   Enumerations
 ******************************************************/

/******************************************************
 *                 Type Definitions
 ******************************************************/

/******************************************************
 *                    Structures
 ******************************************************/

/******************************************************
 *               Static Function Declarations
 ******************************************************/

static int cmp_filename ( const wicedfs_filesystem_t* fs_handle, wicedfs_usize_t loc, const char* filename, uint32_t num_cmp_bytes, uint32_t filename_storage_len );
static int find_item    ( /*@in@*/ const wicedfs_filesystem_t* fs_handle, /*@in@*/ const char * filename, /*@out@*/ wicedfs_dir_header_t* curr_dir, /*@out@*/ wicedfs_file_header_t* file_hnd, /*@out@*/ wicedfs_usize_t* curr_dir_addr );

/******************************************************
 *               Variable Definitions
 ******************************************************/

/******************************************************
 *               Function Definitions
 ******************************************************/

int wicedfs_init ( wicedfs_usize_t base, wicedfs_read_func_t read_func, /*@out@*/ wicedfs_filesystem_t* fs_handle, /*@dependent@*/ void * user_param )
{
    wicedfs_filesystem_header_t fs_header;
    wicedfs_usize_t             bytes_read;

    WICEDFS_CHECK_PARAMS ( ( read_func == NULL ) || ( fs_handle == NULL ), -1 );

    /* Read the filesystem header */
    bytes_read = read_func( user_param, &fs_header, (wicedfs_usize_t) sizeof(fs_header), base );
    if ( bytes_read != sizeof(fs_header) )
    {
        return -1;
    }

    /*@-usedef@*/ /* Lint: splint does not pick up 'out' annotation of function pointer read_func */

    /* Check the magic number and version matches */
    if ( ( fs_header.magic_number != WICEDFS_MAGIC ) ||
         ( fs_header.version != (uint32_t) 1 ) )
    {
        return -2;
    }

    /* Save the hardware address of the root directory
     * in the filesystem handle for use by other functions
     */
    fs_handle->root_dir_addr = base + fs_header.root_dir_offset;       /*@+usedef@*/
    fs_handle->read_func     = read_func;
    fs_handle->user_param    = user_param;

    return 0;
}



int wicedfs_fopen ( /*@dependent@*/ const wicedfs_filesystem_t* fs_handle, /*@out@*/ wicedfs_file_t* handle, const char* filename )
{
    wicedfs_dir_header_t  curr_dir;
    wicedfs_file_header_t file_hnd;
    wicedfs_usize_t       curr_dir_addr;

    WICEDFS_CHECK_PARAMS( ( fs_handle == NULL ) || ( handle    == NULL ) || ( filename  == NULL ), -1 );

    /* Find the file */
    if ( WICEDFS_TYPE_FILE != find_item( fs_handle, filename, &curr_dir, &file_hnd, &curr_dir_addr ) )
    {
        return -1;
    }

    /* Fill out the details of the file handle */
    handle->fs_handle        = fs_handle;
    handle->location         = curr_dir_addr;
    handle->size             = file_hnd.size;
    handle->eof              = 0;
    handle->current_location = 0;

    return 0;
}



wicedfs_ssize_t wicedfs_filesize ( const wicedfs_filesystem_t* fs_handle, const char* filename )
{
    wicedfs_dir_header_t  curr_dir;
    wicedfs_file_header_t file_hnd;
    wicedfs_usize_t       curr_dir_addr;

    WICEDFS_CHECK_PARAMS( ( fs_handle == NULL ) || ( filename  == NULL ), -1 );

    /* Find the file */
    if ( WICEDFS_TYPE_FILE != find_item( fs_handle, filename, &curr_dir, &file_hnd, &curr_dir_addr ) )
    {
        return (wicedfs_ssize_t) -1;
    }

    /* Return the file size */
    return  (wicedfs_ssize_t) file_hnd.size;
}


int wicedfs_fclose( wicedfs_file_t * stream )
{
    WICEDFS_CHECK_PARAMS( ( stream == NULL ), -1 );

    /* Nothing currently needed here */
    UNUSED_PARAMETER( stream );
    return 0;
}

int wicedfs_fseek ( wicedfs_file_t* stream, wicedfs_ssize_t offset, int whence )
{
    wicedfs_ssize_t newpos = offset;

    WICEDFS_CHECK_PARAMS( ( stream == NULL ), -1 );

    /* Calculate the new file position
     * based on the Whence parameter
     */
    if ( whence == SEEK_CUR)
    {
        /* Seek from current file position */
        newpos += (wicedfs_ssize_t) stream->current_location;
    }
    else if ( whence == SEEK_END )
    {
        /* Seek from end of file */
        newpos += (wicedfs_ssize_t)stream->size;
    }
    else if ( whence == SEEK_SET )
    {
        /* Seek from start of file */
        /* do nothing */
    }
#ifdef WICEDFS_CHECK_PARAMS
    else
    {
        /* Bad whence parameter */
        WICEDFS_CHECK_PARAMS( 0==1, -1 );
    }
#endif /* ifndef WICEDFS_NO_CHECK_PARAMS */

    WICEDFS_CHECK_PARAMS( ( newpos < 0 ) || ( newpos > (wicedfs_ssize_t)stream->size ), -2 ); /* bad requested position */

    /* Reset EOF flag */
    stream->eof = 0;

    /* Set new file position */
    stream->current_location = (wicedfs_usize_t) newpos;
    return 0;
}

wicedfs_usize_t wicedfs_ftell(wicedfs_file_t* stream)
{
    WICEDFS_CHECK_PARAMS( ( stream == NULL ), 0 );

    return stream->current_location;
}

wicedfs_usize_t wicedfs_fread( /*@out@*/ void* data, wicedfs_usize_t size, wicedfs_usize_t count, wicedfs_file_t* stream)
{
    wicedfs_usize_t total_bytes = size * count;
    wicedfs_usize_t read_bytes;

    WICEDFS_CHECK_PARAMS( ( data == NULL ) || ( stream  == NULL ), 0 );

    /* User requested zero bytes - not an error */
    if ( total_bytes == 0 )
    {
        /*@-mustdefine@*/ /* LINT: Do not need to define data parameter */
        return 0;
        /*@+mustdefine@*/
    }

    /* Check if the stream is past the end of the file */
    if ( stream->current_location >= stream->size )
    {
        /* Stream past end of file
         * Set EOF flag
         */
        stream->eof = (uint8_t) 1;
        /*@-mustdefine@*/ /* LINT: Error occurred, do not need to define data parameter */
        return 0;
        /*@+mustdefine@*/
    }

    /* Reduce number of bytes to read if there are
     * not enough left before the end of the file
     */
    if ( stream->size - stream->current_location < total_bytes )
    {
        total_bytes = stream->size - stream->current_location;
        stream->eof = 1;
    }

    /* Read the bytes from the hardware */
    read_bytes = stream->fs_handle->read_func( stream->fs_handle->user_param, data, total_bytes, stream->location + stream->current_location );

    if ( read_bytes != total_bytes )
    {
        /* hardware read less than requested - something is wrong */
        return 0;
    }

    /* Update the current location */
    stream->current_location += read_bytes;

    /* Return the number of items read */
    return read_bytes / size;
}


int wicedfs_feof(wicedfs_file_t *stream)
{
    WICEDFS_CHECK_PARAMS( stream == NULL, -1 );

    return (int) stream->eof;
}


int wicedfs_opendir( /*@dependent@*/ const wicedfs_filesystem_t* fs_handle, /*@out@*/ wicedfs_dir_t* handle, const char* dirname )
{
    wicedfs_dir_header_t  curr_dir;
    wicedfs_file_header_t file_hnd;
    wicedfs_usize_t       curr_dir_addr;

    WICEDFS_CHECK_PARAMS( ( fs_handle == NULL ) || ( handle == NULL ) || ( dirname == NULL ) || ( ( dirname != NULL ) && ( dirname[0] == '\x00' ) ), -1 );

    /* Find the directory */
    if ( WICEDFS_TYPE_DIR != find_item( fs_handle, dirname, &curr_dir, &file_hnd, &curr_dir_addr ) )
    {
        return -1;
    }

    /* Fill out the details of the directory handle */
    handle->fs_handle           = fs_handle;
    handle->file_table_location = (wicedfs_usize_t)( (wicedfs_ssize_t) curr_dir_addr + curr_dir.file_table_offset );
    handle->num_files           = curr_dir.num_files;
    handle->file_header_size    = curr_dir.file_header_size;
    handle->filename_size       = curr_dir.file_header_size;
    handle->current_location    = 0;

    return 0;
}

int wicedfs_closedir( wicedfs_dir_t* dirp )
{
    WICEDFS_CHECK_PARAMS( ( dirp == NULL ), -1 );

    /* Nothing currently needed here */
    UNUSED_PARAMETER( dirp );
    return 0;
}


int wicedfs_readdir_with_size( wicedfs_dir_t* dirp, /*@out@*/ char* name_buf, unsigned int name_buf_len, wicedfs_entry_type_t* type, wicedfs_usize_t* size )
{
    wicedfs_usize_t bytes_read;
    wicedfs_usize_t file_header_loc;
    wicedfs_file_header_t file_header;

    WICEDFS_CHECK_PARAMS( ( dirp == NULL ) || ( name_buf  == NULL ) || ( type == NULL ), -1 );

    if ( (wicedfs_usize_t) name_buf_len < dirp->filename_size )
    {
        /* Not enough space in buffer */ /*@-mustdefine@*/
        return -1;
        /*@+mustdefine@*/
    }

    if ( dirp->current_location >= dirp->num_files )
    {
        /* Trying to read past end of directory */ /*@-mustdefine@*/
        return -2;
        /*@+mustdefine@*/
    }

    file_header_loc = dirp->file_table_location + dirp->current_location * dirp->file_header_size;

    /* Read the file header from the file table entry */
    bytes_read = dirp->fs_handle->read_func( dirp->fs_handle->user_param, &file_header, sizeof(file_header), file_header_loc );

    if ( bytes_read != sizeof(file_header) )
    {
        return -1;
    }

    /* Read the filename from the file table entry */
    bytes_read = dirp->fs_handle->read_func( dirp->fs_handle->user_param, name_buf, dirp->filename_size, (wicedfs_usize_t) ( file_header_loc + sizeof(wicedfs_file_header_t) ) );

    if ( bytes_read != dirp->filename_size )
    {
        return -2;
    }

    /* Get the entry type (file or dir) */
    if ( ( file_header.type_flags_permissions & WICEDFS_TYPE_MASK ) == WICEDFS_TYPE_FILE )
    {
        *type = WICEDFS_FILE;
        *size = file_header.size;
    }
    else
    {
        *type = WICEDFS_DIR;
        *size = 0;
    }

    /* Increment the current location within the file table */
    dirp->current_location++;

    return 0;
}

void wicedfs_rewinddir( wicedfs_dir_t *dirp )
{
    WICEDFS_CHECK_PARAMS_NO_RETURN_VAL( dirp == NULL );

    /* Reset the current location within the file table */
    dirp->current_location = 0;
}

int wicedfs_eodir ( wicedfs_dir_t* dirp )
{
    WICEDFS_CHECK_PARAMS( dirp == NULL, 0 );

    return dirp->current_location >= dirp->num_files;
}


/******************************************************
 *               Static Function Definitions
 ******************************************************/


/**
 * Compares part of a file path to a pathname on the hardware device
 *
 * @param[in] fs_handle            : handle of the filesystem obtained from wicedfs_init
 * @param[in] loc                  : The hardware device location of the path to compare against
 * @param[in] filename             : The path that will be partly compared against the path from the hardware device
 * @param[in] num_cmp_bytes        : Number of bytes of parameter "filename" to compare
 * @param[in] filename_storage_len : The maximum number of path bytes available on the hardware
 *
 * @return 0  if the required path parts match, 1 = no match, negative = error
 */
static int cmp_filename( const wicedfs_filesystem_t* fs_handle,
                         wicedfs_usize_t           loc,
                         const char*               filename,
                         uint32_t                  num_cmp_bytes,
                         uint32_t                  filename_storage_len )
{
    /* Stack allocate a small buffer for the comparison */
    char buf[WICEDFS_CMP_FILENAME_BUFF_SIZE];

    /* Determine how many bytes to compare */
    uint32_t cmp_len = ( num_cmp_bytes > filename_storage_len )? filename_storage_len : num_cmp_bytes;

    do
    {
        uint32_t        read_len;
        wicedfs_usize_t bytes_read;

        /* Read up to one buffer of the filename from the hardware */
        read_len = ( cmp_len > sizeof(buf) )? (uint32_t) sizeof(buf) : cmp_len;
        bytes_read = fs_handle->read_func( fs_handle->user_param, buf, (wicedfs_usize_t) read_len, loc );
        if ( bytes_read != (wicedfs_usize_t) read_len )
        {
            /* Error reading data */
            return -1;
        }

        /* Compare the amount read to the filename parameter */
        if ( 0 != strncmp( buf, filename, (size_t) read_len ) )
        {
            /* Filename on hardware did not match the provided filename parameter */
            return 1;
        }

        /* Increment amount read */
        cmp_len  -= read_len;
        filename += read_len;
        loc      += read_len;

    } while ( cmp_len > 0 );  /* Loop to read more if needed */

    /* The entire filename was compared and matched */
    return 0;
}

/**
 * Finds a named file/directory
 *
 * Starting at the root directory, this function
 * searches file tables to find required subdirectories and
 * eventually to find the required file table entry.
 *
 * @param[in]     fs_handle     : filesystem handle obtained from wicedfs_init()
 * @param[in]     filename      : the path of the file table entry (e.g. a file or directory)
 * @param[out]    curr_dir      : Receives the directory header of the item found.
 * @param[out]    file_hnd      : Receives the file table entry of the item found.
 * @param[out]    curr_dir_addr : Receives the hardware address of the content data for the item found.
 *
 * @return The WICEDFS_TYPE (e.g. DIR, FILE, etc) or negative on error
 */
static int find_item( /*@in@*/  const wicedfs_filesystem_t*  fs_handle,
                      /*@in@*/  const char*                filename,
                      /*@out@*/ wicedfs_dir_header_t*      curr_dir,
                      /*@out@*/ wicedfs_file_header_t*     file_hnd,
                      /*@out@*/ wicedfs_usize_t*           curr_dir_addr )
{
    uint32_t        cmp_bytes;
    char*           slash_offset;
    uint32_t        file_num;
    wicedfs_usize_t curr_file_table_addr;

    /* Start at the root directory */
    *curr_dir_addr = fs_handle->root_dir_addr;

    file_hnd->type_flags_permissions = WICEDFS_TYPE_DIR;

    /* Each interation of this loop descends one subdirectory */
    while ( 1 == 1 )
    {
        wicedfs_usize_t bytes_read;
        int cmp_val = -1;

        /* Skip the first character if it is a directory separator */
        if ( filename[0] == WICEDFS_DIRECTORY_SEPARATOR )
        {
            filename++;
        }

        /* Read the header of the current directory from the hardware */
        bytes_read = fs_handle->read_func( fs_handle->user_param, curr_dir, (wicedfs_usize_t) sizeof(wicedfs_dir_header_t), *curr_dir_addr );
        if ( bytes_read != sizeof(wicedfs_dir_header_t) )
        {
            /* Error reading hardware device */
            return -1;
        }

        /* If the filename has been reduced to nothing, then the
         * caller requested a directory name, and it has been found.
         */
        if ( filename[ 0 ] == '\x00' )
        {
            /* Return the file table entry-type */
            return (int) ( file_hnd->type_flags_permissions & WICEDFS_TYPE_MASK );
        }

        /* Look for a directory separator character */
        if ( ( slash_offset = strchr( filename, WICEDFS_DIRECTORY_SEPARATOR ) ) != NULL )
        {
            /* A directory separator was found.
             * Calculate the length of the name comparison that should be performed
             */
            cmp_bytes = (uint32_t)( slash_offset - filename );
        }
        else
        {
            /* No directory separator. Compare entire name */
            cmp_bytes = (uint32_t) strlen( filename );
        }


        /* Set up loop variables */
        file_num = 0;
        curr_file_table_addr = (wicedfs_usize_t)( (wicedfs_ssize_t) *curr_dir_addr + curr_dir->file_table_offset );

        /* Loop over each file table entry
         * For each: read the entry from hardware, then compare the entry name to the given filename
         */
        while ( ( ( bytes_read = fs_handle->read_func( fs_handle->user_param, file_hnd, (wicedfs_usize_t) sizeof(wicedfs_file_header_t), curr_file_table_addr + file_num * curr_dir->file_header_size ) ) == (wicedfs_usize_t) sizeof(wicedfs_file_header_t) ) &&
                ( 1 == ( cmp_val = cmp_filename( fs_handle, (wicedfs_usize_t) ( curr_file_table_addr + file_num * curr_dir->file_header_size + sizeof(wicedfs_file_header_t) ), filename, cmp_bytes, curr_dir->filename_size ) ) ) )
        {
            /* Not found yet - increment file table position */
            file_num++;

            /* Check if position has passed the end of the file table */
            if ( file_num >= curr_dir->num_files )
            {
                /* Table position has passed the end of the
                 * file table without finding a match.
                 *
                 * i.e. file not found
                 */
                return -2;
            }
        }
        if ( ( bytes_read != (wicedfs_usize_t) sizeof(wicedfs_file_header_t) ) ||
             ( cmp_val < 0 ) )
        {
            /* error reading hardware device */
            return -3;
        }

        /* Prepare current directory pointer to descend into next directory */
        *curr_dir_addr = (wicedfs_usize_t) ((wicedfs_ssize_t) curr_file_table_addr + file_hnd->offset +  (wicedfs_ssize_t)( file_num * curr_dir->file_header_size ));

        /* Strip off the part of the filename that was found */
        filename += cmp_bytes;
    }

    /* No return needed here due to the while(1) */ /*@-noret@*/
}
    /*@+noret@*/

/*@noaccess wicedfs_filesystem_t, wicedfs_file_t, wicedfs_dir_t@*/