#include <malloc.h>
#include <string.h>
#include "common.h"
#include "filelist.h"

#define BG_COLOR 0xACACAC
#define SELECT_COLOR 0x5A95CF
#define TEXT_COLOR 0x000000
#define TEXT_SIZE 16
#define ITEM_PAD 5

FileList::FileList() : Widget()
{
	// Ensure nothing allocated
	fList = NULL;
	fListCount = 0;
	
	// Sequential
	_random = false;
	
	// Ensure animation starts out normal
	animateCount = 0;
	
	// Ensure nothing selected
	scrollPos = 0;
	selectPos = -1;
	highlightPos = -1;
	isDragging = 0;
	
	// Ensure proper null values in all class pointers
	highlightImage = NULL;
	selectImage = NULL;
	bgImageSolid = NULL;
	bgImagePicture = NULL;
	
	highlightSprite = NULL;
	selectSprite = NULL;
	bgSprite = NULL;
	
	scrollUpImage = NULL;
	scrollDownImage = NULL;
	scrollUp = NULL;
	scrollDown = NULL;
	
	// Ensure if no icons load that we don't pad for them
	iconWidth = 0;
	
	// Fuck memset
	for(int i = 0; i < TYPE_LASTSPRITE; i++) { icons[i] = NULL; }
}

static void destroyList(FileListType *fList, int fListCount)
{
	if(!fList)
	{
		// Nothing to destroy
		return;
	}
	
	// Delete all sprites
	for(int i = 0; i < fListCount; i++)
	{
		if(fList[i].image) { delete fList[i].image; }
		if(fList[i].sprite) { delete fList[i].sprite; }
		if(fList[i].icon) { delete fList[i].icon; }
	}
	
	// Delete item
	free(fList);
}

FileList::~FileList()
{
	// Destroy list
	destroyList(fList, fListCount);

	// Destroy images
	if(bgImagePicture) { delete bgImagePicture; }
	if(bgImageSolid) { delete bgImageSolid; }
	if(bgSprite) { delete bgSprite; }
	
	if(highlightImage) { delete highlightImage; }
	if(selectImage) { delete selectImage; }
	if(highlightSprite) { delete highlightSprite; }
	if(selectSprite) { delete selectSprite; }
	
	if(scrollUpImage) { delete scrollUpImage; }
	if(scrollDownImage) { delete scrollDownImage; }
	if(scrollUp) { delete scrollUp; }
	if(scrollDown) { delete scrollDown; }
	
	// Destroy one by one
	for(int i = 0; i < TYPE_LASTSPRITE; i++) 
	{ 
		if(icons[i]) { delete icons[i]; }
		
		icons[i] = NULL;
	}
}

static int sortCallback(const void *a, const void *b)
{
	FileListType *fileA = (FileListType *)a;
	FileListType *fileB = (FileListType *)b;
	
	if(fileA->fType != fileB->fType)
	{
		// File type has priority over file name
		return fileB->fType - fileA->fType;
	}
	
	// Lowercase for proper sort order
	static char lFa[MAXPATHLEN];
	static char lFb[MAXPATHLEN];
	
	memcpy(lFa, fileA->fileName, MAXPATHLEN);
	memcpy(lFb, fileB->fileName, MAXPATHLEN);
	
	strlwr(lFa);
	strlwr(lFb);
	
	// Sort by filename
	return strcmp(lFa, lFb);
}

