/***************************************************************************
 *             __________               __   ___.
 *   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.
 *
 ****************************************************************************/
#ifndef _DIRCACHE_REDIRECT_H_

#include "rbpaths.h"
#include "pathfuncs.h"
#include "dir.h"
#include "dircache.h"
#include "file.h"

#if defined(HAVE_MULTIBOOT) && !defined(SIMULATOR) && !defined(BOOTLOADER)
#include "rb-loader.h"
#include "multiboot.h"
#include "bootdata.h"
#endif

#ifndef RB_ROOT_VOL_HIDDEN
#define RB_ROOT_VOL_HIDDEN(v)   (0 == 0)
#endif
#ifndef RB_ROOT_CONTENTS_DIR
#define RB_ROOT_CONTENTS_DIR    "/"
#endif
/***
 ** Internal redirects that depend upon whether or not dircache is made
 **
 ** Some stuff deals with it, some doesn't right now. This is a nexus point..
 **/

/** File binding **/

static inline void get_rootinfo_internal(struct file_base_info *infop)
{
#ifdef HAVE_DIRCACHE
    dircache_get_rootinfo(infop);
#else
    (void)infop;
#endif
}

static inline void fileobj_bind_file(struct file_base_binding *bindp)
{
#ifdef HAVE_DIRCACHE
    dircache_bind_file(bindp);
#else
    file_binding_insert_last(bindp);
#endif
}

static inline void fileobj_unbind_file(struct file_base_binding *bindp)
{
#ifdef HAVE_DIRCACHE
    dircache_unbind_file(bindp);
#else
    file_binding_remove(bindp);
#endif
}


/** File event handlers **/

static inline void fileop_onopen_internal(struct filestr_base *stream,
                                          struct file_base_info *srcinfop,
                                          unsigned int callflags)
{
    fileobj_fileop_open(stream, srcinfop, callflags);
}

static inline void fileop_onclose_internal(struct filestr_base *stream)
{
    fileobj_fileop_close(stream);
}

static inline void fileop_oncreate_internal(struct filestr_base *stream,
                                            struct file_base_info *srcinfop,
                                            unsigned int callflags,
                                            struct file_base_info *dirinfop,
                                            const char *basename)
{
#ifdef HAVE_DIRCACHE
    dircache_dcfile_init(&srcinfop->dcfile);
#endif
    fileobj_fileop_create(stream, srcinfop, callflags);
#ifdef HAVE_DIRCACHE
    struct dirinfo_native din;
    fill_dirinfo_native(&din);
    dircache_fileop_create(dirinfop, stream->bindp, basename, &din);
#endif
    (void)dirinfop; (void)basename;
}

static inline void fileop_onremove_internal(struct filestr_base *stream,
                                            struct file_base_info *oldinfop)
{
    fileobj_fileop_remove(stream, oldinfop);
#ifdef HAVE_DIRCACHE
    dircache_fileop_remove(stream->bindp);
#endif
}

static inline void fileop_onrename_internal(struct filestr_base *stream,
                                            struct file_base_info *oldinfop,
                                            struct file_base_info *dirinfop,
                                            const char *basename)
{
    fileobj_fileop_rename(stream, oldinfop);
#ifdef HAVE_DIRCACHE
    dircache_fileop_rename(dirinfop, stream->bindp, basename);
#endif
    (void)dirinfop; (void)basename;
}

static inline void fileop_onsync_internal(struct filestr_base *stream)
{
    fileobj_fileop_sync(stream);
#ifdef HAVE_DIRCACHE
    struct dirinfo_native din;
    fill_dirinfo_native(&din);
    dircache_fileop_sync(stream->bindp, &din);
#endif
}

#if defined(HAVE_MULTIBOOT) && !defined(SIMULATOR) && !defined(BOOTLOADER)
static inline bool multiboot_is_boot_volume(int volume)
{
    if (boot_data.version == 0)
    {
        /*
         * Version 0 bootloaders just pass the volume number, but that's
         * dynamically assigned and sometimes differs by the time we get
         * into the firmware. So we can't rely on the volume passed by
         * the bootloader.
         */
#if CONFIG_CPU == X1000
        /* The affected X1000 players only have one drive to begin with */
        return volume_drive(volume) == 0;
#else
        /* FIXME: Anything else that can get here is a Sansa. */
        return volume_drive(volume) == boot_data._boot_volume ||
               volume == boot_data._boot_volume;
#endif
    }

    if (boot_data.version == 1)
    {
        /*
         * Since version 1 the bootloader passes drive and partition
         * number which unambiguously identifies the boot volume.
         */
        return volume_drive(volume) == boot_data.boot_drive &&
               volume_partition(volume) == boot_data.boot_partition;
    }

    return false;
}
#endif

