/*
 * (C) Copyright 2002
 * Stäubli Faverges - <www.staubli.com>
 * Pierre AUBERT  p.aubert@staubli.com
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * 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 program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <common.h>
#include <config.h>
#include <malloc.h>

#if defined(CONFIG_CMD_FDOS)

#include "dos.h"
#include "fdos.h"

static int cache_sect;
static unsigned char cache [SZ_STD_SECTOR];


#define min(x,y) ((x)<(y)?(x):(y))

static int descend (Slot_t *parent,
	     Fs_t *fs,
		    char *path);

/*-----------------------------------------------------------------------------
 * init_subdir --
 *-----------------------------------------------------------------------------
 */
void init_subdir (void)
{
    cache_sect = -1;
}
/*-----------------------------------------------------------------------------
 * basename --
 *-----------------------------------------------------------------------------
 */
char *basename (char *name)
{
    register char *cptr;

    if (!name || !*name) {
	return ("");
    }

    for (cptr= name; *cptr++; );
    while (--cptr >= name) {
	if (*cptr == '/')    {
	    return (cptr + 1);
	}
    }
    return(name);
}
/*-----------------------------------------------------------------------------
 * root_map --
 *-----------------------------------------------------------------------------
 */
static int root_map (Fs_t *fs, Slot_t *file, int where, int *len)
{
    *len = min (*len, fs -> dir_len * SZ_STD_SECTOR - where);
    if (*len < 0 ) {
	*len = 0;
	return (-1);
    }
    return fs -> dir_start * SZ_STD_SECTOR + where;
}
/*-----------------------------------------------------------------------------
 * normal_map --
 *-----------------------------------------------------------------------------
 */
static int normal_map (Fs_t *fs, Slot_t *file, int where, int *len)
{
    int offset;
    int NrClu;
    unsigned short RelCluNr;
    unsigned short CurCluNr;
    unsigned short NewCluNr;
    unsigned short AbsCluNr;
    int clus_size;

    clus_size = fs -> cluster_size * SZ_STD_SECTOR;
    offset = where % clus_size;

    *len = min (*len, file -> FileSize - where);

    if (*len < 0 ) {
	*len = 0;
	return (0);
    }

    if (file -> FirstAbsCluNr < 2){
	*len = 0;
	return (0);
    }

    RelCluNr = where / clus_size;

    if (RelCluNr >= file -> PreviousRelCluNr){
	CurCluNr = file -> PreviousRelCluNr;
	AbsCluNr = file -> PreviousAbsCluNr;
    } else {
	CurCluNr = 0;
	AbsCluNr = file -> FirstAbsCluNr;
    }


    NrClu = (offset + *len - 1) / clus_size;
    while (CurCluNr <= RelCluNr + NrClu) {
	if (CurCluNr == RelCluNr){
	    /* we have reached the beginning of our zone. Save
	     * coordinates */
	    file -> PreviousRelCluNr = RelCluNr;
	    file -> PreviousAbsCluNr = AbsCluNr;
	}
	NewCluNr = fat_decode (fs, AbsCluNr);
	if (NewCluNr == 1 || NewCluNr == 0) {
	    PRINTF("Fat problem while decoding %d %x\n",
		    AbsCluNr, NewCluNr);
	    return (-1);
	}
	if (CurCluNr == RelCluNr + NrClu) {
	    break;
	}

	if (CurCluNr < RelCluNr && NewCluNr == FAT12_END) {
	    *len = 0;
	    return 0;
	}

	if (CurCluNr >= RelCluNr && NewCluNr != AbsCluNr + 1)
	    break;
	CurCluNr++;
	AbsCluNr = NewCluNr;
    }

    *len = min (*len, (1 + CurCluNr - RelCluNr) * clus_size - offset);

    return (((file -> PreviousAbsCluNr - 2) * fs -> cluster_size +
	     fs -> dir_start + fs -> dir_len) *
	    SZ_STD_SECTOR + offset);
}
/*-----------------------------------------------------------------------------
 * open_subdir -- open the subdir containing the file
 *-----------------------------------------------------------------------------
 */