void FileList::populate(const char *url)
{
	// Clear old run
	destroyList(fList, fListCount);	
	
	// Clear old history
	history.clear();
	
	// Ensure settings back to defaults
	fList = NULL;
	fListCount = 0;
	
	// Ensure nothing selected
	scrollPos = 0;
	selectPos = -1;
	
	// Remember current directory
	strcpy(currentDir, url);
	
	// Temporary data for directory list
	char fileName[MAXPATHLEN];
	FileType fileType;
	
	if(url[strlen(url) - 1] == '/')
	{
		// Open directory
		File dir;
		dir.open(url, "d");
		
		// Loop through and add all
		while(dir.findNextFile(fileName, fileType))
		{
			if(strcmp(fileName, ".") == 0) { continue; }
			
			if(fListCount > 0)
			{
				// Make more room
				fListCount++;
				fList = (FileListType *)realloc(fList, sizeof(FileListType) * fListCount);
			}
			else
			{
				// Make initial room
				fList = (FileListType *)malloc(sizeof(FileListType) * 1);
				fListCount = 1;
			}
			
			// Copy file details
			strcpy(fList[fListCount - 1].fileName, fileName);
			fList[fListCount - 1].fType = fileType;
			
			// Default to 'already played' state for randomizer
			fList[fListCount - 1].played = true;
			
			if(fileType == FILE_DIRECTORY)
			{
				if(strcmp(fileName, "..") == 0)
				{
					// Back icon
					fList[fListCount - 1].whichIcon = TYPE_BACK;
				}
				else
				{
					// Generic folder icon
					fList[fListCount - 1].whichIcon = TYPE_FOLDER;
				}
			}
			else
			{
				// Figure out if it's supported or not
				if(sound.playsFormat(fileName))
				{
					// Yes, set to sound icon
					fList[fListCount - 1].whichIcon = TYPE_SOUND;
					
					// Set not played yet state
					fList[fListCount - 1].played = false;
				}
				else
				{
					// No, set to unknown icon
					fList[fListCount - 1].whichIcon = TYPE_UNKNOWN;
				}
			}
			
			// Ensure null
			fList[fListCount - 1].sprite = NULL;
			fList[fListCount - 1].image = NULL;
			fList[fListCount - 1].icon = NULL;
			
			// Ensure no sound lag
			sound.update();
		}
		
		// No leaked handles please
		dir.close();
	}
	else
	{
		// Open playlist
	}
	
	if(fListCount >= 2)
	{
		// Sort data
		qsort(fList, fListCount, sizeof(FileListType), sortCallback);
	}
	
	// Figure out next round number for width
	int tWidth = _rect.width - iconWidth;
	while(tWidth % 4) { tWidth--; }
	
	// Figure out next round number for height
	int tHeight = (((textSize + 10) >> 2) << 2) + 4;
	
	// Create the blit surface
	u8 *blitbuf = (u8 *)memalign(32, tWidth * tHeight * 4);
	
	// Tie blit surface to text renderer
	text.setBuffer(blitbuf, tWidth, tHeight);
	
	// Grab number of items
	int numItems = _rect.height / (textSize + ITEM_PAD);
	
	// Render all visible text strings
	for(int i = 0; i < ((numItems > fListCount) ? fListCount : numItems); i++)
	{	
		// Create data types
		fList[i].sprite = new Sprite;
		fList[i].image = new DrawableImage;
		
		// Create the image to blit to
		fList[i].image->createImage(tWidth, tHeight);
		
		// Ensure no artifacts
		memset(blitbuf, 0, tWidth * tHeight * 4);
		
		// Ensure no sound lag
		sound.update();
		
		// Render text
		text.renderSimple(fList[i].fileName);
		
		// Copy to surface
		fList[i].image->copyBuffer(blitbuf);
		
		// Tie to sprite
		fList[i].sprite->SetImage(fList[i].image->getImage());
		
		// Tie entry to an icon
		if(icons[fList[i].whichIcon])
		{
			fList[i].icon = new Sprite;
			fList[i].icon->SetImage(icons[fList[i].whichIcon]);
		}
		
		// Ensure no sound lag
		sound.update();
	}
	
	// Free memory
	free(blitbuf);
}

void FileList::resize(int width, int height)
{
	// Ensure width/height is a multiple of 4
	while(width % 4) { width++; };
	while(height % 4) { height++; };
	
	// Resize widget
	_rect.width = width;
	_rect.height = height;
	
	// Reskin app now that we know the right size
	reskin();
}