static inline void volume_onmount_internal(IF_MV_NONVOID(int volume))
{
#if defined(HAVE_MULTIBOOT) && !defined(SIMULATOR) && !defined(BOOTLOADER)
    char path[VOL_MAX_LEN+2];
    char rtpath[MAX_PATH / 2];
    make_volume_root(volume, path);

    if (boot_data_valid)
    {
        /* we need to mount the drive before we can access it */
        root_mount_path(path, 0); /* root could be different folder don't hide */

        if (multiboot_is_boot_volume(IF_MV_VOL(volume)))
        {
            /* get the full path to the BOOTFILE
               ie. /<0>/redirectdir/.rockbox/rockbox.ext */
            int rtlen = get_redirect_dir(rtpath, sizeof(rtpath),
                                         volume, BOOTDIR, BOOTFILE);

            if (rtlen <= 0 || rtlen >= (int) sizeof(rtpath))
                rtlen = 0; /* path too long or sprintf error */
            else if (file_exists(rtpath))
            {
                rtlen = get_redirect_dir(rtpath, sizeof(rtpath), volume, "", "");
                while (rtlen > 0 && rtpath[--rtlen] == PATH_SEPCH)
                    rtpath[rtlen] = '\0'; /* remove separators */
            }
            else
                rtlen = 0; /* No BOOTFILE found */

#if 0 /*removed, causes issues with playback for now?*/
            if (rtlen <= 0 || rtpath[rtlen] == VOL_END_TOK)
                root_unmount_volume(volume); /* unmount so root can be hidden*/
#endif
            if (rtlen <= 0 || root_mount_path(rtpath, NSITEM_CONTENTS) != 0)
            {   /* Error occurred, card removed? Set root to default */
                boot_data_valid = false;
                root_unmount_volume(volume); /* unmount so root can be hidden*/
                goto standard_redirect;
            }
        }
    }
    else
    {
standard_redirect:
        root_mount_path(path, RB_ROOT_VOL_HIDDEN(volume) ? NSITEM_HIDDEN : 0);
        if (volume == path_strip_volume(RB_ROOT_CONTENTS_DIR, NULL, false))
            root_mount_path(RB_ROOT_CONTENTS_DIR, NSITEM_CONTENTS);
    }
#elif defined(HAVE_MULTIVOLUME)
    char path[VOL_MAX_LEN+2];
    make_volume_root(volume, path);
    root_mount_path(path, RB_ROOT_VOL_HIDDEN(volume) ? NSITEM_HIDDEN : 0);
    if (volume == path_strip_volume(RB_ROOT_CONTENTS_DIR, NULL, false))
        root_mount_path(RB_ROOT_CONTENTS_DIR, NSITEM_CONTENTS);
#else
    const char *path = PATH_ROOTSTR;
    root_mount_path(path, RB_ROOT_VOL_HIDDEN(volume) ? NSITEM_HIDDEN : 0);
    root_mount_path(RB_ROOT_CONTENTS_DIR, NSITEM_CONTENTS);
#endif /* HAVE_MULTIBOOT */

#ifdef HAVE_DIRCACHE
    dircache_mount();
#endif
}

static inline void volume_onunmount_internal(IF_MV_NONVOID(int volume))
{
#ifdef HAVE_DIRCACHE
    /* First, to avoid update of something about to be destroyed anyway */
    dircache_unmount(IF_MV(volume));
#endif
    root_unmount_volume(IF_MV(volume));
    fileobj_mgr_unmount(IF_MV(volume));
}

static inline void fileop_onunmount_internal(struct filestr_base *stream)
{

    if (stream->flags & FD_WRITE)
        force_close_writer_internal(stream); /* try to save stuff */
    else
        fileop_onclose_internal(stream);     /* just readers, bye */
}


/** Directory reading **/

static inline int readdir_dirent(struct filestr_base *stream,
                                 struct dirscan_info *scanp,
                                 struct DIRENT *entry)
{
#ifdef HAVE_DIRCACHE
    return dircache_readdir_dirent(stream, scanp, entry);
#else
    return uncached_readdir_dirent(stream, scanp, entry);
#endif
}

static inline void rewinddir_dirent(struct dirscan_info *scanp)
{
#ifdef HAVE_DIRCACHE
    dircache_rewinddir_dirent(scanp);
#else
    uncached_rewinddir_dirent(scanp);
#endif
}

static inline int readdir_internal(struct filestr_base *stream,
                                   struct file_base_info *infop,
                                   struct fat_direntry *fatent)
{
#ifdef HAVE_DIRCACHE
    return dircache_readdir_internal(stream, infop, fatent);
#else
    return uncached_readdir_internal(stream, infop, fatent);
#endif
}

static inline void rewinddir_internal(struct file_base_info *infop)
{
#ifdef HAVE_DIRCACHE
    dircache_rewinddir_internal(infop);
#else
    uncached_rewinddir_internal(infop);
#endif
}


/** Misc. stuff **/

static inline struct fat_direntry *get_dir_fatent_dircache(void)
{
#ifdef HAVE_DIRCACHE
    return get_dir_fatent();
#else
    return NULL;
#endif
}

#endif /* _DIRCACHE_REDIRECT_H_ */
