/* 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 sector_t SDClass::sector_t
#define fatdir_t SDClass::fatdir_t

File SDClass::open(const char *path, uint8_t mode)
{
	File ret, parent = rootDir;

	//Serial.print("SD.open: ");
	//Serial.println(path);
	while (1) {
		while (*path == '/') path++;
		if (*path == 0) {
			// end of pathname is "/", use last subdir
			ret = parent;
			break;
		}
		File next;
		bool found = parent.find(path, &next);
		const char *p = path;
		do p++; while (*p != '/' && *p != 0);
		if (found) {
			//Serial.println("  open: found");
			if (*p == 0) {
				// found the file
				ret = next;
				break;
			}
			// found next subdir
			parent = next;
			path = p;
		} else {
			//Serial.print("  open: not found ");
			//Serial.println(path);
			if (*p == '/') break; // subdir doesn't exist
			// file doesn't exist
			if (mode == FILE_READ) break;
			// TODO: for writing, create the file
			break;
		}
	}
	return ret;
}

bool SDClass::exists(const char *path)
{
	File f = open(path);
	return (bool)f;
}

File File::openNextFile(uint8_t mode)
{
	File f;
	uint32_t lba, sector_offset, sector_index, sector_count;

	//Serial.print("File::openNextFile, offset=");
	//Serial.println(offset);
	if (mode > FILE_READ) return f; // TODO: writing not yet supported
	if (type == FILE_DIR_ROOT16) {
		//Serial.print("  fat16 dir");
		sector_offset = offset >> 9;
		lba = start_cluster + sector_offset;
		sector_count = length  >> 9;
	} else if (type == FILE_DIR) {
		//Serial.print("  subdir");
		sector_offset = cluster_offset(offset) >> 9;
		lba = custer_to_sector(current_cluster) + sector_offset;
		sector_count = (1 << SDClass::sector2cluster);
	} else {
		return f;
	}
	//Serial.print("  sector_offset=");
	//Serial.println(sector_offset);
	//Serial.print("  lba=");
	//Serial.println(lba);
	//Serial.print("  sector_count=");
	//Serial.println(sector_count);
	sector_index = (offset >> 5) & 15;
	//Serial.print("  sector_index=");
	//Serial.println(sector_index);
	while (1) {
		SDCache sector;
		sector_t *s = sector.read(lba);
		if (!s) return f;
		fatdir_t *dirent = s->dir + sector_index;
		while (sector_index < 16) {
			//Serial.print("    sector_index=");
			//Serial.println(sector_index);
			if (dirent->attrib != ATTR_LONG_NAME) {
				uint8_t b0 = dirent->name[0];
				//Serial.print("    b0=");
				//Serial.println(b0);
				if (b0 == 0) return f;
				if (b0 != 0xE5 && memcmp(dirent->name, ".          ", 11) != 0
				    && memcmp(dirent->name, "..         ", 11) != 0) {
					f.init(dirent);
					sector.release();
					offset += 32;
					if (cluster_offset(offset) == 0) {
						 next_cluster(); // TODO: handle error
					}
					return f;
				}
			}
			offset += 32;
			dirent++;
			sector_index++;
		}
		sector.release();
		sector_index = 0;
		if (++sector_offset >= sector_count) {
			if (type == FILE_DIR_ROOT16) {
				break;
			} else {
				if (!next_cluster()) break;
				lba = custer_to_sector(current_cluster);
				sector_offset = 0;
			}
		}
	}
	return f;
}


bool File::find(const char *filename, File *found)
{
	bool find_unused = true;
	char name83[11];
	uint32_t lba, sector_count;

	//Serial.println("File::open");

	const char *f = filename;
	if (*f == 0) return false;
	char *p = name83;
	while (p < name83 + 11) {
		char c = *f++;
		if (c == 0 || c == '/') {
			while (p < name83 + 11) *p++ = ' ';
			break;
		}
		if (c == '.') {
			while (p < name83 + 8) *p++ = ' ';
			continue;
		}
		if (c > 126) continue;
		if (c >= 'a' && c <= 'z') c -= 32;
		*p++ = c;
	}
	//Serial.print("name83 = ");
	//for (uint32_t i=0; i < 11; i++) {
		//Serial.printf(" %02X", name83[i]);
	//}
	//Serial.println();

	if (type == FILE_DIR_ROOT16) {
		lba = start_cluster;
		sector_count = length >> 9;
	} else if (type == FILE_DIR) {
		current_cluster = start_cluster;
		lba = custer_to_sector(start_cluster);
		sector_count = (1 << SDClass::sector2cluster);
	} else {
		return false; // not a directory
	}
	while (1) {
		for (uint32_t i=0; i < sector_count; i++) {
			SDCache sector;
			sector_t *s = sector.read(lba);
			if (!s) return false;
			fatdir_t *dirent = s->dir;
			for (uint32_t j=0; j < 16; j++) {
				if (dirent->attrib == ATTR_LONG_NAME) {
					// TODO: how to match long names?
				}
				if (memcmp(dirent->name, name83, 11) == 0) {
					//Serial.printf("found 8.3, j=%d\n", j);
					found->init(dirent);
					return true;
				}
				uint8_t b0 = dirent->name[0];
				if (find_unused && (b0 == 0 || b0 == 0xE5)) {
					found->dirent_lba = lba;
					found->dirent_index = j;
					find_unused = false;
				}
				if (b0 == 0) return false;
				offset += 32;
				dirent++;
			}
			lba++;
		}
		if (type == FILE_DIR_ROOT16) break;
		if (!next_cluster()) break;
		lba = custer_to_sector(current_cluster);
		//Serial.printf("  next lba = %d\n", lba);
	}
	return false;
}

void File::init(fatdir_t *dirent)
{
	offset = 0;
	length = dirent->size;
	start_cluster = (dirent->cluster_high << 16) | dirent->cluster_low;
	current_cluster = start_cluster;
	type = (dirent->attrib & ATTR_DIRECTORY) ? FILE_DIR : FILE_READ;
	char *p = namestr;
	const char *s = dirent->name;
	for (int i=0; i < 8; i++) {
		if (*s == ' ') break;
		*p++ = *s++;
	}
	s = dirent->name + 8;
	if (*s != ' ') {
		*p++ = '.';
		*p++ = *s++;
		if (*s != ' ') *p++ = *s++;
		if (*s != ' ') *p++ = *s++;
	}
	*p = 0;
	//Serial.printf("File::init, cluster = %d, length = %d\n", start_cluster, length);
}

#endif
#endif