void FileList::draw()
{
	// Grab location including parent
	Rect dLoc = getDimensions();
	
	if(!fList) 
	{
		// Simple layer manager
		LayerManager manager(1);
		
		// Add bg
		if(bgSprite) 
		{ 
			bgSprite->SetPosition(dLoc.x, dLoc.y);
			manager.Append(bgSprite); 
		}
		
		// Draw scene
		manager.Draw(settingsManager.getOffsetX(), settingsManager.getOffsetY());
		
		// Don't draw the rest
		return;
	}
	
	// Arrange sprites to blit to screen
	int numItems = dLoc.height / (textSize + ITEM_PAD);
	
	// Keep track of animation counter
	animateCount++;
	
	if(animateCount >= 60) { animateCount = 0; }
	
	// Create a window
	LayerManager manager((numItems * 2) + 5);
	
	// animation move amount
	int moveAmount = 0;
	
	switch(animateCount / (60 / 8))
	{
		case 0:
		case 4:
			moveAmount = 0;
			break;
		case 1:
		case 3:
			moveAmount = -1;
			break;
		case 2:
			moveAmount = -2;
			break;
		case 5:
		case 7:
			moveAmount = 1;
			break;
		case 6:
			moveAmount = 2;
			break;
	}
	
	// Display up or down arrows if needed
	if(scrollPos > 0 && scrollUpImage)
	{
		// Display up arrow sprite
		scrollUp->SetPosition(dLoc.x + (dLoc.width / 2) - (scrollUpImage->GetWidth() / 2), dLoc.y - (scrollUpImage->GetHeight() / 2) + moveAmount);
		
		// Add to draw scene
		manager.Append(scrollUp);
	}
	
	// Display up or down arrows if needed
	if(scrollPos < fListCount - numItems && scrollDownImage)
	{
		// Display down arrow sprite
		scrollDown->SetPosition(dLoc.x + (dLoc.width / 2) - (scrollDownImage->GetWidth() / 2), dLoc.y  + dLoc.height - (scrollDownImage->GetHeight() / 2) - moveAmount);
		
		// Add to draw scene
		manager.Append(scrollDown);
	}
	
	// For calculating icon position
	int tHeight = (((textSize + 10) >> 2) << 2);
	
	// Pop all needed sprites on
	for(int i = scrollPos; i < scrollPos + numItems; i++)
	{
		if(i >= fListCount)
		{
			// hit the end of the list
			break;
		}
		
		if(fList[i].icon)
		{
			// Get actual height
			int yPos = (tHeight - fList[i].icon->GetHeight()) / 2;
			
			// Move position of icon
			fList[i].icon->SetPosition(dLoc.x + 4, (i - scrollPos) * (textSize + ITEM_PAD) + dLoc.y + yPos + 2);
			
			// Add to draw scene
			manager.Append(fList[i].icon);
		}
		
		if(fList[i].sprite)
		{		
			// Move position
			fList[i].sprite->SetPosition(dLoc.x + iconWidth, (i - scrollPos) * (textSize + ITEM_PAD) + dLoc.y);
			
			// Add to draw scene
			manager.Append(fList[i].sprite);
		}
		
		if(highlightSprite && i == highlightPos)
		{ 
			highlightSprite->SetPosition(dLoc.x, (i - scrollPos) * (textSize + ITEM_PAD) + 2 + dLoc.y);
			manager.Append(highlightSprite); 
		}
		
		if(selectSprite && i == selectPos)
		{ 
			selectSprite->SetPosition(dLoc.x, (i - scrollPos) * (textSize + ITEM_PAD) + 2 + dLoc.y);
			manager.Append(selectSprite); 
		}
	}
	
	// Add bg
	if(bgSprite) 
	{ 
		bgSprite->SetPosition(dLoc.x, dLoc.y);
		manager.Append(bgSprite); 
	}
	
	// Draw scene
	manager.Draw(settingsManager.getOffsetX(), settingsManager.getOffsetY());
	
	// Figure out if we need to skip to the next song
	if(sound.finishedNaturally())
	{
		// Yes!
		sound.resetNaturalFinish();
		
		// Go to next song
		playFileRelative(1);
	}
}

