/*
 *  ppui/ListBoxFileBrowser.cpp
 *
 *  Copyright 2009 Peter Barth
 *
 *  This file is part of Milkytracker.
 *
 *  Milkytracker is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Milkytracker is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Milkytracker.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

/*
 *  ListBoxFileBrowser.cpp
 *  MilkyTracker
 *
 *  Created by Peter Barth on 19.10.06.
 *
 */
 
#include "ListBoxFileBrowser.h"
#include "Screen.h"
#include "PPPathFactory.h"

PPListBoxFileBrowser::PPListBoxFileBrowser(pp_int32 id, PPScreen* parentScreen, EventListenerInterface* eventListener, 
										   const PPPoint& location, const PPSize& size) :
	PPListBox(id, parentScreen, eventListener, location, size, true, false, true, true),
	filePrefix("<FILE> "), fileSuffix(""),
	directoryPrefix("<DIR>  "), directorySuffix(""),
	sortAscending(true),
	cycleFilenames(true),
	confirmed(false),
	sortType(SortByName)
{
	setRightButtonConfirm(true);
	currentPath = PPPathFactory::createPath();
}

PPListBoxFileBrowser::~PPListBoxFileBrowser()
{
	delete currentPath;
}

pp_int32 PPListBoxFileBrowser::dispatchEvent(PPEvent* event)
{
	if (event->getID() == eKeyChar && cycleFilenames)
	{	
		pp_uint16 keyCode = *((pp_uint16*)event->getDataPtr());		
			
		if (keyCode < 255 && cycleFilenames)
			cycle((char)keyCode);

	}
	if (event->getID() == eRMouseUp || event->getID() == eLMouseDoubleClick ){
		confirmed = true;
	}

	if (event->getID() == eKeyUp ){

		pp_uint16 keyCode = *((pp_uint16*)event->getDataPtr());		
		pp_int32 selectionIndex = PPListBox::getSelectedIndex();

		if( keyCode == VK_RETURN ){ // enter confirms + selects next item
			PPEvent e(eConfirmed, &selectionIndex, sizeof(selectionIndex));
			eventListener->handleEvent(reinterpret_cast<PPObject*>(this), &e);
			parentScreen->paintControl(this);
			confirmed = true;
		}

		if( keyCode == VK_UP || keyCode == VK_DOWN ){ // exit keyjazz 
			confirmed = false;											
		}
	}

	return PPListBox::dispatchEvent(event);
}

void PPListBoxFileBrowser::clearExtensions()
{
	items.clear();
}

// must contain pairs of extensions / description
// terminated by TWO NULL pointers
void PPListBoxFileBrowser::addExtensions(const char* const extensions[])
{
	for (pp_uint32 i = 0; extensions[i] != NULL; i+=2)
		addExtension(extensions[i], extensions[i+1]);
}

void PPListBoxFileBrowser::addExtension(const PPString& ext, const PPString& desc)
{
	Descriptor* d = new Descriptor(ext, desc);
	items.add(d);
}


PPSystemString PPListBoxFileBrowser::getCurrentPathAsString() const
{ 
	return currentPath->getCurrent(); 
}

PPString PPListBoxFileBrowser::getCurrentPathAsASCIIString() const
{
	char* nameASCIIZ = currentPath->getCurrent().toASCIIZ();	
	PPString result(nameASCIIZ);
	delete[] nameASCIIZ;
	return result;
}

void PPListBoxFileBrowser::refreshFiles()
{
	iterateFilesInFolder();
}

const PPPathEntry* PPListBoxFileBrowser::getPathEntry(pp_int32 index) const
{
	if(index >= 0 && index < pathEntries.size())
        return pathEntries.get(index);
    return NULL;
}

bool PPListBoxFileBrowser::canGotoHome() const
{
	return currentPath->canGotoHome();
}

void PPListBoxFileBrowser::gotoHome()
{
	PPSystemString before = currentPath->getCurrent();
	currentPath->gotoHome();
	PPSystemString after = currentPath->getCurrent();
	
	if (after.compareTo(before) != 0)
	{
		history.Push(before); 
		history.Push(after); 
		history.Pop(); 
	} 
	
	refreshFiles();
}

bool PPListBoxFileBrowser::canGotoRoot() const
{
	return currentPath->canGotoRoot();
}

