//*****************************************************************************
//
// fswrapper.c - File System Processing for lwIP Web Server Apps.
//
// Copyright (c) 2007-2014 Texas Instruments Incorporated.  All rights reserved.
// Software License Agreement
// 
// Texas Instruments (TI) is supplying this software for use solely and
// exclusively on TI's microcontroller products. The software is owned by
// TI and/or its suppliers, and is protected under applicable copyright
// laws. You may not combine this software with "viral" open-source
// software in order to form a larger program.
// 
// THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS.
// NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT
// NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY
// CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL
// DAMAGES, FOR ANY REASON WHATSOEVER.
// 
// This is part of revision 2.1.0.12573 of the Tiva Utility Library.
//
//*****************************************************************************

#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "inc/hw_types.h"
#include "driverlib/debug.h"
#include "httpserver_raw/fs.h"
#include "httpserver_raw/fsdata.h"
#include "fatfs/src/ff.h"
#include "fatfs/src/diskio.h"
#include "utils/fswrapper.h"
#include "utils/lwiplib.h"
#include "utils/ustdlib.h"

//*****************************************************************************
//
//! \addtogroup fswrapper_api
//! @{
//
//*****************************************************************************

//*****************************************************************************
//
// Static file system images for use with this module may be created using
// the makefsfile.exe utility.  Both position-independent (built using the -b
// command line option to makefsfile) and position dependent file system images
// may be used.
//
//*****************************************************************************
typedef struct
{
    //
    // The index of the file system containing this file.
    //
    uint32_t ui32MountIndex;

    //
    // The FatFs file structure allocated if the target file is in the FAT
    // file system.
    //
    FIL *psFATFile;
}
fs_wrapper_data;

//*****************************************************************************
//
// A marker used to indicate that a passed filename cannot be mapped to any of
// the configured mount points.
//
//*****************************************************************************
#define BAD_MOUNT_INDEX         0xFFFFFFFF

//*****************************************************************************
//
// This macro is used to extract pointers from the file descriptors.  We
// support files systems linked into the image as well as external, position
// independent file system images and this macro allows us to use the same code
// to extract pointers from file descriptors in each case.
//
//*****************************************************************************
#define FS_POINTER(ptTree, ptValue, bPosInd)                                  \
        ((char *)((bPosInd) ? ((int8_t *)(ptTree) + (uint32_t)(ptValue)) :    \
                  (int8_t *)(ptValue)))

//*****************************************************************************
//
// The pointer to the mount point table and the number of entries in the
// table.
//
//*****************************************************************************
static fs_mount_data *g_psMountPoints = NULL;
static uint32_t g_ui32NumMountPoints = 0;
static uint32_t g_ui32DefaultMountIndex = BAD_MOUNT_INDEX;
static bool g_bFatFsEnabled = false;