void FileList::reskin()
{
	// Set up text renderer
	{
		char tFont[256];
		windowManager.getString("FileList.Font.Face", tFont, "DejaVu");	
		text.setFont(tFont);
		
		windowManager.getLong("FileList.Font.Size", &textSize, TEXT_SIZE);
		text.setSize(textSize);
		
		uint32_t textColor;
		windowManager.getLongHex("FileList.Font.Color", &textColor, TEXT_COLOR);
		text.setColor(Color(textColor));
	}
	
	// Set up background
	{
		if(bgImagePicture) { delete bgImagePicture; }
		if(bgImageSolid) { delete bgImageSolid; }
		if(bgSprite) { delete bgSprite; }
		
		// Sort out background image
		char tPath[256];
		char tGraphic[256];
		
		windowManager.getThemePath(tPath);
		windowManager.getString("FileList.BGGraphic", tGraphic);		
		strcat(tPath, tGraphic);
		
		bgImagePicture = new Image();
		bgSprite = new Sprite();
		
		// Try to load image first
		if(bgImagePicture->LoadImage(tPath) != IMG_LOAD_ERROR_NONE)
		{
			// Kill object that didn't load
			delete bgImagePicture;
			bgImagePicture = NULL;
			
			// Try loading normal
			bgImageSolid = new DrawableImage();			
			bgImageSolid->createImage(_rect.width, _rect.height);
			
			// Create blit buffer			
			u8 *blitbuf = (u8 *)memalign(32, _rect.width * _rect.height * 4);
			
			// Grab color and transparency
			uint32_t tColor;
			int transparency;			
			
			windowManager.getLongHex("FileList.BGColor", &tColor, BG_COLOR);
			windowManager.getLong("FileList.Transparency", &transparency, 255);
			
			// Create color
			tColor = (tColor << 8) | (transparency & 0xFF);
			
			// Copy in
			for(int i = 0; i < _rect.width * _rect.height * 4; i += 4)
			{
				// Copy quad
				memcpy(&blitbuf[i], &tColor, 4);
			}
			
			// Set sprite
			bgImageSolid->copyBuffer(blitbuf);
			bgSprite->SetImage(bgImageSolid->getImage());
			
			// Free memory
			free(blitbuf);
		}
		else
		{
			// Success
			bgSprite->SetImage(bgImagePicture);
		}
	}
	
	// Set up select and hover
	{
		// Ensure no old data
		if(highlightImage) { delete highlightImage; }
		if(selectImage) { delete selectImage; }
		if(highlightSprite) { delete highlightSprite; }
		if(selectSprite) { delete selectSprite; }
		
		uint32_t tColor;		
		windowManager.getLongHex("FileList.SelectColor", &tColor, SELECT_COLOR);
		
		// Create and set a sprite for this
		int tHeight = (((textSize + 10) >> 2) << 2);
		
		// Create the blit surface
		u8 *blitbuf = (u8 *)memalign(32, _rect.width * tHeight * 4);
		
		// Create color
		tColor = (tColor << 8) | 0xFF;
		
		// Copy in
		for(int i = 0; i < _rect.width * tHeight * 4; i += 4)
		{
			// Copy quad
			memcpy(&blitbuf[i], &tColor, 4);
		}
		
		// Set first line blank
		memset(blitbuf, 0, _rect.width * 8);
		
		// New sprite
		selectImage = new DrawableImage();
		selectSprite = new Sprite();
		
		// Set sprite
		selectImage->createImage(_rect.width, tHeight);
		selectImage->copyBuffer(blitbuf);
		selectSprite->SetImage(selectImage->getImage());
		
		// Create color
		tColor &= 0xFFFFFF7F;
		
		// Copy in
		for(int i = 0; i < _rect.width * tHeight * 4; i += 4)
		{
			// Copy quad
			memcpy(&blitbuf[i], &tColor, 4);
		}
		
		// Set first line blank
		memset(blitbuf, 0, _rect.width * 8);
		
		// New sprite
		highlightImage = new DrawableImage();
		highlightSprite = new Sprite();
		
		// Set sprite
		highlightImage->createImage(_rect.width, tHeight);
		highlightImage->copyBuffer(blitbuf);
		highlightSprite->SetImage(highlightImage->getImage());
		
		// Free memory
		free(blitbuf);
	}
	
	// Set up scroll up and scroll down indicators
	{
		// Kill old names if they exist
		if(scrollUpImage) { delete scrollUpImage; }
		if(scrollDownImage) { delete scrollDownImage; }
		if(scrollUp) { delete scrollUp; }
		if(scrollDown) { delete scrollDown; }
		
		scrollUpImage = new Image();
		scrollDownImage = new Image();
		
		scrollUp = new Sprite();
		scrollDown = new Sprite();
		
		// Load Up arrow
		char imagePath[MAXPATHLEN];
		char imageName[MAXPATHLEN];
		
		windowManager.getThemePath(imagePath);
		windowManager.getString("FileList.UpArrow.FileName", imageName);
		strcat(imagePath, imageName);
		
		if(scrollUpImage->LoadImage(imagePath) != IMG_LOAD_ERROR_NONE)
		{
			// Try loading from default
			if(scrollUpImage->LoadImage(DATAPATH "images/up.png") != IMG_LOAD_ERROR_NONE)
			{
				delete scrollUpImage;
				scrollUpImage = NULL;
			}
		}
		
		// Set up arrow sprite
		scrollUp->SetImage(scrollUpImage);
		
		// Load down arrow
		windowManager.getThemePath(imagePath);
		windowManager.getString("FileList.DownArrow.FileName", imageName);
		strcat(imagePath, imageName);
		
		if(scrollDownImage->LoadImage(imagePath) != IMG_LOAD_ERROR_NONE)
		{
			// Try loading from default
			if(scrollDownImage->LoadImage(DATAPATH "images/down.png") != IMG_LOAD_ERROR_NONE)
			{
				delete scrollDownImage;
				scrollDownImage = NULL;
			}
		}
		
		// Set down arrow
		scrollDown->SetImage(scrollDownImage);
	}
	
	// Set up the file icons
	{
		// Kill old sprites if needed
		for(int i = 0; i < TYPE_LASTSPRITE; i++) 
		{ 
			if(icons[i]) { delete icons[i]; }
			
			icons[i] = new Image;
		}
		
		char imagePath[MAXPATHLEN];
		char imageName[MAXPATHLEN];
		
		// Load back
		windowManager.getThemePath(imagePath);
		windowManager.getString("FileList.Icons.Back.FileName", imageName);
		strcat(imagePath, imageName);
		
		if(icons[TYPE_BACK]->LoadImage(imagePath) != IMG_LOAD_ERROR_NONE)
		{
			delete icons[TYPE_BACK];
			icons[TYPE_BACK] = NULL;
		}
		
		// Load folder
		windowManager.getThemePath(imagePath);
		windowManager.getString("FileList.Icons.Folder.FileName", imageName);
		strcat(imagePath, imageName);
		
		if(icons[TYPE_FOLDER]->LoadImage(imagePath) != IMG_LOAD_ERROR_NONE)
		{
			delete icons[TYPE_FOLDER];
			icons[TYPE_FOLDER] = NULL;
		}
		
		// Load sound
		windowManager.getThemePath(imagePath);
		windowManager.getString("FileList.Icons.Audio.FileName", imageName);
		strcat(imagePath, imageName);
		
		if(icons[TYPE_SOUND]->LoadImage(imagePath) != IMG_LOAD_ERROR_NONE)
		{
			delete icons[TYPE_SOUND];
			icons[TYPE_SOUND] = NULL;
		}
		
		// Load unknown
		windowManager.getThemePath(imagePath);
		windowManager.getString("FileList.Icons.Unknown.FileName", imageName);
		strcat(imagePath, imageName);
		
		if(icons[TYPE_UNKNOWN]->LoadImage(imagePath) != IMG_LOAD_ERROR_NONE)
		{
			delete icons[TYPE_UNKNOWN];
			icons[TYPE_UNKNOWN] = NULL;
		}
		
		// Figure out maximum width
		iconWidth = 0;
		
		for(int i = 0; i < TYPE_LASTSPRITE; i++)
		{
			if(icons[i])
			{
				// Only check if it exists
				if(icons[i]->GetWidth() > iconWidth) 
				{ 
					iconWidth = icons[i]->GetWidth(); 
				}
			}
		}
		
		if(iconWidth)
		{
			// Pad for good looks
			iconWidth += 8;
		}
	}
}