void PPListBoxFileBrowser::gotoRoot()
{
	PPSystemString before = currentPath->getCurrent();
	currentPath->gotoRoot();
	PPSystemString after = currentPath->getCurrent();
	
	if (after.compareTo(before) != 0)
	{
		history.Push(before); 
		history.Push(after); 
		history.Pop(); 
	} 

	refreshFiles();
}

bool PPListBoxFileBrowser::canGotoParent() const
{
	return currentPath->canGotoParent();
}

void PPListBoxFileBrowser::gotoParent()
{
	PPSystemString before = currentPath->getCurrent();
	currentPath->gotoParent();
	PPSystemString after = currentPath->getCurrent();

	if (after.compareTo(before) != 0)
	{
		history.Push(before); 
		history.Push(after); 
		history.Pop(); 
	} 
	
	refreshFiles();
}

bool PPListBoxFileBrowser::currentSelectionIsFile()
{
	const PPPathEntry* entry = getPathEntry(PPListBox::getSelectedIndex());
	return entry ? entry->isFile() : false;
}

bool PPListBoxFileBrowser::stepIntoCurrentSelection()
{
	const PPPathEntry* entry = getPathEntry(PPListBox::getSelectedIndex());
	
	return entry ? stepInto(*entry) : false;
}

bool PPListBoxFileBrowser::stepInto(const PPPathEntry& entry)
{
	if (entry.isDirectory())
	{
		PPSystemString before = currentPath->getCurrent();
		// check if we can actually change to this directory
		if (!currentPath->stepInto(entry.getName()))
			return false;
		PPSystemString after = currentPath->getCurrent();
		
		if (after.compareTo(before) != 0)
		{
			history.Push(before); 
			history.Push(after); 
			history.Pop(); 
		} 
		
		refreshFiles();
		return true;
	}
	
	return false;
}

bool PPListBoxFileBrowser::gotoPath(const PPSystemString& path, bool reload/* = true*/)
{
	bool res = currentPath->change(path);
	if (res && reload) 
		refreshFiles(); 
	return res;
}

bool PPListBoxFileBrowser::canPrev() const
{
	return !history.IsEmpty();
}

// undo last changes
void PPListBoxFileBrowser::prev()
{
	if (history.IsEmpty()) return;
	
	gotoPath(*history.Pop());
}

bool PPListBoxFileBrowser::canNext() const
{
	return !history.IsTop();
}

// redo last changes
void PPListBoxFileBrowser::next()
{
	if (history.IsTop()) return;
	
	gotoPath(*history.Advance());
}

void PPListBoxFileBrowser::setFilePrefix(const PPString& prefix)
{
	filePrefix = prefix;
}

void PPListBoxFileBrowser::setFileSuffix(const PPString& suffix)
{
	fileSuffix = suffix;
}

void PPListBoxFileBrowser::setDirectoryPrefix(const PPString& prefix)
{
	directoryPrefix = prefix;
}

void PPListBoxFileBrowser::setDirectorySuffix(const PPString& suffix)
{
	directorySuffix = suffix;
}

void PPListBoxFileBrowser::setDirectorySuffixPathSeperator()
{
	directorySuffix = currentPath->getPathSeparatorAsASCII();
}

void PPListBoxFileBrowser::iterateFilesInFolder()
{
	pathEntries.clear();
	
	const PPPathEntry* entry = currentPath->getFirstEntry();
	while (entry)
	{
		if (!entry->isHidden() && checkExtension(*entry))
		{
			pathEntries.add(entry->clone());			
		}
		entry = currentPath->getNextEntry();
	}
	
	sortFileList();
	
	buildFileList();
}

void PPListBoxFileBrowser::buildFileList()
{
	PPListBox::clear();
	
	for (pp_int32 i = 0; i < pathEntries.size(); i++)
	{
		const PPPathEntry* entry = pathEntries.get(i);
		char* nameASCIIZ = entry->getName().toASCIIZ();
		PPString str(entry->isDirectory() ? directoryPrefix : filePrefix);
		str.append(nameASCIIZ);
		str.append(entry->isDirectory() ? directorySuffix : fileSuffix);
		
		appendFileSize(str, *entry);
		
		PPListBox::addItem(str);
		delete[] nameASCIIZ;
	}
}

