#include <3ds.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "metadata.h"
#include "file.h"
#include "all.h"
#include "gui.h"

/* Internal helper functions */
static int extractId3v2Metadata(FILE* fp, struct metadata_t* metadata);
static int extractId3v1Metadata(FILE* fp, struct metadata_t* metadata);
static int extractVorbisComment(FILE* fp, struct metadata_t* metadata);
static int extractFlacMetadata(FILE* fp, struct metadata_t* metadata);
static void trimWhitespace(char* str);
static void parseFilename(const char* file, struct metadata_t* metadata);

/**
 * Extract metadata from an audio file
 */
int extractMetadata(const char* file, struct metadata_t* metadata)
{
	FILE* fp;
	enum file_types fileType;
	
	if(!file || !metadata)
		return -1;
		
	/* Clear metadata structure */
	clearMetadata(metadata);
	
	fileType = getFileType(file);
	fp = fopen(file, "rb");
	if(!fp)
		return -1;
	
	switch(fileType)
	{
		case FILE_TYPE_MP3:
			/* Try ID3v2 first, then ID3v1 */
			if(extractId3v2Metadata(fp, metadata) != 0)
				extractId3v1Metadata(fp, metadata);
			break;
			
		case FILE_TYPE_VORBIS:
		case FILE_TYPE_OPUS:
			extractVorbisComment(fp, metadata);
			break;
			
		case FILE_TYPE_FLAC:
			extractFlacMetadata(fp, metadata);
			break;
			
		case FILE_TYPE_WAV:
		case FILE_TYPE_SID:
		default:
			/* No metadata support for these formats yet */
			break;
	}
	
	fclose(fp);
	
	/* If no metadata was found, try to parse filename */
	if(!metadata->title[0] && !metadata->artist[0] && !metadata->album[0])
	{
		parseFilename(file, metadata);
	}
	
	return 0;
}

/**
 * Clear metadata structure and free any allocated memory
 */
void clearMetadata(struct metadata_t* metadata)
{
	if(!metadata)
		return;
		
	memset(metadata->title, 0, METADATA_TITLE_MAX);
	memset(metadata->artist, 0, METADATA_ARTIST_MAX);
	memset(metadata->album, 0, METADATA_ALBUM_MAX);
	

}

/**
 * Display metadata on the top screen
 * (Now uses GUI rendering - this is a wrapper for compatibility)
 */
void displayMetadata(struct metadata_t* metadata, const char* filename)
{
	guiDisplayMetadata(metadata, filename);
}

/**
 * Extract ID3v2 metadata from MP3 file
 */