//*****************************************************************************
//
// Given a filename, this function determine which of the configured mount
// points it resides under.  It returns the index of the mount point in the
// g_psMountPoints array and also a pointer to the first character of the
// filename with the mount point name (directory) stripped from it.
//
//*****************************************************************************
static uint32_t
fs_find_mount_index(const char *pcName, char **ppcFSFilename)
{
    uint32_t ui32Loop;
    int iLenDirName;
    int iLenMountName;
    char *pcSlash;

    //
    // First extract the top level directory name which we need to match
    // with the mount point name.  For this to exist, the pcName string
    // must start with a '/' character and must contain at least one more
    // '/'.
    //
    if(pcName[0] == '/')
    {
        //
        // The string starts with a '/'.  Does it contain a second one?
        //
        pcSlash = strchr(pcName + 1, '/');

        //
        // Did we find another forward slash character?
        //
        if(pcSlash)
        {
            //
            // Yes - the mount point name is between the start of the
            // string and the slash we just found.  How long is this string?
            //
            iLenDirName = (int)(pcSlash - (pcName + 1));
        }
        else
        {
            //
            // The mount point name is the whole string.
            //
            iLenDirName = ustrlen(pcName + 1);
            pcSlash = (char *)pcName + 1 + iLenDirName;
        }

        //
        // Now figure out which, if any, of the mount points this matches.
        //
        for(ui32Loop = 0; ui32Loop < g_ui32NumMountPoints; ui32Loop++)
        {
            //
            // Skip the default mount point if found.
            //
            if(!g_psMountPoints[ui32Loop].pcNamePrefix)
            {
                continue;
            }

            //
            // How long is the name of this mount point?
            //
            iLenMountName = ustrlen(g_psMountPoints[ui32Loop].pcNamePrefix);

            //
            // Does the mount point name match the directory name extracted
            // from the passed pcName?
            //
            if(iLenMountName == iLenDirName)
            {
                //
                // The lengths match but are the strings the same?
                //
                if(!ustrncmp(g_psMountPoints[ui32Loop].pcNamePrefix,
                             pcName + 1, iLenDirName))
                {
                    //
                    // Yes - we have a match.  Set the stripped filename to
                    // the second '/' and return the mount point index.
                    //
                    *ppcFSFilename = pcSlash;
                    return(ui32Loop);
                }
            }
        }
    }

    //
    // If we drop out of the loop, we didn't find a specific mount point for
    // this file so just return the filename passed and the default mount
    // point.
    //
    *ppcFSFilename = (char *)pcName;

    return(g_ui32DefaultMountIndex);
}

//*****************************************************************************
//
//! Initializes the file system wrapper.
//!
//! \param psMountPoints points to an array of fs_mount_data structures.  Each
//! element in the array maps a top level directory name to a particular
//! file system image or to the FAT file system and a logical drive number.
//! \param ui32NumMountPoints provides the number of populated elements in the
//! \e psMountPoints array.
//!
//! This function should be called to initialize the file system wrapper and
//! provide it with the information required to access the files in multiple
//! file system images via a single filename space.
//!
//! Each entry in \e psMountPoints describes a top level directory in the
//! unified namespace and indicates to fswrapper where the files for that
//! directory can be found.  Each entry can describe either a file system
//! image in system memory or a logical disk handled via the FatFs file system
//! driver.
//!
//! For example, consider the following 3 entry mount point table:
//!
//! \verbatim
//! {
//!     { "internal", &g_pui8FSImage,   0, NULL,         NULL },
//!     { "sdcard",   NULL,             0, SDCardEnable, SDCardDisable },
//!     { NULL,       &g_pui8FSDefault, 0, NULL,         NULL}
//! }
//! \endverbatim
//!
//! Requests to open file ``/internal/index.html'' will be handled by
//! attempting to open ``/index.html'' in the internal file system pointed to
//! by \e g_pui8FSImage.  Similarly, opening ``/sdcard/images/logo.gif'' will
//! result in a call to the FAT f_open function requesting
//! ``0:/images/logo.gif''.  If a request to open ``index.htm'' is received,
//! this is handled by attempting to open ``index.htm'' in the default internal
//! file system image, \e g_pui8FSDefault.
//!
//! \return Returns \b true on success or \b false on failure.
//
//*****************************************************************************
bool
fs_init(fs_mount_data *psMountPoints, uint32_t ui32NumMountPoints)
{
    uint32_t ui32Loop;

    //
    // Check for non-zero parameters in debug builds.
    //
    ASSERT(psMountPoints);
    ASSERT(ui32NumMountPoints);

    //
    // Remember the mount point information we have been given.
    //
    if(psMountPoints && ui32NumMountPoints)
    {
        //
        // Remember the information passed.
        //
        g_psMountPoints = psMountPoints;
        g_ui32NumMountPoints = ui32NumMountPoints;

        //
        // Check to determine if any of the mount points refer to FAT file
        // system drivers.  We also hijack this loop to determine what the
        // default mount point (if any) is.
        //
        g_bFatFsEnabled = false;
        for(ui32Loop = 0; ui32Loop < g_ui32NumMountPoints; ui32Loop++)
        {
            //
            // If the pui8FSImage field of a mount point structure is NULL,
            // this implies that we are using the FAT file system for that
            // node.
            //
            if(!g_psMountPoints[ui32Loop].pui8FSImage)
            {
                g_bFatFsEnabled = true;
            }

            //
            // Does this entry describe the default mount point?
            //
            if(g_psMountPoints[ui32Loop].pcNamePrefix == NULL)
            {
                g_ui32DefaultMountIndex = ui32Loop;
            }
        }

        return(true);
    }
    else
    {
        //
        // Return an error due to being passed a bad parameter.
        //
        return(false);
    }
}