void PPListBoxFileBrowser::appendFileSize(PPString& name, const PPPathEntry& entry)
{
	if (entry.isFile())
	{
		pp_int64 size = entry.getSize();

		char buffer[1024];
		
		if (size < 1024)
			sprintf(buffer, " (%db)", (pp_int32)size);
		else if (size < 1024*1024)
		{
			size>>=10;
			sprintf(buffer, " (%dkb)", (pp_int32)size);
		}
		else
		{
			size>>=20;
			sprintf(buffer, " (%dmb)", (pp_int32)size);
		}
	
		name.append(buffer);
	}
}

void PPListBoxFileBrowser::sortFileList()
{
	pp_int32 i;

	PPPathEntry** tempEntries;
	PPPathEntry** drives;
	PPPathEntry** parents;
	
	pp_int32 numEntries = pathEntries.size();
	tempEntries = new PPPathEntry*[numEntries];	
	drives = new PPPathEntry*[numEntries];	
	parents = new PPPathEntry*[numEntries];	
	//for (i = 0; i < numEntries; i++)
	//	tempEntries[i] = pathEntries.get(i)->clone();
	
	pp_int32 numDrives = 0;
	pp_int32 numParents = 0;
	pp_int32 content = 0;
	for (i = 0; i < numEntries; i++)
	{
		if (pathEntries.get(i)->isParent())
		{
			parents[numParents] = pathEntries.get(i)->clone();			
			numParents++;
		}
		else if (pathEntries.get(i)->isDrive())
		{
			drives[numDrives] = pathEntries.get(i)->clone();			
			numDrives++;
		}
		else 
		{
			tempEntries[content] = pathEntries.get(i)->clone();
			content++;
		}
	}
	
	
	PPPathEntry::PathSortRuleInterface* sortRules[NumSortRules];
	
	PPPathEntry::PathSortByFileRule sortByFileRule;
	PPPathEntry::PathSortBySizeRule sortBySizeRule;
	PPPathEntry::PathSortByExtRule sortByExtRule;
	
	sortRules[0] = &sortByFileRule;
	sortRules[1] = &sortBySizeRule;
	sortRules[2] = &sortByExtRule;
	
	if (content)
		PPPathEntry::sort(tempEntries, 0, content-1, *sortRules[sortType], !sortAscending);
	if (numDrives)
		PPPathEntry::sort(drives, 0, numDrives-1, *sortRules[0], false);
	
	pathEntries.clear();
	
	for (i = 0; i < numParents; i++)
	{
		pathEntries.add(parents[i]->clone());
		delete parents[i];
	}

	for (i = 0; i < content; i++)
	{
		pathEntries.add(tempEntries[i]->clone());
		delete tempEntries[i];
	}
	
	for (i = 0; i < numDrives; i++)
	{
		pathEntries.add(drives[i]->clone());
		delete drives[i];
	}
	
	delete[] parents;
	delete[] drives;
	delete[] tempEntries;
}

void PPListBoxFileBrowser::cycle(char chr)
{
	if( confirmed ) return; // don't cycle when instrument/sample was just loaded (allow keyjazz)
	pp_int32 currentIndex = PPListBox::getSelectedIndex();
	
	PPSystemString prefix(chr);
	prefix.toUpper();
	
	pp_uint32 j = currentIndex+1;
	for (pp_int32 i = 0; i < pathEntries.size(); i++, j++)
	{
		PPSystemString str = pathEntries.get(j % pathEntries.size())->getName();
		str.toUpper();
		
		if (str.startsWith(prefix))
		{
			PPListBox::setSelectedIndex(j % pathEntries.size(), false);
			
			pp_int32 selectionIndex = PPListBox::getSelectedIndex();
			PPEvent e(eSelection, &selectionIndex, sizeof(selectionIndex));
			eventListener->handleEvent(reinterpret_cast<PPObject*>(this), &e);
			
			parentScreen->paintControl(this);
			break;
		}
	}
}

bool PPListBoxFileBrowser::checkExtension(const PPPathEntry& entry)
{
	if (items.size() == 0 || entry.isDirectory())
		return true;

	bool res = false;
	
	for (pp_int32 i = 0; i < items.size(); i++)
	{
		PPSystemString sysStr(items.get(i)->extension);
		if (entry.getName().compareToExtension(sysStr))
		{
			res = true;
			break;
		}
	}

	return res;
}