void FileList::resetRandom()
{
	// Loop through and set everything to unplayed
	for(int i = 0; i < fListCount; i++)
	{
		if(fList[i].whichIcon == TYPE_SOUND)
		{
			// Ensure all sounds are marked as unplayed
			fList[i].played = false;
		}
	}
}

int FileList::getRandomCount()
{
	// Remember how many items were still left to play
	int count = 0;
	
	for(int i = 0; i < fListCount; i++)
	{
		if(!fList[i].played)
		{
			// Here's one
			count++;
		}
	}
	
	return count;
}

int FileList::getRandomItem(int x)
{
	// Limit
	if(x < 0) { x = 0; }
	if(x > getRandomCount()) { x = 0; }
	
	// Find element
	int count = 0;
	
	for(int i = 0; i < fListCount; i++)
	{
		if(!fList[i].played)
		{
			if(count == x)
			{
				// This is the one we want
				return i;
			}
			
			// Here's one to skip
			count++;
		}
	}
	
	return -1;
}

void FileList::playFileRelative(int offset)
{
	// Remember select position
	int tSelectPos = selectPos;
	int realSelectPos = -1;
	
	// Only play relative to selected file
	if(tSelectPos != -1)
	{
		if(_random)
		{
			// +1 is next random, -1 is history
			if(offset > 0)
			{
				// Go forward in history if exists, else create a new point
				if(history.hasNext())
				{
					// Grab previously saved song
					realSelectPos = history.getNext();
				}
				else
				{
					// Ensure current item doesn't get played again
					if(tSelectPos) { fList[tSelectPos].played = true; }
					
					// Figure out how many songs we have left that haven't been played
					if(getRandomCount() == 0)
					{
						// Ensure we have a clean slate if we picked all of the songs
						resetRandom();
					}
					
					// Ensure current item doesn't get played again
					if(tSelectPos) { fList[tSelectPos].played = true; }
					
					// Figure out if we have anything left to play
					int count = getRandomCount();
					
					if(count > 0)
					{
						// Figure out which of the unplayed songs to play
						int whichSong = (int)(rand() % count);
						
						// Map to a real song
						realSelectPos = getRandomItem(whichSong);
						
						if(realSelectPos >= 0)
						{
							// Add to history
							history.add(realSelectPos);
						}
					}
				}
			}
			else
			{
				// Go back in history
				if(history.hasPrevious())
				{
					// Grab previously saved song
					realSelectPos = history.getPrevious();
				}
			}
		}
		else
		{
			// Sequential play
			while(tSelectPos >= 0 && tSelectPos < fListCount)
			{
				// Offset
				tSelectPos += offset;
				
				// Figure out if it's playable
				if(sound.playsFormat(fList[tSelectPos].fileName))
				{
					// Set sound and exit
					realSelectPos = tSelectPos;
					break;
				}
			}
		}
		
		if(realSelectPos >= 0)
		{
			// File Action
			char fileName[MAXPATHLEN];
			
			// Start with old
			strcpy(fileName, currentDir);
			
			if(fileName[strlen(fileName) - 1] != '/')
			{
				// Add slash
				strcat(fileName, "/");
			}
			
			// Add current directory
			strcat(fileName, fList[realSelectPos].fileName);
			
			// File Action
			if(sound.loadSound(fileName))
			{
				// Only play if load succeeded
				sound.playSound();
				
				// Remember where we highlighted
				selectPos = realSelectPos;
				
				// Arrange sprites to blit to screen
				int numItems = _rect.height / (textSize + ITEM_PAD);
				
				// Remember old position
				int oldPos = scrollPos;
				
				// Move to keep up with list
				if(!isDragging && selectPos >= 0)
				{
					if(selectPos < scrollPos) { scrollPos = selectPos; }
					if(selectPos > scrollPos + (numItems - 1)) { scrollPos = selectPos - (numItems - 1); }
					
					if(scrollPos < 0) { scrollPos = 0; }
				}
				
				// Scroll update
				if(oldPos != scrollPos)
				{
					fixScroll(oldPos);
				}
				
				// Only exit this loop if we played successfully
				return;
			}
		}
	}
}