static int extractId3v2Metadata(FILE* fp, struct metadata_t* metadata)
{
	char header[10];
	uint32_t tagSize;
	uint8_t major, minor, flags;
	
	if(!fp || !metadata)
		return -1;
	
	fseek(fp, 0, SEEK_SET);
	if(fread(header, 1, 10, fp) != 10)
		return -1;
	
	/* Check ID3v2 header "ID3" */
	if(memcmp(header, "ID3", 3) != 0)
		return -1;
	
	major = header[3];
	minor = header[4];
	flags = header[5];
	
	/* Calculate tag size (synchsafe integer) */
	tagSize = ((header[6] & 0x7F) << 21) |
			  ((header[7] & 0x7F) << 14) |
			  ((header[8] & 0x7F) << 7) |
			  (header[9] & 0x7F);
	
	/* Skip extended header if present */
	if(flags & 0x40)
	{
		uint32_t extSize;
		if(fread(&extSize, 4, 1, fp) != 1)
			return -1;
		/* Convert from big-endian */
		extSize = __builtin_bswap32(extSize);
		fseek(fp, extSize - 4, SEEK_CUR);
	}
	
	long tagStart = ftell(fp);
	long tagEnd = tagStart + tagSize;
	
	/* Parse frames */
	while(ftell(fp) < tagEnd - 10)
	{
		char frameId[5] = {0};
		uint32_t frameSize;
		uint16_t frameFlags;
		
		if(fread(frameId, 1, 4, fp) != 4)
			break;
		
		/* Check for padding */
		if(frameId[0] == 0)
			break;
		
		if(fread(&frameSize, 4, 1, fp) != 1)
			break;
		frameSize = __builtin_bswap32(frameSize);
		
		if(fread(&frameFlags, 2, 1, fp) != 1)
			break;
		
		/* Handle text frames */
		if(strncmp(frameId, "TIT2", 4) == 0 || /* Title */
		   strncmp(frameId, "TPE1", 4) == 0 || /* Artist */
		   strncmp(frameId, "TALB", 4) == 0)   /* Album */
		{
			char* frameData = malloc(frameSize + 1);
			if(!frameData)
			{
				fseek(fp, frameSize, SEEK_CUR);
				continue;
			}
			
		if(fread(frameData, 1, frameSize, fp) == frameSize)
		{
			frameData[frameSize] = 0;
			
			/* Text encoding byte:
			 * 0x00 = ISO-8859-1
			 * 0x01 = UTF-16 with BOM
			 * 0x02 = UTF-16BE without BOM
			 * 0x03 = UTF-8
			 */
			uint8_t encoding = frameData[0];
			char* text = frameData + 1;
			int textLen = frameSize - 1;
			
			/* Skip UTF-16 BOM if present */
			if(encoding == 0x01 && textLen >= 2)
			{
				if((uint8_t)text[0] == 0xFF && (uint8_t)text[1] == 0xFE)
				{
					text += 2;
					textLen -= 2;
				}
				else if((uint8_t)text[0] == 0xFE && (uint8_t)text[1] == 0xFF)
				{
					text += 2;
					textLen -= 2;
				}
			}
			
			/* Copy to appropriate field */
			char* dest = NULL;
			size_t maxLen = 0;
			
			if(strncmp(frameId, "TIT2", 4) == 0)
			{
				dest = metadata->title;
				maxLen = METADATA_TITLE_MAX - 1;
			}
			else if(strncmp(frameId, "TPE1", 4) == 0)
			{
				dest = metadata->artist;
				maxLen = METADATA_ARTIST_MAX - 1;
			}
			else if(strncmp(frameId, "TALB", 4) == 0)
			{
				dest = metadata->album;
				maxLen = METADATA_ALBUM_MAX - 1;
			}
			
			if(dest)
			{
				/* Handle different encodings */
				if(encoding == 0x01 || encoding == 0x02) /* UTF-16 */
				{
					/* Convert UTF-16 to ASCII (simplified - just take every other byte) */
					int outPos = 0;
					for(int i = 0; i < textLen && outPos < (int)maxLen; i += 2)
					{
						if(text[i] >= 0x20 && text[i] < 0x7F)
							dest[outPos++] = text[i];
						else if(text[i] == 0 && text[i+1] == 0)
							break;
					}
					dest[outPos] = 0;
				}
				else /* ISO-8859-1 or UTF-8 */
				{
					/* Copy as-is, filtering out non-ASCII characters */
					int outPos = 0;
					for(int i = 0; i < textLen && outPos < (int)maxLen; i++)
					{
						if((uint8_t)text[i] >= 0x20 && (uint8_t)text[i] < 0x7F)
							dest[outPos++] = text[i];
						else if(text[i] == 0)
							break;
					}
					dest[outPos] = 0;
				}
				trimWhitespace(dest);
			}
		}			free(frameData);
		}
		else if(strncmp(frameId, "APIC", 4) == 0) /* Attached Picture */
		{
			/* Skip album art data */
			fseek(fp, frameSize, SEEK_CUR);
		}
		else
		{
			/* Skip unknown frame */
			fseek(fp, frameSize, SEEK_CUR);
		}
	}
	
	return 0;
}