//*****************************************************************************
//
//! Provides a periodic tick for the file system.
//!
//! \param ui32TickMS is the number of milliseconds which have elapsed since
//! the last time this function was called.
//!
//! Applications making use of the file system wrapper with underlying FatFs
//! drives must call this function at least once every 10 milliseconds to
//! provide a time reference for use by the file system.  It is typically
//! called in the context of the application's SysTick interrupt handler or
//! from the handler of some other timer interrupt.
//!
//! If only binary file system images are in use, this function need not be
//! called.
//!
//! \return None
//
//
//*****************************************************************************
void
fs_tick(uint32_t ui32TickMS)
{
    static uint32_t ui32TickCounter = 0;

    //
    // Check if the file system has been enabled yet.
    //
    if(!g_bFatFsEnabled)
    {
        return;
    }

    //
    // Increment the tick counter.
    //
    ui32TickCounter += ui32TickMS;

    //
    // Check to see if the FAT FS tick needs to run.
    //
    if(ui32TickCounter >= 10)
    {
        ui32TickCounter = 0;
        disk_timerproc();
    }
}

//*****************************************************************************
//
//! Opens a file.
//!
//! \param pcName points to a NULL terminated string containing the path and
//! file name to open.
//!
//! This function opens a file and returns a handle allowing it to be read.
//!
//! \return Returns a valid file handle on success or NULL on failure.
//
//*****************************************************************************
struct fs_file *
fs_open(const char *pcName)
{
    const struct fsdata_file *psTree;
    const struct fsdata_file *psEnd = NULL;
    struct fs_file *psFile = NULL;
    fs_wrapper_data *psWrapper;
    FRESULT fresult = FR_OK;
    bool bPosInd = false;
    char *pcFSFilename;
    char *pcFilename;
    uint32_t ui32Length;

    //
    // Allocate memory for the file system structure.
    //
    psFile = mem_malloc(sizeof(struct fs_file));
    if(NULL == psFile)
    {
        return(NULL);
    }

    //
    // Allocate memory for our internal control structure.
    //
    psFile->pextension = mem_malloc(sizeof(fs_wrapper_data));
    psWrapper = (fs_wrapper_data *)psFile->pextension;

    if(NULL == psWrapper)
    {
        return(NULL);
    }

    //
    // Find which mount point we need to use to satisfy this file open request.
    //
    psWrapper->ui32MountIndex = fs_find_mount_index(pcName, &pcFSFilename);
    if(psWrapper->ui32MountIndex == BAD_MOUNT_INDEX)
    {
        //
        // We can't map the mount index so return an error.
        //
        mem_free(psWrapper);
        mem_free(psFile);
        return(NULL);
    }

    //
    // Enable access to the physical medium if we have been provided with
    // a callback for this.
    //
    if(g_psMountPoints[psWrapper->ui32MountIndex].pfnEnable)
    {
        g_psMountPoints[psWrapper->ui32MountIndex].
            pfnEnable(psWrapper->ui32MountIndex);
    }

    //
    // Are we opening a file on an internal file system image?
    //
    if(g_psMountPoints[psWrapper->ui32MountIndex].pui8FSImage)
    {
        //
        // Initialize the file system tree pointer to the root of the linked
        // list for this mount point's file system image.
        //
        psTree = ((const struct fsdata_file *)
                  g_psMountPoints[psWrapper->ui32MountIndex].pui8FSImage);

        //
        // Which type of file system are we dealing with?
        //
        if(psTree->next == FILE_SYSTEM_MARKER)
        {
            //
            // If we found the marker, this is a position independent file
            // system image.  Remember this and fix up the pointer to the
            // first descriptor by skipping over the 4 byte marker and the
            // 4 byte image size entry.  We also keep track of where the file
            // system image ends since this allows us to do a bit more error
            // checking later.
            //
            bPosInd = true;
            ui32Length = *(uint32_t *)((uint8_t *)psTree + 4);
            psTree = (struct fsdata_file *)((int8_t *)psTree + 8);
            psEnd = (struct fsdata_file *)((int8_t *)psTree + ui32Length);
        }

        //
        // Begin processing the linked list, looking for the requested file
        // name.
        //
        while(NULL != psTree)
        {
            //
            // Compare the requested file "name" to the file name in the
            // current node.
            //
            if(ustrncmp(pcFSFilename,
                        FS_POINTER(psTree, psTree->name, bPosInd),
                        psTree->len) == 0)
            {
                //
                // Fill in the data pointer and length values from the
                // linked list node.
                //
                psFile->data = FS_POINTER(psTree, psTree->data, bPosInd);
                psFile->len = psTree->len;

                //
                // For now, we setup the read index to the end of the file,
                // indicating that all data has been read.  This indicates that
                // all the data is currently available in a contiguous block
                // of memory (which is always the case with an internal file
                // system image).
                //
                psFile->index = psTree->len;

                //
                // We are not using a FAT file system file and don't need to
                // remap the filename so set these pointers to NULL.
                //
                psWrapper->psFATFile = NULL;

                //
                // Exit the loop and return the file system pointer.
                //
                break;
            }

            //
            // If we get here, we did not find the file at this node of the
            // linked list.  Get the next element in the list.  We can't just
            // assign psTree from psTree->next since this will give us the
            // wrong pointer for a position independent image (where the values
            // in the structure are offsets from the start of the file
            // descriptor, not absolute pointers) but we do know that a 0 in
            // the "next" field does indicate that this is the last file so we
            // can use that info to force the loop to exit at the end.
            //
            if(psTree->next == 0)
            {
                psTree = NULL;
            }
            else
            {
                psTree = (struct fsdata_file *)FS_POINTER(psTree, psTree->next,
                                                          bPosInd);

                //
                // If this is a position independent file system image, we can
                // also check that the new node is within the image.  If it
                // isn't, the image is corrupted to stop the search.
                //
                if(bPosInd && (psTree >= psEnd))
                {
                    psTree = NULL;
                }
            }
        }

        //
        // If we didn't find the file, ptTee will be NULL.  Make sure we
        // return a NULL pointer if this happens.
        //
        if(NULL == psTree)
        {
            mem_free(psFile->pextension);
            mem_free(psFile);
            psFile = NULL;
        }
    }
    else
    {
        //
        // This file is on the FAT file system.
        //

        //
        // Allocate memory for the Fat File system handle.
        //
        psWrapper->psFATFile = mem_malloc(sizeof(FIL));
        if(NULL == psWrapper->psFATFile)
        {
            mem_free(psFile->pextension);
            mem_free(psFile);
            psFile = NULL;
        }
        else
        {
            //
            // Reformat the filename to start with the FAT logical drive
            // number.
            //
            ui32Length = ustrlen(pcFSFilename) + 16;
            pcFilename = mem_malloc(ui32Length);
            if(!pcFilename)
            {
                //
                // Can't allocate temporary storage for the reformatted
                // filename!
                //
                mem_free(psWrapper->psFATFile);
                mem_free(psFile->pextension);
                mem_free(psFile);
                psFile = NULL;
            }
            else
            {
                usnprintf(pcFilename, ui32Length, "%d:%s",
                          g_psMountPoints[psWrapper->ui32MountIndex].
                          ui32DriveNum, pcFSFilename);
                //
                // Attempt to open the file on the Fat File System.
                //
                fresult = f_open(psWrapper->psFATFile, pcFilename, FA_READ);

                //
                // Free the filename storage
                //
                mem_free(pcFilename);

                //
                // Did we open the file correctly?
                //
                if(FR_OK == fresult)
                {
                    //
                    // Yes - fill in the file structure to indicate that a
                    // FAT file is in use.
                    //
                    psFile->data = NULL;
                    psFile->len = 0;
                    psFile->index = 0;
                }
                else
                {
                    //
                    // If we get here, we failed to find the file on the FAT
                    // file system so free up the FAT handle/object.
                    //
                    mem_free(psWrapper->psFATFile);
                    mem_free(psWrapper);
                    mem_free(psFile);
                    psFile = NULL;
                }
            }
        }
    }

    //
    // Disable access to the physical medium if we have been provided with
    // a callback for this.
    //
    if(g_psMountPoints[psWrapper->ui32MountIndex].pfnDisable)
    {
        g_psMountPoints[psWrapper->ui32MountIndex].
            pfnDisable(psWrapper->ui32MountIndex);
    }

    return(psFile);
}

