/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 *
 * Copyright (C) 2014 by Michael Sevakis
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include <string.h>
#include <ctype.h>
#include "system.h"
#include "pathfuncs.h"
#include "string-extra.h"
#include <stdio.h>
#include "file_internal.h"
#include "debug.h"

// #define LOGF_ENABLE
#include "logf.h"

#ifdef HAVE_MULTIVOLUME
#include "storage.h"

static char vol_dec_strings[NUM_VOLUMES][ALIGN_UP(VOL_MAX_LEN+2, 4)] = {{0}};

enum storage_name_dec_indexes
{
#if (CONFIG_STORAGE & STORAGE_ATA)
    STORAGE_DEC_IDX_ATA,
#endif
#if (CONFIG_STORAGE & STORAGE_MMC)
    STORAGE_DEC_IDX_MMC,
#endif
#if (CONFIG_STORAGE & STORAGE_SD)
    STORAGE_DEC_IDX_SD,
#endif
#if (CONFIG_STORAGE & STORAGE_NAND)
    STORAGE_DEC_IDX_NAND,
#endif
#if (CONFIG_STORAGE & STORAGE_RAMDISK)
    STORAGE_DEC_IDX_RAMDISK,
#endif
#if (CONFIG_STORAGE & STORAGE_USB)
    STORAGE_DEC_IDX_USB,
#endif
#if (CONFIG_STORAGE & STORAGE_HOSTFS)
    STORAGE_DEC_IDX_HOSTFS,
#endif
    STORAGE_NUM_DEC_IDX,
};

static const char * const storage_dec_names[STORAGE_NUM_DEC_IDX+1] =
{
#if (CONFIG_STORAGE & STORAGE_ATA)
    [STORAGE_DEC_IDX_ATA]     = ATA_VOL_DEC,
#endif
#if (CONFIG_STORAGE & STORAGE_MMC)
    [STORAGE_DEC_IDX_MMC]     = MMC_VOL_DEC,
#endif
#if (CONFIG_STORAGE & STORAGE_SD)
    [STORAGE_DEC_IDX_SD]      = SD_VOL_DEC,
#endif
#if (CONFIG_STORAGE & STORAGE_NAND)
    [STORAGE_DEC_IDX_NAND]    = NAND_VOL_DEC,
#endif
#if (CONFIG_STORAGE & STORAGE_RAMDISK)
    [STORAGE_DEC_IDX_RAMDISK] = RAMDISK_VOL_DEC,
#endif
#if (CONFIG_STORAGE & STORAGE_USB)
    [STORAGE_DEC_IDX_USB]     = USB_VOL_DEC,
#endif
#if (CONFIG_STORAGE & STORAGE_HOSTFS)
    [STORAGE_DEC_IDX_HOSTFS]  = HOSTFS_VOL_DEC,
#endif
    [STORAGE_NUM_DEC_IDX]     = DEFAULT_VOL_DEC,
};

static const unsigned char storage_dec_indexes[STORAGE_NUM_TYPES+1] =
{
    [0 ... STORAGE_NUM_TYPES] = STORAGE_NUM_DEC_IDX,
#if (CONFIG_STORAGE & STORAGE_ATA)
    [STORAGE_ATA_NUM]     = STORAGE_DEC_IDX_ATA,
#endif
#if (CONFIG_STORAGE & STORAGE_MMC)
    [STORAGE_MMC_NUM]     = STORAGE_DEC_IDX_MMC,
#endif
#if (CONFIG_STORAGE & STORAGE_SD)
    [STORAGE_SD_NUM]      = STORAGE_DEC_IDX_SD,
#endif
#if (CONFIG_STORAGE & STORAGE_NAND)
    [STORAGE_NAND_NUM]    = STORAGE_DEC_IDX_NAND,
#endif
#if (CONFIG_STORAGE & STORAGE_RAMDISK)
    [STORAGE_RAMDISK_NUM] = STORAGE_DEC_IDX_RAMDISK,
#endif
#if (CONFIG_STORAGE & STORAGE_USB)
    [STORAGE_USB_NUM]     = STORAGE_DEC_IDX_USB,
#endif
#if (CONFIG_STORAGE & STORAGE_HOSTFS)
    [STORAGE_HOSTFS_NUM]  = STORAGE_DEC_IDX_HOSTFS,
#endif
};

