/*
 * PUNY
 *
 * This is Public Domain software
 *
 * Written 2006 by VladiTX <vladitx@nucleusys.com>
 */

#include <string.h>

#include "cfg.h"
#include "rtns.h"

#include "puny.h"

extern long image_start_sector;

/* ------------------------------------------------------------------------ */

sect_t puny_sect_min, puny_sect_max;		/* allowed I/O range */

sect_t puny_fat_start;
byte puny_fat_type;				/* 0- FAT12, 1- FAT16 */

sect_t puny_root_start;				/* root directory */
uint16 puny_root_size;

byte puny_cluster_size;
sect_t puny_cluster2_start;
clust_t puny_cluster_max;


byte mask;
byte log_cl_size;


/* ------------------------------------------------------------------------ */

/* --- sector i/o primitives --- */

/*
 * read sector into buffer
 */

int puny_sect_read (sect_t sect)
{
	if ((sect < puny_sect_min) || (sect >= puny_sect_max))
		return 1;

	if (puny_sib == sect)
		return 0;

	puny_sect_invalidate ();
	//	if (puny_ext_read (sect, puny_buf))
	if(mmcRead(sect, puny_buf))
	  return 1;
	puny_sib = sect;

	return 0;
}


#if FULL_FAT

/* ------------------------------------------------------------------------ */

/* --- File Allocation Table primitives --- */

/*
 * dereference a cluster
 */

clust_t puny_fat_peek (clust_t cl)
{
	byte *t;
	clust_t l, fs;
	uint16 fo;

	if (puny_fat_type == 0) {		/* FAT12 */
		fs = (l = cl * 3) >> 1;
		fo = fs & 511;
		fs >>= 9;

		if (puny_sect_read (puny_fat_start + fs++))
			return 0;

		t = puny_buf + fo;
		cl = *t;
		if (++fo == 512) {		/* nibbles on boundary? */
			fo = 0;
			if (puny_sect_read (puny_fat_start + fs))
				return 0;
		}
		t = puny_buf + fo;
		cl |= *t << 8;
		if (l & 1)
			cl >>= 4;
		cl &= 0xFFF;

		if ((cl >= puny_cluster_max) || (cl > 0xFF0))
			return 0;

		return cl;
	} else {				/* FAT16 */
		fs = cl >> 8;
		fo = (cl & 255) << 1;

		if (puny_sect_read (puny_fat_start + fs))
			return 0;

		cl = rtns_get_u16le (puny_buf + fo);

		if ((cl >= puny_cluster_max) || (cl > 0xFFF0))
			return 0;

		return cl;
	}
}

/* ------------------------------------------------------------------------ */

/* --- cluster cache primitives --- */

/*
 * init a cluster cache with a given start cluster
 *
 * cache entry #0 is special:
 *	[0].clord = size of the cache (in entries)
 *	[0].cl  = start cluster
 */

void puny_cl_cache_init (struct puny_cl_cache *ch, uint size, clust_t cl)
{
	memset (ch, 0, size * sizeof (struct puny_cl_cache));

	ch->clord = size;
	ch->cl = cl;
}

/*
 * return cluster corresponding to a given order number, updating
 * cache if necessary
 */

clust_t puny_cl_cache_get (struct puny_cl_cache *ch, clust_t clord)
{
	int i, ndx_free;
	struct puny_cl_cache *p;
	clust_t clnkord, clnk;

	/* scan all cache */

	ndx_free = 0;				/* find first free entry */
	clnkord = 0;				/* find closest forward link */
	clnk = ch[0].cl;

	if (!clord)				/* start cluster? */
		return clnk;

	p = ch + 1;
	for (i = 1 ; i < ch[0].clord ; i++, p++) {
		if (!p->clord) {		/* free cache entry? */
			if (!ndx_free)
				ndx_free = i;
		} else if (p->clord == clord)	/* cache hit? */
			return p->cl;
		else {				/* cache miss */
			if ((p->clord < clord) && (p->clord > clnkord)) {
				clnkord = p->clord;
				clnk = p->cl;
			}
		}
	}

	/* follow cluster link up-to desired cluster */

	while (clnkord != clord) {
		clnk = puny_fat_peek (clnk);
		if (!clnk)
			return 0;

		clnkord++;
	}

	/* check if cache replacement needed */

	if (ch[0].clord == 1)			/* no cache */
		return clnk;

	if (!ndx_free) {			/* must replace */
	  //ndx_free = (uint)puny_rand () % ch[0].clord;
	  ndx_free = (uint)puny_rand () & 0x0f;
		if (!ndx_free)
			ndx_free++;
	}

	/* update cache */

	ch[ndx_free].clord = clord;
	ch[ndx_free].cl = clnk;

	return clnk;
}