void FileList::fixScroll(int oldPos)
{	
	// Get number of visible items
	int numItems = _rect.height / (textSize + ITEM_PAD);
	
	// Figure out next round number for width
	int tWidth = _rect.width - iconWidth;
	while(tWidth % 4) { tWidth--; }
	
	// Figure out next round number for height
	int tHeight = (((textSize + 10) >> 2) << 2) + 4;
	
	if(abs(oldPos - scrollPos) == 1) // Quick way
	{
		
		int deleteItem;
		int addItem;
		
		// Figure out which item to delete
		if(oldPos < scrollPos)
		{
			deleteItem = oldPos;
			addItem = oldPos + numItems;
		}
		else
		{
			deleteItem = scrollPos + numItems;
			addItem = scrollPos;
		}
		
		// Move item item
		fList[addItem].image = fList[deleteItem].image;
		fList[addItem].sprite = fList[deleteItem].sprite;
		fList[addItem].icon = fList[deleteItem].icon;
		
		// Delete old pointer
		fList[deleteItem].image = NULL;
		fList[deleteItem].sprite = NULL;
		fList[deleteItem].icon = NULL;
		
		// Re-blit filename
		u8 *blitbuf = (u8 *)memalign(32, tWidth * tHeight * 4);
		
		// Ensure no artifacts
		memset(blitbuf, 0, tWidth * tHeight * 4);
		// Tie blit surface to text renderer
		text.setBuffer(blitbuf, tWidth, tHeight);
		
		// Render text
		text.renderSimple(fList[addItem].fileName);
		
		// Copy to surface
		fList[addItem].image->copyBuffer(blitbuf);
		
		// Tie entry to an icon
		if(fList[addItem].icon)
		{
			if(icons[fList[addItem].whichIcon]) 
			{ 
				fList[addItem].icon->SetImage(icons[fList[addItem].whichIcon]); 
			}
		}
		
		// Free blit surface
		free(blitbuf);
	}
	else // Long way
	{
		// Delete all sprites
		for(int i = 0; i < fListCount; i++)
		{
			if(fList[i].image) { delete fList[i].image; }
			if(fList[i].sprite) { delete fList[i].sprite; }
			if(fList[i].icon) { delete fList[i].icon; }
			
			fList[i].image = NULL;
			fList[i].sprite = NULL;
			fList[i].icon = NULL;
		}
		
		// Create the blit surface
		u8 *blitbuf = (u8 *)memalign(32, tWidth * tHeight * 4);
		
		// Tie blit surface to text renderer
		text.setBuffer(blitbuf, tWidth, tHeight);
		
		// Regenerate all sprites
		for(int i = scrollPos; i < scrollPos + numItems; i++)
		{
			if(i >= fListCount)
			{
				// hit the end of the list
				break;
			}
			
			// Create data types
			fList[i].sprite = new Sprite;
			fList[i].image = new DrawableImage;
			
			// Create the image to blit to
			fList[i].image->createImage(tWidth, tHeight);
			
			// Ensure no artifacts
			memset(blitbuf, 0, tWidth * tHeight * 4);
			
			// Ensure no sound lag
			sound.update();
			
			// Render text
			text.renderSimple(fList[i].fileName);
			
			// Copy to surface
			fList[i].image->copyBuffer(blitbuf);
			
			// Tie to sprite
			fList[i].sprite->SetImage(fList[i].image->getImage());
			
			// Tie entry to an icon
			if(icons[fList[i].whichIcon])
			{
				fList[i].icon = new Sprite;
				fList[i].icon->SetImage(icons[fList[i].whichIcon]);
			}
			
			// Ensure no sound lag
			sound.update();
		}
		
		// Free old buffer
		free(blitbuf);
	}
}