/* builds a list of drive/volume specifiers <volstr#> */
void init_volume_names(void)
{
    DEBUGF("%s: ", __func__);
    FOR_EACH_VOLUME(-1, volume)
    {
        const char *voldec = "";
        char *buffer = vol_dec_strings[volume];

        int type = storage_driver_type(volume_drive(volume));
        if (type < 0 || type > STORAGE_NUM_TYPES)
            type = STORAGE_NUM_TYPES;
        voldec = storage_dec_names[storage_dec_indexes[type]];
        snprintf(buffer, VOL_MAX_LEN + 1, "%c%s%d%c",
                VOL_START_TOK, voldec, volume, VOL_END_TOK);
        DEBUGF("vol<%d> = %s ", volume, buffer);
    }
    DEBUGF("\n");
}

#include <stdio.h>

int path_get_volume_id(const char *name)
{
    int v = -1;

    if (!name || *name != VOL_START_TOK)
        goto bail;

    do {
        switch (*name)
        {
        case '0' ... '9':       /* digit; parse volume number */
            v = (v * 10 + *name - '0') % VOL_NUM_MAX;
            break;
        case '\0':
        case PATH_SEPCH:        /* no closing bracket; no volume */
            v = -1;
            goto bail;
        default:                /* something else; reset volume */
            v = 0;
        }
    } while (*++name != VOL_END_TOK);  /* found end token? */

bail:
    return v;
}

/* Returns on which volume this is and sets *nameptr to the portion of the
 * path after the volume specifier, which could be the null if the path is
 * just a volume root. If *nameptr > name, then a volume specifier was
 * found. If 'greedy' is 'true', then all separators after the volume
 * specifier are consumed.
 */
int path_strip_volume(const char *name, const char **nameptr, bool greedy)
{
    int volume = ROOT_VOLUME;
    const char *t = name;
    int c, v = 0;

    /* format: "/<xxx##>/foo/bar"
     * the "xxx" is pure decoration; only an unbroken trailing string of
     * digits within the brackets is parsed as the volume number and of
     * those, only the last ones VOL_MUM_MAX allows.
     */
    t = GOBBLE_PATH_SEPCH(t);   /* skip all leading slashes */
    if (t == name)
    {
        volume = -1;            /* relative path; don't know */
        goto psv_out;
    }
    c = *t;
    if (c != VOL_START_TOK)     /* missing start token? no volume */
        goto psv_out;

    do
    {
        switch (c)
        {
        case '0' ... '9':       /* digit; parse volume number */
            v = (v * 10 + c - '0') % VOL_NUM_MAX;
            break;
        case '\0':
        case PATH_SEPCH:        /* no closing bracket; no volume */
            goto psv_out;
        default:                /* something else; reset volume */
            v = 0;
        }
    }
    while ((c = *++t) != VOL_END_TOK);  /* found end token? */

    if (!(c = *++t))            /* no more path and no '/' is ok */
        ;
    else if (c != PATH_SEPCH)   /* more path and no separator after end */
        goto psv_out;
    else if (greedy)
        t = GOBBLE_PATH_SEPCH(++t); /* strip remaining separators */

    /* if 'greedy' is true and **nameptr == '\0' then it's only a volume
       root whether or not it has trailing separators */

    volume = v;
    name = t;
psv_out:
    if (nameptr)
        *nameptr = name;
    return volume;
}

/* Strip the last volume component in the path and return the remainder of
 * the path in *nameptr. If 'greedy' is 'true', then all separators after
 * the volume specifier are consumed.
 */