/**
 * Extract ID3v1 metadata from MP3 file
 */
static int extractId3v1Metadata(FILE* fp, struct metadata_t* metadata)
{
	char tag[128];
	
	if(!fp || !metadata)
		return -1;
	
	/* Seek to last 128 bytes */
	fseek(fp, -128, SEEK_END);
	
	if(fread(tag, 1, 128, fp) != 128)
		return -1;
	
	/* Check for "TAG" signature */
	if(memcmp(tag, "TAG", 3) != 0)
		return -1;
	
	/* Extract fields */
	if(!metadata->title[0])
	{
		strncpy(metadata->title, tag + 3, 30);
		metadata->title[30] = 0;
		trimWhitespace(metadata->title);
	}
	
	if(!metadata->artist[0])
	{
		strncpy(metadata->artist, tag + 33, 30);
		metadata->artist[30] = 0;
		trimWhitespace(metadata->artist);
	}
	
	if(!metadata->album[0])
	{
		strncpy(metadata->album, tag + 63, 30);
		metadata->album[30] = 0;
		trimWhitespace(metadata->album);
	}
	
	return 0;
}

/**
 * Extract Vorbis comment metadata (for OGG Vorbis/Opus)
 */
static int extractVorbisComment(FILE* fp, struct metadata_t* metadata)
{
	/* This is a simplified implementation for Vorbis comments.
	 * A full implementation would require parsing the OGG container structure.
	 * For now, we'll try to find common Vorbis comment fields by searching for them.
	 */
	char buffer[4096];
	size_t bytesRead;
	
	if(!fp || !metadata)
		return -1;
	
	/* Reset to beginning */
	fseek(fp, 0, SEEK_SET);
	
	/* Read and search for Vorbis comment patterns */
	while((bytesRead = fread(buffer, 1, sizeof(buffer) - 1, fp)) > 0)
	{
		buffer[bytesRead] = '\0';
		
		/* Look for TITLE= */
		char* title = strstr(buffer, "TITLE=");
		if(title && !metadata->title[0])
		{
			title += 6; /* Skip "TITLE=" */
			char* end = strchr(title, '\1'); /* Vorbis comments end with \1 or \0 */
			if(!end) end = strchr(title, '\0');
			if(end)
			{
				size_t len = end - title;
				if(len >= METADATA_TITLE_MAX) len = METADATA_TITLE_MAX - 1;
				strncpy(metadata->title, title, len);
				metadata->title[len] = '\0';
				trimWhitespace(metadata->title);
			}
		}
		
		/* Look for ARTIST= or ALBUMARTIST= */
		char* artist = strstr(buffer, "ARTIST=");
		if(!artist)
			artist = strstr(buffer, "ALBUMARTIST=");
		
		if(artist && !metadata->artist[0])
		{
			if(strncmp(artist, "ALBUMARTIST=", 12) == 0)
				artist += 12; /* Skip "ALBUMARTIST=" */
			else
				artist += 7; /* Skip "ARTIST=" */
			
			char* end = strchr(artist, '\1');
			if(!end) end = strchr(artist, '\0');
			if(end)
			{
				size_t len = end - artist;
				if(len >= METADATA_ARTIST_MAX) len = METADATA_ARTIST_MAX - 1;
				strncpy(metadata->artist, artist, len);
				metadata->artist[len] = '\0';
				trimWhitespace(metadata->artist);
			}
		}
		
		/* Look for ALBUM= */
		char* album = strstr(buffer, "ALBUM=");
		if(album && !metadata->album[0])
		{
			album += 6; /* Skip "ALBUM=" */
			char* end = strchr(album, '\1');
			if(!end) end = strchr(album, '\0');
			if(end)
			{
				size_t len = end - album;
				if(len >= METADATA_ALBUM_MAX) len = METADATA_ALBUM_MAX - 1;
				strncpy(metadata->album, album, len);
				metadata->album[len] = '\0';
				trimWhitespace(metadata->album);
			}
		}
	}
	
	return 0;
}