void FileList::cursorDown(int x, int y, Action a)
{
	// Only act on primary click
	if(a == BUTTON_PRIMARY)
	{	
		// Calculate which item we selected
		int whichItem = (y / (textSize + ITEM_PAD)) + scrollPos;
		
		// Prevent selecting past end
		if(whichItem >= fListCount) { return; }
		
		// Prevent selecting past the visible range
		int numItems = _rect.height / (textSize + ITEM_PAD);
		if(whichItem >= scrollPos + numItems) { return; }
		
		// Select
		selectPos = whichItem;
		
		// Perform Action
		if(strcmp(fList[whichItem].fileName, "..") == 0)
		{	
			// Must strip back one level
			char newDir[MAXPATHLEN];
			
			// Start with old
			strcpy(newDir, currentDir);
			
			// Special cases for root of any device
			if(strcmp(newDir, "sd://") == 0)
			{
				populate("/");
				return;
			}
			if(strcmp(newDir, "usb://") == 0)
			{
				populate("/");
				return;
			}
			
			// Strip trailing slash
			if(newDir[strlen(newDir) - 1] == '/')
			{
				newDir[strlen(newDir) - 1] = 0;
			}
			
			// Loop until we hit a slash
			for(int i = strlen(newDir); i >= 0; i--)
			{
				if(newDir[i] == '/')
				{
					// Found previous
					newDir[i+1] = 0;
					
					// Populate
					populate(newDir);
					
					// Exit for
					return;
				}
			}
		}
		else if(fList[whichItem].fType == FILE_DIRECTORY || fList[whichItem].fType == FILE_SOURCE)
		{
			// Populate new directory
			char newDir[MAXPATHLEN];
			
			if(strcmp(currentDir, "/") == 0)
			{
				// Start with nothing
				newDir[0] = 0;
			}
			else
			{
				// Start with old
				strcpy(newDir, currentDir);
				
				if(newDir[strlen(newDir) - 1] != '/')
				{
					// Add slash
					strcat(newDir, "/");
				}
			}
			
			// Add current directory
			strcat(newDir, fList[whichItem].fileName);
			
			if(newDir[strlen(newDir) - 1] != '/')
			{
				// Add slash
				strcat(newDir, "/");
			}
			
			// Populate
			populate(newDir);
		}
		else
		{
			// File Action
			char fileName[MAXPATHLEN];
			
			// Start with old
			strcpy(fileName, currentDir);
			
			if(fileName[strlen(fileName) - 1] != '/')
			{
				// Add slash
				strcat(fileName, "/");
			}
			
			// Add current directory
			strcat(fileName, fList[whichItem].fileName);
			
			// File Action
			if(sound.loadSound(fileName))
			{
				// Only play if load succeeded
				sound.playSound();
				
				if(_random)
				{
					// Add to history
					history.add(whichItem);
				}
			}
		}
	}
	else if(a == BUTTON_SECONDARY)
	{
		// Drag
		isDragging = 1;
		_dragging = true;
		
		// Remember position to drag from
		yDragPos = y;
	}
	else if(a == BUTTON_MINUS)
	{
		// Ensure not on the root
		if(strcmp(currentDir, "/"))
		{
			// Must strip back one level
			char newDir[MAXPATHLEN];
			
			// Start with old
			strcpy(newDir, currentDir);
			
			// Special cases for root of any device
			if(strcmp(newDir, "sd://") == 0)
			{
				populate("/");
				return;
			}
			if(strcmp(newDir, "usb://") == 0)
			{
				populate("/");
				return;
			}
			
			// Strip trailing slash
			if(newDir[strlen(newDir) - 1] == '/')
			{
				newDir[strlen(newDir) - 1] = 0;
			}
			
			// Loop until we hit a slash
			for(int i = strlen(newDir); i >= 0; i--)
			{
				if(newDir[i] == '/')
				{
					// Found previous
					newDir[i+1] = 0;
					
					// Populate
					populate(newDir);
					
					// Exit for
					return;
				}
			}
		}
	}
}