/* ------------------------------------------------------------------------ */

/*
 * convert cluster# to sector#
 */

#if 0
sect_t puny_cl_to_sect (clust_t cl, uint sofs)
{
	if ((cl < 2) || (cl >= puny_cluster_max))
		return PUNY_SECTEOF;

	return (sect_t)(cl - 2) * puny_cluster_size + sofs + puny_cluster2_start;
}

/*
 * convert file sector offset to a physical sector#
 */

sect_t puny_file_sect (struct puny_cl_cache *ch, sect_t sofs)
{
	/*
	 * no need to check for unresolved link (0), it will get caught
	 * in conversion to sector#
	 */

	return puny_cl_to_sect (puny_cl_cache_get (ch, sofs / puny_cluster_size),
				sofs % puny_cluster_size);
}
#else

sect_t puny_file_sect (struct puny_cl_cache *ch, sect_t sofs){

  uint cl;
  ulong res;
  cl = puny_cl_cache_get (ch, sofs >> log_cl_size);
  if ((cl < 2) || (cl >= puny_cluster_max))
    return PUNY_SECTEOF;

  res = (cl - 2) << log_cl_size;
  res += (sofs & mask) + puny_cluster2_start;
  return (sect_t)res;

}

#endif

/* ------------------------------------------------------------------------ */

#endif //FULL_FAT

/*
 * mount FAT12/16 partition (1..4)
 *
 * use part=0 for floppy-like images (no partition table)
 */

int puny_mount (sect_t size, uint part)
{
	byte *p;
	sect_t part_start, part_size;		/* partition geometry */
	sect_t total;				/* as reported in boot sector */

	puny_sect_min = 0;			/* whole medium allowed */
	puny_sect_max = size;
	puny_sect_invalidate ();

	/* read partition table */

	if (!part--) {				/* no MBR */
		part_start = 0;
		part_size = size;
	} else if (part < 4) {			/* 0 .. 3 */
		if (puny_sect_read (0))		/* partition table */
			return 1;

		if (rtns_get_u16le (puny_buf + 0x1FE) != 0xAA55)
			return 1;

		p = puny_buf + 0x1BE + (part << 4);

		part_start = rtns_get_u32le (p + 0x08);
		if (part_start >= size)
			return 1;
		part_size = rtns_get_u32le (p + 0x0C);
		if ((part_start + part_size) > size)
			part_size = size - part_start;
	} else
		return 1;


	/* set to partition start */
	image_start_sector =  part_start;


	/* read boot sector */

	if (puny_sect_read (part_start))	/* boot sector */
		return 1;

	total = rtns_get_u16le (puny_buf + 0x13);
	if (!total)				/* DOS 4.0+ BPB */
		total = rtns_get_u32le (puny_buf + 0x20);
	if (total > part_size)
		total = part_size;

#define puny_fat_copies (puny_buf[0x10])
	puny_fat_start = rtns_get_u16le (puny_buf + 0x0E) + part_start;
#define puny_fat_size (rtns_get_u16le (puny_buf + 0x16))

	puny_root_start = puny_fat_size * puny_fat_copies + puny_fat_start;
	puny_root_size = (rtns_get_u16le (puny_buf + 0x11) + 15) >> 4;

	puny_cluster_size = puny_buf[0x0D];
	
	
#if 0
	switch(puny_cluster_size){
	case 1:
	  mask = 0;
	  log_cl_size = 0;
	  break;
	case 2:
	  mask = 1;
	  log_cl_size = 1;
	  break;
	case 4:
	  mask = 3;
	  log_cl_size = 2;
	  break;
	default:
	  return 1;
	}
#else

       /* calculate shift and mask, also catch invalid values */

        for (log_cl_size = 0 ; log_cl_size < 8 ; log_cl_size++) {
                if (puny_cluster_size == (1 << log_cl_size))
                        goto log_done;
        }
        return 1;                               /* not power of 2 */

log_done:
        mask = puny_cluster_size - 1; 
#endif

	puny_cluster2_start = puny_root_start + puny_root_size;
	puny_cluster_max =((part_start + total - puny_cluster2_start) >> log_cl_size)
	  + 2;

	puny_fat_type = (puny_cluster_max >= 0x0FF0) ? 1 : 0;

	puny_sect_min = part_start;
	puny_sect_max = part_start + part_size;

#undef puny_fat_copies
#undef puny_fat_size

	return 0;
}