int path_strip_last_volume(const char *name, const char **nameptr, bool greedy)
{
    const char *p = name + strlen(name);

    while (p > name)
    {
        /* skip the component */
        while (p > name && p[-1] != PATH_SEPCH)
            --p;

        /* bail if we reached the beginning */
        if (p <= name+1)
            break;

        /* point at the seprator */
        --p;

        /* try to strip the volume and return it if found */
        int volume = path_strip_volume(p, nameptr, greedy);
        if (volume != ROOT_VOLUME)
            return volume;

        /* skip any extra separators */
        while (p > name && p[-1] == PATH_SEPCH)
            --p;
    }

    /* return whatever is at the beginning of the path */
    return path_strip_volume(name, nameptr, greedy);
}

/* Returns the volume specifier decorated with the storage type name.
 * Assumes the supplied buffer size is at least {VOL_MAX_LEN}+1,
 * vol_dec_strings has been initialized by init_volume_names().
 */
int get_volume_name(int volume, char *buffer)
{
    if (volume < 0 || volume == ROOT_VOLUME)
    {
        char *t = buffer;
        if (volume == ROOT_VOLUME)
            *t++ = PATH_ROOTCHR;
        *t = '\0';
        return t - buffer;
    }

    volume %= VOL_NUM_MAX; /* as path parser would have it */

    return strlcpy(buffer, vol_dec_strings[volume], VOL_MAX_LEN + 1);
}

/* Returns volume name formatted with the root. Assumes buffer size is at
 * least {VOL_MAX_LEN}+2, vol_dec_strings has been initialized by init_volume_names().
 */
int make_volume_root(int volume, char *buffer)
{
    char *t = buffer;
    if (volume >= 0 && volume != ROOT_VOLUME)
        *t++ = PATH_ROOTCHR;
    t += get_volume_name(volume, t);
    return t - buffer;
}
#endif /* HAVE_MULTIVOLUME */


/* Just like path_strip_volume() but strips a leading drive specifier and
 * returns the drive number (A=0, B=1, etc.). -1 means no drive was found.
 * If 'greedy' is 'true', all separators after the volume are consumed.
 */
int path_strip_drive(const char *name, const char **nameptr, bool greedy)
{
    int c = toupper(*name);

    if (c >= 'A' && c <= 'Z' && name[1] == PATH_DRVSEPCH)
    {
        name = &name[2];
        if (greedy)
            name = GOBBLE_PATH_SEPCH(name);

        *nameptr = name;
        return c - 'A';
    }

    *nameptr = name;
    return -1;
}

/* Strips directory components from the path
 * ""      *nameptr->NUL,   len=0: ""
 * "/"     *nameptr->/,     len=1: "/"
 * "//"    *nameptr->2nd /, len=1: "/"
 * "/a"    *nameptr->a,     len=1: "a"
 * "a/"    *nameptr->a,     len=1: "a"
 * "/a/bc" *nameptr->b,     len=2: "bc"
 * "d"     *nameptr->d,     len=1: "d"
 * "ef/gh" *nameptr->g,     len=2: "gh"
 *
 * Notes: * Doesn't do suffix removal at this time.
 *        * In the same string, path_dirname() returns a pointer with the
 *          same or lower address as path_basename().
 *        * Pasting a separator between the returns of path_dirname() and
 *          path_basename() will result in a path equivalent to the input.
 */
size_t path_basename(const char *name, const char **nameptr)
{
    const char *p = name;
    const char *q = p;
    const char *r = q;

    while (*(p = GOBBLE_PATH_SEPCH(p)))
    {
        q = p;
        p = GOBBLE_PATH_COMP(++p);
        r = p;
    }

    if (r == name && p > name)
        q = p, r = q--; /* root - return last slash */
    /* else path is an empty string */

    *nameptr = q;
    return r - q;
}

/* Strips the trailing component from the path
 * ""      *nameptr->NUL,   len=0: ""
 * "/"     *nameptr->/,     len=1: "/"
 * "//"    *nameptr->2nd /, len=1: "/"
 * "/a"    *nameptr->/,     len=1: "/"
 * "a/"    *nameptr->a,     len=0: ""
 * "/a/bc" *nameptr->/,     len=2: "/a"
 * "d"     *nameptr->d,     len=0: ""
 * "ef/gh" *nameptr->e,     len=2: "ef"
 *
 * Notes: * Interpret len=0 as ".".
 *        * In the same string, path_dirname() returns a pointer with the
 *          same or lower address as path_basename().
 *        * Pasting a separator between the returns of path_dirname() and
 *          path_basename() will result in a path equivalent to the input.
 *
 */