void FileList::getDir(char *url)
{
	// Return current directory
	strcpy(url, currentDir);
}

void FileList::cursorMove(int x, int y)
{
	// Figure out if we need to move
	if(isDragging)
	{
		// Drag only if we hit a threshold
		int threshold = (abs(y - yDragPos) / 10);
		if(threshold > 16) { threshold = 16; }
		
		if(threshold)
		{
			// Convert to a timing
			threshold = 16 - threshold;
			
			if(isDragging >= threshold)
			{
				// Remember old position
				int oldPos = scrollPos;
				
				// Compute number of items
				int numItems = _rect.height / (textSize + ITEM_PAD);
				
				// Move
				if((y - yDragPos) > 0)
				{
					// Down
					scrollPos++;
					
					// Ensure not too far
					if(scrollPos + numItems >= fListCount) { scrollPos = fListCount - numItems; }
				}
				else
				{
					// Up
					scrollPos--;
				}
				
				// Ensure bounds checked
				if(scrollPos < 0) { scrollPos = 0; }
				
				// Figure out if we have to render an item
				if(oldPos != scrollPos)
				{
					fixScroll(oldPos);
				}
				
				// Reset count
				isDragging = 1;
			}
			else
			{
				// Remember wait
				isDragging++;
			}
		}
	}
	
	// Calculate which item we hovered over
	int whichItem = (y / (textSize + ITEM_PAD)) + scrollPos;
	
	// Prevent selecting past end
	if(whichItem >= fListCount) 
	{ 
		// Ensure nothing highlighted
		highlightPos = -1;
		return; 
	}
	
	// Highlight
	highlightPos = whichItem;
}

void FileList::cursorUp(int x, int y, Action a)
{
	if(a == BUTTON_SECONDARY)
	{
		// No more dragging
		isDragging = 0;
		_dragging = false;
	}
}

void FileList::cursorOut()
{
	// Ensure nothing highlighted
	highlightPos = -1;
	
	// Ensure nothing dragging
	isDragging = 0;
	_dragging = false;
}

void FileList::setRandomMode(bool mode)
{
	// Remember choice
	_random = mode;
	
	// Reset play count if random
	if(_random)
	{
		resetRandom();
	}
}

bool FileList::isRandomMode()
{
	return _random;
}