int open_subdir (File_t *desc)
{
    char *pathname;
    char *tmp, *s, *path;
    char terminator;

    if ((pathname = (char *)malloc (MAX_PATH)) == NULL) {
	return (-1);
    }

    strcpy (pathname, desc -> name);

    /* Suppress file name                                                    */
    tmp = basename (pathname);
    *tmp = '\0';

    /* root directory  init                                                  */
    desc -> subdir.FirstAbsCluNr = 0;
    desc -> subdir.FileSize = -1;
    desc -> subdir.map = root_map;
    desc -> subdir.dir.attr = ATTR_DIRECTORY;

    tmp = pathname;
    for (s = tmp; ; ++s) {
	if (*s == '/' || *s == '\0') {
	    path = tmp;
	    terminator = *s;
	    *s = '\0';
	    if (s != tmp && strcmp (path,".")) {
		if (descend (&desc -> subdir, desc -> fs, path) < 0) {
		    free (pathname);
		    return (-1);
		}
	    }
	    if (terminator == 0) {
		break;
	    }
	    tmp = s + 1;
	}
    }
    free (pathname);
    return (0);
}
/*-----------------------------------------------------------------------------
 * descend --
 *-----------------------------------------------------------------------------
 */
static int descend (Slot_t *parent,
	     Fs_t *fs,
	     char *path)
{
    int entry;
    Slot_t SubDir;

    if(path[0] == '\0' || strcmp (path, ".") == 0) {
	return (0);
    }


    entry = 0;
    if (vfat_lookup (parent,
		     fs,
		     &(SubDir.dir),
		     &entry,
		     0,
		     path,
		     ACCEPT_DIR | SINGLE | DO_OPEN,
		     0,
		     &SubDir) == 0) {
	*parent = SubDir;
	return (0);
    }

    if (strcmp(path, "..") == 0) {
	parent -> FileSize = -1;
	parent -> FirstAbsCluNr = 0;
	parent -> map = root_map;
	return (0);
    }
    return (-1);
}
/*-----------------------------------------------------------------------------
 * open_file --
 *-----------------------------------------------------------------------------
 */
int open_file (Slot_t *file, Directory_t *dir)
{
    int first;
    unsigned long size;

    first = __le16_to_cpu (dir -> start);

    if(first == 0 &&
       (dir -> attr & ATTR_DIRECTORY) != 0) {
	file -> FirstAbsCluNr = 0;
	file -> FileSize = -1;
	file -> map = root_map;
	return (0);
    }

    if ((dir -> attr & ATTR_DIRECTORY) != 0) {
	size = (1UL << 31) - 1;
    }
    else {
	size = __le32_to_cpu (dir -> size);
    }

    file -> map = normal_map;
    file -> FirstAbsCluNr = first;
    file -> PreviousRelCluNr = 0xffff;
    file -> FileSize = size;
    return (0);
}
/*-----------------------------------------------------------------------------
 * read_file --
 *-----------------------------------------------------------------------------
 */
int read_file (Fs_t *fs,
	       Slot_t *file,
	       char *buf,
	       int where,
	       int len)
{
    int pos;
    int read, nb, sect, offset;

    pos = file -> map (fs, file, where, &len);
    if  (pos < 0) {
	return -1;
    }
    if (len == 0) {
	return (0);
    }

    /* Compute sector number                                                 */
    sect = pos / SZ_STD_SECTOR;
    offset = pos % SZ_STD_SECTOR;
    read = 0;

    if (offset) {
	/* Read doesn't start at the sector beginning. We need to use our    */
	/* cache                                                             */
	if (sect != cache_sect) {
	    if (dev_read (cache, sect, 1) < 0) {
		return (-1);
	    }
	    cache_sect = sect;
	}
	nb = min (len, SZ_STD_SECTOR - offset);

	memcpy (buf, cache + offset, nb);
	read += nb;
	len -= nb;
	sect += 1;
    }

    if (len > SZ_STD_SECTOR) {
	nb = (len - 1) / SZ_STD_SECTOR;
	if (dev_read (buf + read, sect, nb) < 0) {
	    return ((read) ? read : -1);
	}
	/* update sector position                                            */
	sect += nb;

	/* Update byte position                                              */
	nb *= SZ_STD_SECTOR;
	read += nb;
	len -= nb;
    }

    if (len) {
	if (sect != cache_sect) {
	    if (dev_read (cache, sect, 1) < 0) {
		return ((read) ? read : -1);
		cache_sect = -1;
	    }
	    cache_sect = sect;
	}

	memcpy (buf + read, cache, len);
	read += len;
    }
    return (read);
}
#endif