size_t path_dirname(const char *name, const char **nameptr)
{
    const char *p = GOBBLE_PATH_SEPCH(name);
    const char *q = name;
    const char *r = p;

    while (*(p = GOBBLE_PATH_COMP(p)))
    {
        const char *s = p;

        if (!*(p = GOBBLE_PATH_SEPCH(p)))
            break;

        q = s;
    }

    if (q == name && r > name)
        name = r, q = name--; /* root - return last slash */

    *nameptr = name;
    return q - name;
}

/* Removes trailing separators from a path
 * ""     *nameptr->NUL,   len=0: ""
 * "/"    *nameptr->/,     len=1: "/"
 * "//"   *nameptr->2nd /, len=1: "/"
 * "/a/"  *nameptr->/,     len=2: "/a"
 * "//b/" *nameptr->1st /, len=3: "//b"
 * "/c/"  *nameptr->/,     len=2: "/c"
 */
size_t path_strip_trailing_separators(const char *name, const char **nameptr)
{
    const char *p;
    size_t len = path_basename(name, &p);

    if (len == 1 && *p == '/' && p > name)
    {
        *nameptr = p;
        name = p - 1; /* root with multiple separators */
    }
    else
    {
        *nameptr = name;
        p += len; /* length to end of basename */
    }

    return p - name;
}

/* Transforms "wrong" separators into the correct ones
 * "c:\windows\system32" -> "c:/windows/system32"
 *
 * 'path' and 'dstpath' may either be the same buffer or non-overlapping
 */
void path_correct_separators(char *dstpath, const char *path)
{
    char *dstp = dstpath;
    const char *p = path;

    while (1)
    {
        const char *next = strchr(p, PATH_BADSEPCH);
        if (!next)
            break;

        size_t size = next - p;

        if (dstpath != path)
            memcpy(dstp, p, size); /* not in-place */

        dstp += size;
        *dstp++ = PATH_SEPCH;
        p = next + 1;
    }

    if (dstpath != path)
        strcpy(dstp, p);
}

/* Remove dot segments from the path
 *
 * 'path' and 'dstpath' may either be the same buffer or non-overlapping
 */
void path_remove_dot_segments (char *dstpath, const char *path)
{
    char *dstp = dstpath;
    char *odstp = dstpath;
    const char *p = path;

    while (*p)
    {
        if (p[0] == '.' && p[1] == PATH_SEPCH)
            p += 2;
        else if (p[0] == '.' && p[1] == '.' && p[2] == PATH_SEPCH)
            p += 3;
        else if (p[0] == PATH_SEPCH && p[1] == '.' && p[2] == PATH_SEPCH)
            p += 2;
        else if (p[0] == PATH_SEPCH && p[1] == '.' && !p[2])
        {
            *dstp++ = PATH_SEPCH;
            break;
        }
        else if (p[0] == PATH_SEPCH && p[1] == '.' &&
            p[2] == '.' && p[3] == PATH_SEPCH)
        {
            dstp = odstp;
            p += 3;
        }
        else if (p[0] == PATH_SEPCH && p[1] == '.' && p[2] == '.' && !p[3])
        {
            dstp = odstp;
            *dstp++ = PATH_SEPCH;
            break;
        }
        else if (p[0] == '.' && !p[1])
            break;
        else if (p[0] == '.' && p[1] == '.' && !p[2])
            break;
        else
        {
            odstp = dstp;
            if (p[0] == PATH_SEPCH)
                *dstp++ = *p++;
            while (p[0] && p[0] != PATH_SEPCH)
                *dstp++ = *p++;
        }
    }
    *dstp = 0;
}