/**
 * Extract FLAC metadata
 */
static int extractFlacMetadata(FILE* fp, struct metadata_t* metadata)
{
	char header[4];
	
	if(!fp || !metadata)
		return -1;
	
	/* Check for FLAC signature */
	fseek(fp, 0, SEEK_SET);
	if(fread(header, 1, 4, fp) != 4 || memcmp(header, "fLaC", 4) != 0)
		return -1;
	
	/* FLAC metadata blocks follow the signature */
	while(1)
	{
		uint8_t blockHeader[4];
		uint32_t blockSize;
		bool isLast;
		uint8_t blockType;
		
		if(fread(blockHeader, 1, 4, fp) != 4)
			break;
		
		isLast = (blockHeader[0] & 0x80) != 0;
		blockType = blockHeader[0] & 0x7F;
		blockSize = (blockHeader[1] << 16) | (blockHeader[2] << 8) | blockHeader[3];
		
		if(blockType == 6) /* PICTURE */
		{
			/* Skip picture block */
			fseek(fp, blockSize, SEEK_CUR);
		}
		else if(blockType == 4) /* VORBIS_COMMENT */
		{
			/* FLAC uses Vorbis comments for metadata */
			/* This is a simplified implementation */
			char* commentData = malloc(blockSize);
			if(commentData)
			{
				if(fread(commentData, 1, blockSize, fp) == blockSize)
				{
					/* Skip vendor string length (4 bytes) and vendor string */
					if(blockSize > 4)
					{
						uint32_t vendorLength = commentData[0] | (commentData[1] << 8) | 
											   (commentData[2] << 16) | (commentData[3] << 24);
						
						if(vendorLength + 8 < blockSize) /* 4 bytes vendor len + vendor + 4 bytes comment count */
						{
							uint32_t commentCount = commentData[4 + vendorLength] | 
												   (commentData[5 + vendorLength] << 8) |
												   (commentData[6 + vendorLength] << 16) |
												   (commentData[7 + vendorLength] << 24);
							
							uint32_t offset = 8 + vendorLength;
							for(uint32_t i = 0; i < commentCount && offset < blockSize; i++)
							{
								if(offset + 4 > blockSize) break;
								
								uint32_t commentLength = commentData[offset] | (commentData[offset + 1] << 8) |
														 (commentData[offset + 2] << 16) | (commentData[offset + 3] << 24);
								offset += 4;
								
								if(offset + commentLength > blockSize) break;
								
								/* Parse comment */
								char* comment = commentData + offset;
								if(commentLength > 0 && commentLength < 1000) /* Reasonable size check */
								{
									char tempComment[1001];
									strncpy(tempComment, comment, commentLength);
									tempComment[commentLength] = '\0';
									
									if(strncmp(tempComment, "TITLE=", 6) == 0 && !metadata->title[0])
									{
										strncpy(metadata->title, tempComment + 6, METADATA_TITLE_MAX - 1);
										metadata->title[METADATA_TITLE_MAX - 1] = '\0';
										trimWhitespace(metadata->title);
									}
									else if(strncmp(tempComment, "ARTIST=", 7) == 0 && !metadata->artist[0])
									{
										strncpy(metadata->artist, tempComment + 7, METADATA_ARTIST_MAX - 1);
										metadata->artist[METADATA_ARTIST_MAX - 1] = '\0';
										trimWhitespace(metadata->artist);
									}
									else if(strncmp(tempComment, "ALBUMARTIST=", 12) == 0 && !metadata->artist[0])
									{
										strncpy(metadata->artist, tempComment + 12, METADATA_ARTIST_MAX - 1);
										metadata->artist[METADATA_ARTIST_MAX - 1] = '\0';
										trimWhitespace(metadata->artist);
									}
									else if(strncmp(tempComment, "ALBUM=", 6) == 0 && !metadata->album[0])
									{
										strncpy(metadata->album, tempComment + 6, METADATA_ALBUM_MAX - 1);
										metadata->album[METADATA_ALBUM_MAX - 1] = '\0';
										trimWhitespace(metadata->album);
									}
								}
								
								offset += commentLength;
							}
						}
					}
				}
				free(commentData);
			}
		}
		else
		{
			/* Skip other block types */
			fseek(fp, blockSize, SEEK_CUR);
		}
		
		if(isLast)
			break;
	}
	
	return 0;
}