//*****************************************************************************
//
//! Closes a file.
//!
//! \param phFile is the handle of the file that is to be closed.  This will
//! have been returned by an earlier call to fs_open().
//!
//! This function closes the file identified by \e phFile and frees all
//! resources associated with the file handle.
//!
//! \return None.
//
//*****************************************************************************
void
fs_close(struct fs_file *phFile)
{
    fs_wrapper_data *psWrapper;

    psWrapper = (fs_wrapper_data *)phFile->pextension;

    //
    // If a Fat file was opened, free its object.
    //
    if(psWrapper->psFATFile)
    {
        //
        // Close the file.
        //
        f_close(psWrapper->psFATFile);

        //
        // Free the file object.
        //
        mem_free(psWrapper->psFATFile);
    }

    //
    // Free our file wrapper control structure.
    //
    mem_free(phFile->pextension);

    //
    // Free the main file system object.
    //
    mem_free(phFile);
}

//*****************************************************************************
//
//! Reads data from an open file.
//!
//! \param phFile is the handle of the file which is to be read.  This will
//! have been returned by a previous call to fs_open().
//! \param pcBuffer points to the first byte of the buffer into which the
//! data read from the file will be copied.  This buffer must be large enough
//! to hold \e iCount bytes.
//! \param iCount is the maximum number of bytes of data that are to be read
//! from the file.
//!
//! This function reads the next block of data from the given file into a
//! buffer and returns the number of bytes read or -1 if the end of the file
//! has been reached.
//!
//! \return Returns the number of bytes read from the file or -1 if the end of
//! the file has been reached and no more data is available.
//
//*****************************************************************************
int
fs_read(struct fs_file *phFile, char *pcBuffer, int iCount)
{
    int iAvailable, iRetcode;
    fs_wrapper_data *psWrapper;

    psWrapper = (fs_wrapper_data *)phFile->pextension;

    //
    // Call the application's enable function for this physical medium (if
    // an enable function has been provided).
    //
    if(g_psMountPoints[psWrapper->ui32MountIndex].pfnEnable)
    {
        g_psMountPoints[psWrapper->ui32MountIndex].
            pfnEnable(psWrapper->ui32MountIndex);
    }

    //
    // Check to see if a Fat File was opened and process it.
    //
    if(psWrapper->psFATFile)
    {
        uint32_t ui32BytesRead;
        FRESULT fresult;

        //
        // Read the data.
        //
        fresult = f_read(psWrapper->psFATFile, pcBuffer, iCount,
                         (UINT*)&ui32BytesRead);
        if((fresult != FR_OK) || (ui32BytesRead == 0))
        {
            iRetcode = -1;
        }
        else
        {
            iRetcode = (int)ui32BytesRead;
        }
    }
    else
    {
        //
        // We are reading a file from a file system image.  Check to see if
        // more data is available.
        //
        if(phFile->len == phFile->index)
        {
            //
            // There is no remaining data.  Return a -1 for EOF indication.
            //
            return(-1);
        }

        //
        // Determine how much data we can copy.  The minimum of the 'iCount'
        // parameter or the available data in the file system buffer.
        //
        iAvailable = phFile->len - phFile->index;
        if(iAvailable > iCount)
        {
            iAvailable = iCount;
        }

        //
        // Copy the data.
        //
        memcpy(pcBuffer, phFile->data + phFile->index, iAvailable);
        phFile->index += iAvailable;

        //
        // Return the count of data that we copied.
        //
        iRetcode = iAvailable;
    }

    //
    // Call the application's disable function now that we have finished
    // accessing the file.
    //
    if(g_psMountPoints[psWrapper->ui32MountIndex].pfnDisable)
    {
        g_psMountPoints[psWrapper->ui32MountIndex].
            pfnDisable(psWrapper->ui32MountIndex);
    }

    //
    // Return the number of bytes read.
    //
    return(iRetcode);
}