/* Appends one path to another, adding separators between components if needed.
 * basepath_max can be used to truncate the basepath if desired
 * NOTE: basepath is truncated after copying to the buffer so there must be enough
 * free space for the entirety of the basepath even if the resulting string would fit
 *
 * Return value and behavior is otherwise as strlcpy so that truncation may be
 * detected.
 *
 * For basepath and component:
 * PA_SEP_HARD adds a separator even if the base path is empty
 * PA_SEP_SOFT adds a separator only if the base path is not empty
 */
size_t path_append_ex(char *buf, const char *basepath, size_t basepath_max,
                   const char *component, size_t bufsize)
{
    size_t len = 0;
    bool separate = false;
    const char *base = basepath && basepath[0] ? basepath : buf;
    if (!base)
        return bufsize; /* won't work to get lengths from buf */

    if (!buf)
        bufsize = 0;

    if (path_is_absolute(component)) /* starts with a '/' path separator */
    {
        #if defined(HAVE_MULTIVOLUME)
            const char *baseend;
            /* our component doesn't appear to have a drive/volume */
            if (path_strip_volume(component, &baseend, false) == ROOT_VOLUME)
            {
                /* find the basepath's drive/volume name, if it has one.
                 * ensure basepath is not NULL, that causes a crash on sim */
                if (basepath != NULL && path_strip_volume(basepath, &baseend, false) != ROOT_VOLUME)
                {
                    basepath_max = baseend - basepath;
                    /* strip leading separators from component */
                    while (*component == PATH_SEPCH)
                        component++;
                } else {
                    /* component is absolute with no drive/volume, but basepath doesn't 
                     * have a volume either... we must be on root volume */
                    basepath = component;
                    component = "";
                    basepath_max = -1u;
                }
            }
            /* else component likely already has drive/volume name */
            else
        #endif
            {
                /* 'component' is absolute; replace all */
                basepath = component;
                component = "";
                basepath_max = -1u;
            }
    }

    /* if basepath is not null or empty, buffer contents are replaced,
       otherwise buf contains the base path */

    if (base == buf)
        len = strlen(buf);
    else if (basepath)
    {
        /* strip extra leading separators */
        while (*basepath == PATH_SEPCH && *(basepath + 1) == PATH_SEPCH)
            basepath++;
        len = strlcpy(buf, basepath, bufsize);
        if (basepath_max < len) /*if needed truncate basepath to basepath_max */
        {
            len = basepath_max;
            buf[MIN(bufsize - 1, basepath_max)] = '\0';
        }
    }

    if (!basepath || !component || basepath_max == 0)
        separate = !len || base[len-1] != PATH_SEPCH;
    else if (component[0])
        separate = len && base[len-1] != PATH_SEPCH;

    /* caller might lie about size of buf yet use buf as the base */
    if (base == buf && bufsize && len >= bufsize)
        buf[bufsize - 1] = '\0';

    buf += len;
    bufsize -= MIN(len, bufsize);

    if (separate && (len++, bufsize > 0) && --bufsize > 0)
        *buf++ = PATH_SEPCH;

    return len + strlcpy(buf, component ?: "", bufsize);
}


size_t path_append(char *buf, const char *basepath,
                   const char *component, size_t bufsize)
{
    return path_append_ex(buf, basepath, -1u, component, bufsize);
}
/* Returns the location and length of the next path component, consuming the
 * input in the process.
 *
 * "/a/bc/d" breaks into:
 *   start:  *namep->1st /
 *   call 1: *namep->a,   *pathp->2nd / len=1: "a"
 *   call 2: *namep->b,   *pathp->3rd / len=2: "bc"
 *   call 3: *namep->d,   *pathp->NUL,  len=1: "d"
 *   call 4: *namep->NUL, *pathp->NUL,  len=0: ""
 *
 * Returns: 0 if the input has been consumed
 *          The length of the component otherwise
 */
ssize_t parse_path_component(const char **pathp, const char **namep)
{
    /* a component starts at a non-separator and continues until the next
       separator or null */
    const char *p = GOBBLE_PATH_SEPCH(*pathp);
    const char *name = p;

    if (*p)
        p = GOBBLE_PATH_COMP(++p);

    *pathp = p;
    *namep = name;

    return p - name;
}
