/* Optimized SD Library for Teensy 3.X
 * Copyright (c) 2015, Paul Stoffregen, paul@pjrc.com
 *
 * Development of this SD library was funded by PJRC.COM, LLC by sales of
 * Teensy and Audio Adaptor boards.  Please support PJRC's efforts to develop
 * open source software by purchasing genuine Teensy or other PJRC products.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice, development funding notice, and this permission
 * notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#if defined(__arm__)
#include "SD_t3.h"
#ifdef USE_TEENSY3_OPTIMIZED_CODE

#define cache_t  SDCache::cache_t
#define sector_t SDClass::sector_t

cache_t *SDCache::cache_list = NULL;
cache_t SDCache::cache[SD_CACHE_SIZE];

#define CACHE_FLAG_HAS_DATA  1
#define CACHE_FLAG_IS_DIRTY  2
#define CACHE_FLAG_IS_FAT    4

//#define PRINT_SECTORS

#ifdef PRINT_SECTORS
static void print_sector(const void *data)
{
	const uint8_t *p = (const uint8_t *)data;
	for (int i=0; i < 512; i++) {
		Serial.printf(" %02X", *p++);
		if ((i & 31) == 31) Serial.println();
	}
}
#endif

void SDCache::print_cache(void)
{
#if 0
	const cache_t *end=cache+SD_CACHE_SIZE;
	for (cache_t *c = cache; c < end; c++) {
		Serial.printf("      cache index %u, lba= %u, ucount=%u, flags=%u\n",
			c - cache, c->lba, c->usagecount, c->flags);
	}
	Serial.print("      cache order:");
	for (cache_t *c = cache_list; c; c = c->next) {
		Serial.printf(" %u ->", c - cache);
	}
	Serial.println();
#endif
}

// Read a sector into the cache.  If the sector is already cached,
// of course no actual read occurs.  This is the primary function
// used to access the SD card.
//
sector_t * SDCache::read(uint32_t lba, bool is_fat)
{
	sector_t *ret = NULL;
	//uint32_t slot=0, ucount=0;

	// the entire read operation, including all cache manipulation,
	// needs to be protected with exclusive access to the hardware.
	//Serial.printf("cache read: lba = %d\n", lba);
	SPI.beginTransaction(SD_SPI_SPEED);
	// does the cache already have the sector?
	cache_t *c = get(lba);
	if (c) {
		if (c->flags & CACHE_FLAG_HAS_DATA) {
			 //Serial.printf("   cache hit,  lba=%u\n", lba);
			ret = &c->data;
		} else {
			if (SDClass::sd_read(lba, &c->data)) {
				c->flags = CACHE_FLAG_HAS_DATA;
				if (is_fat) c->flags |= CACHE_FLAG_IS_FAT;
				ret = &c->data;
				 //Serial.printf("   cache miss, lba=%u\n", lba);
			} else {
				 //Serial.printf("   cache miss: read error, lba=%u\n", lba);
			}
		}
	} else {
		//Serial.printf("   cache full & all in use\n", lba);
	}
	SPI.endTransaction();
	//print_cache();
	return ret;
}

// Read a whole 512 byte sector directly to memory.  If the sector is
// already cached, of course no actual read occurs and data is copied
// from the cache.  When the sector is not cached, it's transferred
// directly from SD card to memory, bypassing the cache.
//
bool SDCache::read(uint32_t lba, void *buffer)
{
	bool ret = true;

	SPI.beginTransaction(SD_SPI_SPEED);
	cache_t *c = get(lba, false);
	if (!c || !(c->flags & CACHE_FLAG_HAS_DATA)) {
		ret = SDClass::sd_read(lba, buffer);
	}
	SPI.endTransaction();
	if (c) {
		if ((c->flags & CACHE_FLAG_HAS_DATA)) {
			memcpy(buffer, &c->data, 512);
			release();
			return true;
		}
		release();
	}
	return ret;
}


// locate a sector in the cache.
cache_t * SDCache::get(uint32_t lba, bool allocate)
{
	cache_t *c, *p=NULL, *last=NULL, *plast=NULL;

	// TODO: move initialization to a function called when the SD card is initialized
	if (cache_list == NULL) init();
	// have we already acquired a cache entry?
	if (item) {
		// if it's the desired block, use it
		if (item->lba == lba) return item;
		// if not, release our hold on it
		release();
	}
	__disable_irq();
	c = cache_list;
	do {
		if (c->lba == lba) {
			if (p) {
				p->next = c->next;
				c->next = cache_list;
				cache_list = c;
			}
			c->usagecount++;
			__enable_irq();
			item = c;
			return item;
		}
		if (c->usagecount == 0) {
			plast = p;
			last = c;
		}
		p = c;
		c = c->next;
	} while (c);
	if (allocate && last) {
		if (plast) {
			plast->next = last->next;
			last->next = cache_list;
			cache_list = last;
		}
		last->usagecount = 1;
		// TODO: flush if dirty
		last->lba = lba;
		last->flags = 0;
		item = last;
	}
	__enable_irq();
	return item;
}


void SDCache::init(void)
{
	cache_t *c = cache;
	cache_t *end = c + SD_CACHE_SIZE;
	//Serial.println("cache init");
	__disable_irq();
	do {
		c->lba = 0xFFFFFFFF;
		c->usagecount = 0;
		c->flags = 0;
		c->next = c + 1;
		c = c + 1;
	} while (c < end);
	c--;
	c->next = NULL;
	cache_list = cache;
	__enable_irq();
}


void SDCache::dirty(void)
{
	__disable_irq();
	item->flags |= CACHE_FLAG_IS_DIRTY;
	__enable_irq();
}

void SDCache::release(void)
{
	//Serial.printf("cache release\n");
	if (item) {
		__disable_irq();
		item->usagecount--;
		//uint32_t ucount = item->usagecount;
		//uint32_t lba = item->lba;
		//Serial.printf("release %d, %d, slot %u\n", item->lba, item->usagecount, item-cache);
		__enable_irq();
		item = NULL;
	}
}







#endif
#endif