/**
 * Trim leading and trailing whitespace from string
 */
static void trimWhitespace(char* str)
{
	char* start = str;
	char* end;
	
	if(!str || *str == 0)
		return;
	
	/* Trim leading space */
	while(isspace((unsigned char)*start))
		start++;
	
	if(*start == 0)  /* All spaces? */
	{
		*str = '\0';
		return;
	}
	
	/* Trim trailing space */
	end = start + strlen(start) - 1;
	while(end > start && isspace((unsigned char)*end))
		end--;
	
	/* Write new null terminator */
	end[1] = '\0';
	
	/* Move trimmed string to beginning if needed */
	if(start != str)
	{
		memmove(str, start, strlen(start) + 1);
	}
}

/**
 * Parse filename for metadata when tags are not available
 * Handles formats like "Artist - Album - Track - Title.ext"
 */
static void parseFilename(const char* file, struct metadata_t* metadata)
{
	if(!file || !metadata)
		return;
	
	/* Extract just the filename without path */
	const char* basename = strrchr(file, '/');
	if(!basename)
		basename = file;
	else
		basename++; /* Skip the '/' */
	
	/* Remove file extension */
	char filename[256];
	strncpy(filename, basename, sizeof(filename) - 1);
	filename[sizeof(filename) - 1] = '\0';
	char* dot = strrchr(filename, '.');
	if(dot) *dot = '\0';
	
	/* Look for pattern: "Artist - Album - Track - Title" */
	char* parts[4] = {NULL, NULL, NULL, NULL};
	int partCount = 0;
	
	char* token = strtok(filename, " - ");
	while(token && partCount < 4)
	{
		parts[partCount] = token;
		partCount++;
		token = strtok(NULL, " - ");
	}
	
	/* Assign parts based on count */
	if(partCount >= 4)
	{
		/* Artist - Album - Track - Title */
		strncpy(metadata->artist, parts[0], METADATA_ARTIST_MAX - 1);
		strncpy(metadata->album, parts[1], METADATA_ALBUM_MAX - 1);
		strncpy(metadata->title, parts[3], METADATA_TITLE_MAX - 1);
	}
	else if(partCount == 3)
	{
		/* Artist - Album - Title */
		strncpy(metadata->artist, parts[0], METADATA_ARTIST_MAX - 1);
		strncpy(metadata->album, parts[1], METADATA_ALBUM_MAX - 1);
		strncpy(metadata->title, parts[2], METADATA_TITLE_MAX - 1);
	}
	else if(partCount == 2)
	{
		/* Artist - Title */
		strncpy(metadata->artist, parts[0], METADATA_ARTIST_MAX - 1);
		strncpy(metadata->title, parts[1], METADATA_TITLE_MAX - 1);
	}
	else if(partCount == 1)
	{
		/* Just Title */
		strncpy(metadata->title, parts[0], METADATA_TITLE_MAX - 1);
	}
	
	/* Ensure null termination */
	metadata->artist[METADATA_ARTIST_MAX - 1] = '\0';
	metadata->album[METADATA_ALBUM_MAX - 1] = '\0';
	metadata->title[METADATA_TITLE_MAX - 1] = '\0';
	
	/* Trim whitespace */
	trimWhitespace(metadata->artist);
	trimWhitespace(metadata->album);
	trimWhitespace(metadata->title);
}