//*****************************************************************************
//
//! Maps a path string containing mount point names to a path suitable for
//! use in calls to the FatFs APIs.
//!
//! \param pcPath points to a string containing a path in the namespace
//! defined by the mount information passed to fs_init().
//! \param pcMapped points to a buffer into which the mapped path string will
//! be written.
//! \param iLen is the size, in bytes, of the buffer pointed to by pcMapped.
//!
//! This function may be used by applications which want to make use of FatFs
//! functions which are not directly mapped by the fswrapper layer.  A path
//! in the namespace defined by the mount points passed to function fs_init()
//! is translated to an equivalent path in the FatFs namespace and this may
//! then be used in a direct call to functions such as f_opendir() or
//! f_getfree().
//!
//! \return Returns \b true on success or \b false if fs_init() has not
//! been called, if the path provided maps to an internal file system image
//! rather than a FatFs logical drive or if the buffer pointed to by
//! \e pcMapped is too small to fit the output string.
//
//*****************************************************************************
bool
fs_map_path(const char *pcPath, char *pcMapped, int iLen)
{
    char *pcFSFilename;
    uint32_t ui32MountIndex;
    int iCount;

    //
    // If no mount points have been defined, return an error.
    //
    if(!g_psMountPoints)
    {
        return(false);
    }

    //
    // Find which mount point we need to use to satisfy this file open request.
    //
    ui32MountIndex = fs_find_mount_index(pcPath, &pcFSFilename);

    //
    // If we got a bad mount index or the index returned represents a mount
    // point that is not in the FAT file system, return an error.
    //
    if((ui32MountIndex == BAD_MOUNT_INDEX) ||
       (g_psMountPoints[ui32MountIndex].pui8FSImage))
    {
        //
        // We can't map the mount index so return an error.
        //
        return(false);
    }

    //
    // Now we can generate the FatFs namespace path string.
    //
    iCount = usnprintf(pcMapped, iLen, "%d:%s",
                       g_psMountPoints[ui32MountIndex].ui32DriveNum,
                       pcFSFilename);

    //
    // Tell the user how we got on.  The count returned by usnprintf is the
    // number of characters that should have been written, excluding the
    // terminating NULL so we use this to check for overflow of the output
    // buffer.
    //
    return((iLen >= (iCount + 1)) ? true : false);
}

//*****************************************************************************
//
// Close the Doxygen group.
//! @}
//
//*****************************************************************************