/*
 * Copyright (C) 2006-2007	Andre Beckedorf
 * 					 		<evilJazz _AT_ katastrophos _DOT_ net>
 * 
 * The few remaining parts in this almost completely rewritten file are
 * Copyright (C) 2005 Atmark <atmarkat _AT_ msn _DOT_ com>
 *                    AGAWA Koji <i _AT_ atty _DOT_ jp>
 *
 * This program 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 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <cstdlib>
#include <ctime>
#include <time.h>

#include <qstring.h>
#include <qtextstream.h>
#include <qheader.h>
#include <qapplication.h>
#include <qdatetime.h>
#ifdef QTOPIA
#include <qpe/config.h>
#include "qt/qlistview.h"
#else
#include <config.h>
#include <qlistview.h>
#endif
#include <qpainter.h>
#include <qimage.h>
#include "mediadatabase.h"

#include "media.h"
#include "playlist.h"
#include "debug.h"
#include "skinmanager.h"
#include "sqlite_source/sqlite3.h"

PlayListParams::PlayListParams()
	:	sortedColumn(0),
		isSortAscending(false),
		isSorted(false),
		filterText(""),
		isFiltered(false),
		directMode(false)
{
}


PlayListItem::PlayListItem(Media *media, unsigned long playlistID, QListView *parent)
	: QListViewItem(parent),
	  media_(media),
	  id_(playlistID)
{
}

PlayListItem::PlayListItem(Media *media, unsigned long playlistID, QListView *parent, QListViewItem *after)
	: QListViewItem(parent, after),
	  media_(media),
	  id_(playlistID)
{
}

/*
QString PlayListItem::text(int column) const
{
	switch (column)
	{
	case 0:
		return "    ";
	case 1:
		return media_->trackNo() + "  ";
	case 2:
		return media_->title();
	case 3:
		return media_->artist();
	case 4:
		return media_->album();
	case 5:
		return media_->playTime();
	default:
		return NULL;
	}
}
*/

QString PlayListItem::key(int column, bool) const
{
	// omit sorting. We'll do it in our DB backend...
	return NULL;
}	

void PlayListItem::paintFocus(QPainter *, const QColorGroup &, const QRect &)
{
	// don't paint focus rect...
}

void PlayListItem::paintCell(QPainter * p, const QColorGroup & cg, int column, int width, int align )
{
	int ownHeight = height();
	PlayList *lv = static_cast<PlayList *>(listView());

	int vy = lv->itemRect(this).y();   // get y inside the viewport
	int x, y;

	lv->viewportToContents(0,vy,x,y);  // get y inside the contents
		
	QColorGroup grp;
											    
	if (ownHeight != 0 && (y / ownHeight) % 2 == 0)
		grp = lv->evenRowColorGroup_;
	else
		grp = lv->oddRowColorGroup_;

	p->fillRect( 0, 0, width, ownHeight, grp.brush(QColorGroup::Base));

	int r = 1;
	int marg = 1;

	if (isSelected() && (column == 0 || lv->allColumnsShowFocus()))
	{
		p->fillRect(r - marg, 0, width - r + marg, ownHeight,
					cg.brush(QColorGroup::Highlight));
		
		p->setPen(cg.highlightedText());
	} 
	else
	{
		p->setPen(cg.text());
	}

	if (column == 0)
	{
		if (this == lv->activeItem_)
		{
			const QPixmap *indicator = lv->activeItemPaused_ ? lv->pauseIndicatorImage_ : lv->playIndicatorImage_;
			if (indicator)
			{
				int t = (ownHeight - indicator->height()) / 2;
				p->drawPixmap(r, t, *indicator);
			}
		}
	}
	else
	{
		QString t;
		switch (column)
		{
		case 0:
			t = "    ";
			break;
		case 1:
			t = media_->trackNo() + "  ";
			break;
		case 2:
			t = media_->title();
			break;
		case 3:
			t = media_->artist();
			break;
		case 4:
			t = media_->album();
			break;
		case 5:
			t = media_->playTime();
			break;
		}
		
		if (!t.isEmpty())
		{
			if (!(align & AlignTop | align & AlignBottom))
				align |= AlignVCenter;
			
			p->drawText(r, 0, width-marg-r, ownHeight, align, t);
		}
	}
}

int PlayListItem::width( const QFontMetrics& fm, const QListView* lv, int c ) const
{
	return lv->columnWidth(c);
}

void PlayListItem::setSelected(bool selected)
{
	QListViewItem::setSelected(selected);
	static_cast<PlayList *>(this->listView())->itemSelectedStateChanged(this, selected);
}

PlayList::PlayList(const QString &mediaDBFilename, QWidget *parent, const char *name)
	: QListView(parent, name, 0),
		updateCount_(0),
		updatingView_(false),
		playOrder_(ORDER_NORMAL),
		playOrderTable(NULL),
		count_(0), currentIndex(0),
		evenRowColorGroup_(colorGroup()),
		oddRowColorGroup_(colorGroup()),
		playIndicatorImage_(NULL),
		pauseIndicatorImage_(NULL),
		activeItem_(NULL),
		activeID_(0),
		activeMediaID_(0),
		activeItemPaused_(false),
	  	playListSource_("playlist"),
		viewSource_("playlist_view"),
		query_(""),
		dbFilterText_(""),
		dbFiltered_(false),
		dbPlaylistItemOrderDirty_(false),
		itemCount_(0),
		stmtInsertMediaIDIntoPlaylist(NULL)
{
	// Media cache
	DPRINTF("Initialize MediaCache class...");
	mediaDatabase_ = new MediaDatabase(mediaDBFilename);
	// make sure we're notified when the database was compacted...
	connect(mediaDatabase_, SIGNAL(databaseCompacted()),
			this, SLOT(mediaDatabaseCompacted()));
			
	connect(mediaDatabase_, SIGNAL(preparingStatements(sqlite3*)),
			this, SLOT(mediaDatabasePreparingStatements(sqlite3*)));

	connect(mediaDatabase_, SIGNAL(finalizingStatements(sqlite3*)),
			this, SLOT(mediaDatabaseFinalizingStatements(sqlite3*)));

	mediaDatabase_->load();
	DPRINTF("Done");

	// set to manual width mode.
	// this boosts performance quite dramatically as the 
	// column width doesn't need to be measured
	addColumn(tr("   "), 20);
	setColumnAlignment(addColumn(tr("No."), 100), AlignRight);
	addColumn(tr("Title"), 100);
	addColumn(tr("Artist"), 100);
	addColumn(tr("Album"), 100);
	setColumnAlignment(addColumn(tr("Length"), 40), AlignRight);
	
	setRootIsDecorated(false);
	setAllColumnsShowFocus(true);
	setWFlags(Qt::WNorthWestGravity | Qt::WRepaintNoErase | Qt::WResizeNoErase);
	
	setInputMode(PlayList::Select);
	
	QListView::setSorting(-1);
	
	header()->setClickEnabled(true, 1);
	header()->setClickEnabled(true, 2);
	header()->setClickEnabled(true, 3);
	header()->setClickEnabled(true, 4);
	header()->setClickEnabled(true, 5);
	
	header()->setResizeEnabled(false, 0);

	connect(header(), SIGNAL(indexChange(int, int, int)),
			this, SLOT(headerIndexChange(int, int, int)));
	
	connect(header(), SIGNAL(clicked(int)),
			this, SLOT(headerClicked(int)));
	
	setFrameStyle(QFrame::NoFrame);
}

PlayList::~PlayList()
{
	QListView::clear();
	delete mediaDatabase_; // desctructor will save database...
}

void PlayList::clear(bool clearDB)
{
	beginUpdate();
	activeItem_ = NULL;
	activeID_ = 0;
	activeMediaID_ = 0;
	selectedItems_.clear();
	QListView::clear();
	mediaDatabase_->clearCache();
	
	if (clearDB)
	{
		QString query = "DELETE FROM " + playListSource_ + ";";
		sqlite3_exec(mediaDatabase_->db_, (const char *)query.utf8(), NULL, NULL, NULL);
	}
	
	dbPlaylistItemOrderDirty_ = true;
	
	endUpdate();
	updateView();	
}

void PlayList::beginUpdate()
{
	DENTERMETHOD("PlayList::beginUpdate()");
	
	if (updateCount_ == 0)
	{
		setUpdatesEnabled(false);
		blockSignals(true);
		mediaDatabase_->beginUpdate();
	}
	++updateCount_;
	
	DEXITMETHOD("PlayList::beginUpdate()");
}

void PlayList::endUpdate()
{
	DENTERMETHOD("PlayList::endUpdate()");

	--updateCount_;
	if (updateCount_ == 0)
	{
		DTIMERINIT(time);
		mediaDatabase_->endUpdate();
		DTIMERPRINT(time, "mediaDatabase_->endUpdate()");
	
		DTIMERSTART(time);
		blockSignals(false);
		setUpdatesEnabled(true);
		triggerUpdate();
		DTIMERPRINT(time, "list view refresh");
	}
	
	DEXITMETHOD("PlayList::endUpdate()");
}

void PlayList::beginChangingUpdate()
{
	DENTERMETHOD("PlayList::beginChangingUpdate()");
	
	beginUpdate();
			
	// switch to unsorted, unfiltered mode...
	savedParams = params;
	params.isSorted = false;
	params.isFiltered = false;
	params.filterText = "";
	
	// direct mode means external filtering will be omitted
	// the listview's content and order is the same as in DB table "playlist" after calling updateView();
	params.directMode = true;
	updateView();
	
	DEXITMETHOD("PlayList::beginChangingUpdate()");
}

void PlayList::endChangingUpdate()
{
	DENTERMETHOD("PlayList::endChangingUpdate()");
	
	params = savedParams;

	endUpdate();
	
	DEXITMETHOD("PlayList::endChangingUpdate()");
}

void PlayList::ensurePlayListValidInDB()
{
	DENTERMETHOD("PlayList::ensurePlayListValidInDB()");
	if (!dbFiltered_ && dbPlaylistItemOrderDirty_)
	{
		DPRINTF("playlist item order is dirty, we have to save the order to the DB...");
		transferPlayListToDB();
	}
	DEXITMETHOD("PlayList::ensurePlayListValidInDB()");
}

void PlayList::transferPlayListToDB()
{
	DENTERMETHOD("PlayList::transferPlayListToDB()");
	
	DPRINTF("dbFiltered_ : %s", dbFiltered_ ? "true" : "false");
	DPRINTF("dbPlaylistItemOrderDirty_ : %s", dbPlaylistItemOrderDirty_ ? "true" : "false");
	if (dbFiltered_) 
	{
		DEXITMETHOD("PlayList::transferPlayListToDB()");
		return;
	}
	
	beginUpdate();
	
	QString query = "DELETE FROM " + playListSource_ + ";";
	sqlite3_exec(mediaDatabase_->db_, (const char *)query.utf8(), NULL, NULL, NULL);
	mediaDatabase_->dbDebug("DELETE FROM playlist;");

	PlayListItem *item;
	int i = 0;
	
	sqlite3_stmt *vm = stmtInsertMediaIDIntoPlaylist;

	QListViewItemIterator it(this);
	while (it.current())
	{
		item = static_cast<PlayListItem *>(it.current());

		if (!item->id())
		{
			// set new playlist ID if the playlist ID was previously NULL
			sqlite3_bind_int(vm, 1, i);
			sqlite3_bind_null(vm, 2);
			sqlite3_bind_int(vm, 3, item->media()->mediaID());		
			sqlite3_step(vm);
			item->setID(sqlite3_last_insert_rowid(mediaDatabase_->db_));
			DHPRINTF("new item id: %d", item->id());
		}
		else
		{
			DHPRINTF("item id: %d", item->id());
			sqlite3_bind_int(vm, 1, i);
			sqlite3_bind_int(vm, 2, item->id());
			sqlite3_bind_int(vm, 3, item->media()->mediaID());		
			sqlite3_step(vm);
			DHOP(mediaDatabase_->dbDebug("sqlite3_step(vm);"));
		}
		
		//DOP(mediaDatabase_->dbDebug("stmtInsertMediaIDIntoPlaylist " + item->media()->mediaID()));
		sqlite3_reset(vm);

		++it;
		++i;
	} 
	
	endUpdate();
	
	dbPlaylistItemOrderDirty_ = false;
	
	DEXITMETHOD("PlayList::transferPlayListToDB()");
}

void PlayList::loadFromViewSource()
{
	DENTERMETHOD("PlayList::loadFromViewSource()");
	
	beginUpdate();

	activeItem_ = NULL;
	activeID_ = 0;
	activeMediaID_ = 0;
	
	DTIMERINIT(time);

	QString query = "SELECT playlist_id, media_id FROM " + dbSafeString(viewSource_) + " ";
	
	if (params.isSorted)
	{
		QString direction = params.isSortAscending ? "ASC" : "DESC";

		if (params.sortedColumn == 1)
			query += "ORDER BY track " + direction;
		else if (params.sortedColumn == 2)
			query += "ORDER BY title " + direction;
		else if (params.sortedColumn == 3)
			query += "ORDER BY artist " + direction + ", album, track";
		else if (params.sortedColumn == 4)
			query += "ORDER BY album " + direction + ", track";
		else if (params.sortedColumn == 5)
			query += "ORDER BY playtime " + direction;
	}
	else
		query += "ORDER BY idx ASC";
	
	query += ";";
	
	sqlite3_stmt *vm;
	sqlite3_prepare(mediaDatabase_->db_, query.utf8(), -1, &vm, 0);

	DTIMERPRINT(time, "loadFromViewSource query");
	DTIMERSTART(time);

	///*
	QListView::clear();

	DTIMERPRINT(time, "loadFromViewSource ht update");

	PlayListItem *lastNewItem = NULL;
	while (sqlite3_step(vm) == SQLITE_ROW)
		lastNewItem = new PlayListItem(mediaDatabase_->media(sqlite3_column_int(vm, 1)), sqlite3_column_int(vm, 0), this, lastNewItem);
	//*/
		
	/*
	// * too slow currently -> delete current; Therefore not used.
	// * 
	PlayListItem *after = static_cast<PlayListItem *>(firstChild());
	PlayListItem *lastNewItem = NULL;
	Media *media = NULL;
	int playlistID = 0;
	while (sqlite3_step(vm) == SQLITE_ROW)
	{
		playlistID = sqlite3_column_int(vm, 0);
		media = mediaDatabase_->media(sqlite3_column_int(vm, 1));
		
		if (after)
		{
			after->id_ = playlistID;
			after->media_ = media;
			lastNewItem = after;
			after = (PlayListItem *)after->nextSibling();
		}
		else
			lastNewItem = new PlayListItem(media, playlistID, this, lastNewItem);
	}

	DTIMERPRINT(time, "loadFromDB ht update");
	
	// delete remaining items...
	if (after)
	{
		setClearing(true);
	    while (after) 
	    {
	    	lastNewItem = (PlayListItem *)after->nextSibling();
	    	delete after;
	    	after = lastNewItem;
        }
		setClearing(false);
	}
	//*/
	
	DTIMERPRINT(time, "loadFromViewSource t update");
	
	sqlite3_finalize(vm);
	endUpdate();
	
	DEXITMETHOD("PlayList::loadFromViewSource()");
}

void PlayList::updateViewStatistics()
{
	DENTERMETHOD("PlayList::updateViewStatistics()");	
	DTIMERINIT(ptfs);

	sqlite3_stmt *vm;
	QString query = "SELECT SUM(playtime), SUM(filesize), COUNT(filesize) FROM " + dbSafeString(viewSource_) + ";";
	sqlite3_prepare(mediaDatabase_->db_, query, -1, &vm, 0);
	if (sqlite3_step(vm) == SQLITE_ROW)
	{
		totalPlaytime_ = sqlite3_column_int64(vm, 0);
		totalFilesize_ = sqlite3_column_int64(vm, 1);
		itemCount_ = sqlite3_column_int64(vm, 2);
	}
	sqlite3_finalize(vm);
	
	DTIMERINIT(temp);
	emit viewUpdated();
	DTIMERPRINT(temp, "emit viewUpdated();");
	
	DTIMERPRINT(ptfs, "totalPlaytime, totalFilesize");
	DEXITMETHOD("PlayList::updateViewStatistics()");	
}

void PlayList::updateView(bool omitCacheCheck)
{
	DENTERMETHOD("PlayList::updateView()");

	if (updatingView_) 
	{
		DEXITMETHOD("PlayList::updateView()");
		return;
	}
	updatingView_ = true;
	
	beginUpdate();

	DPRINTF("dbFiltered_ : %s", dbFiltered_ ? "true" : "false");
	DPRINTF("dbPlaylistItemOrderDirty_ : %s", dbPlaylistItemOrderDirty_ ? "true" : "false");

	DTIMERINIT(overall);
	
	bool dbPlaylistItemOrderWasDirty = dbPlaylistItemOrderDirty_;
	ensurePlayListValidInDB();

	unsigned long curActiveID = activeID_;
	unsigned long curActiveMediaID = activeMediaID_;
	// save ID of currently selected item so we can select it again later on	
	unsigned long curSelectedID = 0;
	if (currentItem())
		curSelectedID = static_cast<PlayListItem *>(currentItem())->id_;

	// only (re)create view in DB if one of our parameters has changed.
	// this is an attempt to reduce the parse time overhead and to defeat cache bashing in the DB
	// if nothing changed.
	if (dbFilterText_ != params.filterText ||
		dbFiltered_ != params.isFiltered || params.directMode || dbPlaylistItemOrderWasDirty || omitCacheCheck)	
	{
		QString query = 
			"DROP TABLE IF EXISTS playlist_view; "
			"CREATE TEMPORARY TABLE playlist_view AS "
			"SELECT " + playListSource_ + ".playlist_id as playlist_id, " + playListSource_ + ".media_id as media_id, idx, track, title, album, artist, genre, playtime, filesize "
			"FROM " + playListSource_ + ", media "
			"WHERE " + playListSource_ + ".media_id = media.media_id ";
		
		if (params.isFiltered)
		{
			QString conditionString = "";
			QString safeSearchString = "";
		
			// split input filter string into parts so we can build separate conditions for each...
			QStringList searchStrings = QStringList::split(' ', params.filterText, false);
			int searchStringCount = searchStrings.count();
			
			// build condition string for view...
			for (int i = 0; i < searchStringCount; ++i)
			{
				safeSearchString = dbSafeString("%" + searchStrings[i] + "%");
				//conditionString += "(title LIKE '" + safeSearchString + "' OR artist LIKE '" + safeSearchString + "' OR album LIKE '" + safeSearchString + "')";
				
				// the following is faster but less accurate:
				conditionString += "(title || album || artist LIKE '" + safeSearchString + "')";
				
				// only append AND if this is not the last search string
				if (i < searchStringCount -1)
					conditionString += " AND ";			
			}
			
			query += "AND (" + conditionString + ") ";
		}
	
		query += ";";
		
		query_ = query;
		
		DPRINTF("%s", (const char *)query.utf8());
		
		DTIMERINIT(temp);
		sqlite3_exec(mediaDatabase_->db_, query.utf8(), NULL, NULL, NULL);
		// the schema changed, so we need to finalize and re-prepare statements...
		mediaDatabase_->ensurePreparedStatementsAreValid();
		DTIMERPRINT(temp, "query");

		viewSource_ = "playlist_view";
		
		if (!params.directMode)
		{
			DTIMERSTART(temp);
			blockSignals(false);
			emit viewFiltered();
			blockSignals(true);
			DTIMERPRINT(temp, "emit viewFiltered();");
		}
		
		// save current db view state
		dbFilterText_ = params.filterText;
		dbFiltered_ = params.isFiltered;
	}

	loadFromViewSource();

	DTIMERINIT(postwork);
	
	// try to select the previously selected item...
	DPRINTF("setActiveItemByID(%d);", curActiveID);
	if (setActiveItemByID(curActiveID))
	{
		// something was found by the playlist ID!
		// now check, if the media ID fits too.
		// if not, try to set by media ID...
		if (activeItem()->media()->mediaID() != curActiveMediaID)
		{
			setActiveItemByMediaID(curActiveMediaID);
		}
	}
	else
	{
		// nothing was found by playlist ID, try with
		// media ID instead...
		setActiveItemByMediaID(curActiveMediaID);
	}
	
	// even if nothing was found, force activeID...
	activeID_ = curActiveID;
	activeMediaID_ = curActiveMediaID;
	
	// try to find previous selection, reselect it and make it visible
	if (curSelectedID)
	{
		DPRINTF("PlayListItem *foundItem = findItemByID(curSelectedID);");
		PlayListItem *foundItem = findItemByID(curSelectedID);
		if (foundItem)
		{
			DPRINTF("setCurrentItem(foundItem);");
			setCurrentItem(foundItem);
			DPRINTF("ensureItemVisible(foundItem);");
			ensureItemVisible(foundItem);
		}
	}
	
	// make sure to select at least something...
	if (!currentItem() && firstChild())
	{
		DPRINTF("setCurrentItem(firstChild());");
		setCurrentItem(firstChild());
		DPRINTF("ensureItemVisible(firstChild());");
		ensureItemVisible(firstChild());
	}

	DTIMERPRINT(postwork, "postwork");
	DTIMERPRINT(overall, "overall");

	endUpdate();
	updateViewStatistics();

	updatingView_ = false;
	
	DEXITMETHOD("PlayList::updateView()");
}

void PlayList::setFilter(const QString &filter)
{
	if (filter.isEmpty())
	{
		params.isFiltered = false;
		params.filterText = "";
	}
	else
	{
		params.isFiltered = true;
		params.filterText = filter;
	}

	updateView();
}

void PlayList::resetFilter()
{
	setFilter("");
}

void PlayList::resetSorting()
{
	setSortParams(PlayList::COLUMN_NONE);
}

void PlayList::updateFilter()
{
	if (params.isFiltered)
		setFilter(params.filterText);
}

void PlayList::setInputMode(PlayList::InputMode inputmode)
{
	switch (inputmode)
	{
	case PlayList::Select:
		selectAll(false);
		setSelectionMode(QListView::Single);
		if (currentItem())
			currentItem()->setSelected(true);
		break;
	case PlayList::MultiSelect:
	case PlayList::Move:
		setSelectionMode(QListView::Extended);
		break;
	}
	
	inputMode_ = inputmode;
}

void PlayList::takeItem(QListViewItem *item)
{
	if (item == activeItem_)
		setActiveItem(0);
	
	dbPlaylistItemOrderDirty_ = true;
		
	QListView::takeItem(item);
}

QValueList<unsigned long> PlayList::getSelectedMediaIDs()
{
	QValueList<unsigned long> list;
	for (PlayListItem *item = selectedItems_.first(); item != NULL; item = selectedItems_.next())
		list.append(item->media()->mediaID());
	return list;
}

QList<Media> PlayList::getSelectedMedia()
{
	QList<Media> list;
	for (PlayListItem *item = selectedItems_.first(); item != NULL; item = selectedItems_.next())
		list.append(item->media());
	return list;
}       

QValueList<unsigned long> PlayList::getSelectedItemIDs()
{
	QValueList<unsigned long> list;
	for (PlayListItem *item = selectedItems_.first(); item != NULL; item = selectedItems_.next())
		list.append(item->id());
	return list;
}

QList<PlayListItem> & PlayList::selectedItems()
{
	return selectedItems_;
}

void PlayList::removeSelectedItems()
{
	if (selectedItems_.count() > 0)
	{
		// important to get the selected IDs here, since beginChangingUpdate
		// will clear the list and thus the selection...
		QValueList<unsigned long> itemIDs = getSelectedItemIDs();
		
		beginChangingUpdate();
		
		sqlite3_stmt *vm;
		QString query = "DELETE FROM " + playListSource_ + " WHERE playlist_id = ?1;";
		sqlite3_prepare(mediaDatabase_->db_, query.utf8(), -1, &vm, 0);
	
		for (QValueList<unsigned long>::Iterator it = itemIDs.begin(); it != itemIDs.end(); ++it)
		{
			sqlite3_bind_int(vm, 1, (*it));
			sqlite3_step(vm);
			sqlite3_reset(vm);
		}
	
		sqlite3_finalize(vm);
		
		endChangingUpdate();
		reloadView();
	}		
}

void PlayList::mediaDatabaseCompacted()
{
	// update the current view in any case
	// omit filter checks because something might have changed in our
	// playlist, ie. items that were previously there are missing now
	// because the files were missing...
	reloadView();
}

void PlayList::headerIndexChange(int section, int fromIndex, int toIndex) 
{
	// don't allow moving the first column...
	if (header()->mapToIndex(0) != 0)
	{
		header()->moveSection(0, 0); // make sure, blank column is always first.
		header()->update();
	}
}

void PlayList::headerClicked(int section)
{
	DENTERMETHOD("PlayList::headerClicked");
	if (section != params.sortedColumn)
	{
		params.sortedColumn = section;
		params.isSortAscending = true;
	}
	else
	{
		params.isSortAscending = !params.isSortAscending;
	}
	
	params.isSorted = section != 0;

	header()->setSortIndicator(params.sortedColumn, !params.isSortAscending);
	updateView();
	emit viewSorted();
	
	DPRINTF("Header section %d clicked. Preparing playlist item index...", section);
	DEXITMETHOD("PlayList::headerClicked");
}

void PlayList::setSorting(int column, bool ascending)
{
	// omit sorting. We'll do it in our DB backend...
	// NOTE: This is a no-op stub, because the ListView widget calls it internally
	//       Call the method below for a similar functionality.
}

void PlayList::setSortParams(int column, bool ascending)
{
	DENTERMETHOD("PlayList::setSortParams");
	
	// record sorted section and sort direction...	
	params.sortedColumn = column;
	params.isSortAscending = ascending;
	params.isSorted = column != 0;
	
	header()->setSortIndicator(params.sortedColumn, !params.isSortAscending);
	updateView();
	emit viewSorted();
		
	DEXITMETHOD("PlayList::setSortParams");
}

void PlayList::setParams(PlayListParams params)
{
	this->params = params;
	header()->setSortIndicator(params.sortedColumn, !params.isSortAscending);
}

void PlayList::setSortedColumn(int column)
{
	headerClicked(column);
}

void PlayList::setPlayOrder(PlayOrder newOrder)
{
	currentIndex = 0;
	playOrder_ = newOrder;
	// generatePlayOrderTable();
}

/*!s
	@brief	generatePlayOrderTable()で使用
	
	libstdc++がリンクできないのでalgorithmのstd::swap()の代用
*/
void PlayList::swap(int &n1, int &n2)
{
	int n = n1;
	n1 = n2;
	n2 = n;
}

/*!
	@brief	再生順序のテーブルを作成する
	
	アイテムの数が変化する場合は必ず呼び出す
*/
void PlayList::generatePlayOrderTable()
{
	currentIndex = 0;
	
	delete playOrderTable;
	playOrderTable = NULL;
	
	const int count = count_ = this->childCount();
	
	if (count < 1)
		return;
	
	if (playOrder_ == ORDER_RANDOM || playOrder_ == ORDER_REPEAT_RANDOM)
	{
		playOrderTable = new int[count];
		
		for (int i = 0; i != count; ++i)
			playOrderTable[i] = i;
		
		// Randomize
		std::srand(time(NULL));
		
		for (int i = 0; i != count; ++i)
			swap(playOrderTable[i], playOrderTable[std::rand() % count]);

		if (currentItem())
		{
			int selectedIndex = itemIndex(currentItem());
			
			// make selected index the first in our play order table...
			for (int i = 0; i != count; ++i)
				if (playOrderTable[i] == selectedIndex)
				{
					swap(playOrderTable[i], playOrderTable[0]);
					break;
				}
		}
	}
}

void PlayList::checkPlayOrderTable()
{
	if (!playOrderTable || count_ != this->childCount())
		generatePlayOrderTable();
}

Media *PlayList::currentMedia()
{
	if (childCount() < 1)
		return NULL;
	
	if (playOrder_ == ORDER_RANDOM || playOrder_ == ORDER_REPEAT_RANDOM)
	{
		checkPlayOrderTable();
		setActiveItemByIndex(playOrderTable[currentIndex]);
	}
	else if (!activeItem_)
	{
		if (currentItem())
			setActiveItem(static_cast<PlayListItem *>(currentItem()));
		else
			setActiveItem(firstItem());			
	}
	
	return activeItem_->media();
}

PlayListItem *PlayList::firstItem()
{
	return static_cast<PlayListItem *>(firstChild());
}

PlayListItem *PlayList::lastItem()
{
	if (childCount() < 1)
		return NULL;
	
	PlayListItem *item = activeItem_;
	
	if (!item)
		item = firstItem();
	
	if (!item)
		return NULL;
	
	while (item->nextSibling())
		item = static_cast<PlayListItem *>(item->nextSibling());
	
	return item;
}

PlayListItem *PlayList::previousItem()
{
	if (!activeItem_)
		return NULL;
	
	if (activeItem_ == firstChild())
		return NULL;
	
	if (activeItem_->itemAbove())
		return static_cast<PlayListItem *>(activeItem_->itemAbove());
	else
		return NULL;
}

PlayListItem *PlayList::nextItem()
{
	if (!activeItem_)
		return NULL;
	
	if (activeItem_->nextSibling())
		return static_cast<PlayListItem *>(activeItem_->nextSibling());
	else
		return NULL;
}

PlayListItem *PlayList::nextPlayItem(bool modifyCurrentIndex)
{
	int count = this->childCount();
	
	if (count < 1)
		return NULL;
	
	PlayListItem* item = NULL;
	
	switch (playOrder_)
	{
	case ORDER_NORMAL:
		if (!activeItem_)
			item = static_cast<PlayListItem *>(currentItem());
		else if (!nextItem())
			item = NULL;
		else
			item = nextItem();
		
		break;
		
	case ORDER_REPEAT_ONE:
		item = activeItem_;
	
		break;
	
	case ORDER_REPEAT_ALL:
		if (!nextItem())
			item = firstItem();
		else
			item = nextItem();
		
		break;
	
	case ORDER_RANDOM:
		checkPlayOrderTable();

		if ((currentIndex + 1) < count)
			item = findItemByIndex(playOrderTable[modifyCurrentIndex ? ++currentIndex : currentIndex + 1]);
		else
			item = NULL;
		
		break;
	
	case ORDER_REPEAT_RANDOM:
		checkPlayOrderTable();

		if ((currentIndex + 1) < count)
			item = findItemByIndex(playOrderTable[modifyCurrentIndex ? ++currentIndex : currentIndex + 1]);
		else
			item = findItemByIndex(playOrderTable[modifyCurrentIndex ? currentIndex = 0 : 0]);
		
		break;
	}
	
	return item;
}

PlayListItem *PlayList::previousPlayItem(bool modifyCurrentIndex)
{
	int count = this->childCount();
	
	if (count < 1)
		return NULL;
	
	PlayListItem* item = NULL;
	
	switch (playOrder_)
	{
	case ORDER_NORMAL:
		if (activeItem_ == firstItem())
			item = NULL;
		else
			item = previousItem();
		
		break;
		
	case ORDER_REPEAT_ONE:
		break;
		
	case ORDER_REPEAT_ALL:
		if (activeItem_ == firstItem())
			item = lastItem();
		else
			item = previousItem();
		
		break;
	
	case ORDER_RANDOM:
		checkPlayOrderTable();

		if (currentIndex <= 0)
			item = NULL;
		else
			item = findItemByIndex(playOrderTable[modifyCurrentIndex ? --currentIndex : currentIndex - 1]);
		
		break;
	
	case ORDER_REPEAT_RANDOM:
		checkPlayOrderTable();

		if (currentIndex <= 0)
			item = NULL;
		else
			item = findItemByIndex(playOrderTable[modifyCurrentIndex ? --currentIndex : currentIndex - 1]);
		
		break;
	}
	
	return item;
}

Media *PlayList::nextMedia()
{
	PlayListItem *item = nextPlayItem(true);
	
	if (!item)
		return NULL;
	
	setActiveItem(item, true, playOrder_ == ORDER_RANDOM || playOrder_ == ORDER_REPEAT_RANDOM);
	return currentMedia();
}

Media *PlayList::previousMedia()
{
	PlayListItem *item = previousPlayItem(true);
	
	if (!item)
		return NULL;
	
	setActiveItem(item, true, playOrder_ == ORDER_RANDOM || playOrder_ == ORDER_REPEAT_RANDOM);
	return currentMedia();
}

PlayListItem *PlayList::insertItem(Media *media, PlayListItem *after)
{
	if (media)
	{
		if (!after)
		{
			QListViewItemIterator it(this);
			QListViewItemIterator last;
			while (it.current()) {
				last = it;
				++ it;
			}
			
			after = (PlayListItem *)last.current();
		}
		
		// add to database...
		sqlite3_stmt *vm = stmtInsertMediaIDIntoPlaylist;
		sqlite3_bind_null(vm, 1);
		sqlite3_bind_null(vm, 2);
		sqlite3_bind_int(vm, 3, media->mediaID());		
		sqlite3_step(vm);
		sqlite3_reset(vm);
		// ... and get new playlist ID
		int newID = sqlite3_last_insert_rowid(mediaDatabase_->db_);

		if (!params.isSorted && !params.isFiltered && after)
			dbPlaylistItemOrderDirty_ = true;
		
		return new PlayListItem(media, newID, this, after);
	}
	else
		return NULL;
}

PlayListItem *PlayList::insertItem(unsigned long mediaID, PlayListItem *after)
{
	return insertItem(mediaDatabase_->media(mediaID), after);	
}

PlayListItem *PlayList::insertItem(const QString &mediaFilename, PlayListItem *after)
{
	return insertItem(mediaDatabase_->media(mediaFilename), after);	
}

void PlayList::keyPressEvent(QKeyEvent *e)
{
	if (e->key() == Key_Delete && currentItem()) {
		takeItem(currentItem());
		e->accept();
		return;
	}
	
	QListView::keyPressEvent(e);
}

void PlayList::contentsMousePressEvent(QMouseEvent *e) 
{
	if (inputMode() == PlayList::Move)
	{
		buttonDown_ = true;
		moveTriggered_ = false;
		buttonDownPos_ = e->pos();
	}
	else
		QListView::contentsMousePressEvent(e);
}

void PlayList::contentsMouseMoveEvent(QMouseEvent *e) 	
{
	if (inputMode() == PlayList::Move)
	{
		if (buttonDown_ && !params.isSorted && !params.isFiltered)
		{
			if (moveTriggered_)
			{
			    QPoint vp = contentsToViewport(e->pos());
			    QListViewItem *i = itemAt(vp);
			    bool moveFirstChild = false;
			    
			    if (i == firstChild())
			    {
			    	moveFirstChild = true;
			    }
			    else if (i)
			    {
			    	QListViewItem *iabove = i->itemAbove(); 
			    	if (iabove && !selectedItemsForMove_.contains(static_cast<PlayListItem *>(iabove)))
			    		i = iabove;
			    }
			    else
			    {
			    	i = firstChild();
			    	while (i->nextSibling())
			    		i = i->nextSibling();
			    }
			    
			    if (!selectedItemsForMove_.contains(static_cast<PlayListItem *>(i)))
			    {
				    PlayListItem *pli;
				    for (pli = selectedItemsForMove_.first(); pli != 0; pli = selectedItemsForMove_.next())
				    {
				    	pli->moveItem(i);
				    	i = pli;
				    }
				    
				    if (moveFirstChild)
				    	firstChild()->moveItem(i);
				    	
				    dbPlaylistItemOrderDirty_ = true;
			    }
			}
			else
			{
				if (e->pos().y() > buttonDownPos_.y() + 10 ||
					e->pos().y() < buttonDownPos_.y() - 10)
				{
					moveTriggered_ = true;
					selectedItemsForMove_ = selectedItems();
				}
			}
		    
		    return;
		}
	}

	QListView::contentsMouseMoveEvent(e);
}

void PlayList::contentsMouseReleaseEvent(QMouseEvent *e) 
{
	buttonDown_ = false;
	moveTriggered_ = false;
	
	QListView::contentsMouseReleaseEvent(e);
}

/*!
	@brief	現在のアイテムと一つ前のアイテムの順序を入れ替える
*/
void PlayList::swapPreviousItem()
{
	if (params.isSorted || params.isFiltered) return;
	
	// Check item exist
	if (!currentItem() || !currentItem()->itemAbove() || childCount() < 1)
		return;
	
	// If first item, there are nothing to do
	if (currentItem() == firstChild())
		return;
	
	currentItem()->itemAbove()->moveItem(currentItem());
	
	dbPlaylistItemOrderDirty_ = true;
}

/*!
	@brief	現在のアイテムと一つ後のアイテムの順序を入れ替える
*/
void PlayList::swapNextItem()
{
	if (params.isSorted || params.isFiltered) return;
	
	// Check item exist
	if (!currentItem() || !currentItem()->itemBelow() || childCount() < 1)
		return;
	
	// If last item, there are nothing to do
	if (!currentItem()->nextSibling())
		return;
	
	currentItem()->moveItem(currentItem()->itemBelow());
	
	dbPlaylistItemOrderDirty_ = true;
}

PlayListItem *PlayList::findItemByID(unsigned long playlistID)
{
	QListViewItemIterator it(this);
	for (; it.current() && static_cast<PlayListItem *>(it.current())->id_ != playlistID; ++it)
		;

	return static_cast<PlayListItem *>(it.current());
}

bool PlayList::setActiveItemByID(unsigned long playlistID)
{
	DHPRINTF("setActiveItemByID %d", playlistID);
	
	PlayListItem *foundItem = findItemByID(playlistID);
	
	if (foundItem)
	{
		setActiveItem(foundItem, true);
		return true;
	}
	
	return false;
}
PlayListItem *PlayList::findItemByMediaID(unsigned long mediaID)
{
	QListViewItemIterator it(this);
	for (; it.current() && static_cast<PlayListItem *>(it.current())->media()->mediaID() != mediaID; ++it)
		;

	return static_cast<PlayListItem *>(it.current());
}

bool PlayList::setActiveItemByMediaID(unsigned long mediaID)
{
	DHPRINTF("setActiveItemByMediaID %d", mediaID);
	
	PlayListItem *foundItem = findItemByMediaID(mediaID);
	
	if (foundItem)
	{
		setActiveItem(foundItem, true);
		return true;
	}
	
	return false;
}

PlayListItem *PlayList::findItemByIndex(int index)
{
	if (this->childCount() <= index)
		return NULL;

	QListViewItemIterator it(this);
	for (int i = 0; it.current() && i != index; ++it, ++i);
	
	return static_cast<PlayListItem *>(it.current());
}

void PlayList::setActiveItemByIndex(int index)
{
	PlayListItem *foundItem = findItemByIndex(index); 
	
	if (foundItem)
		setActiveItem(foundItem, true, true); // also select new item (ie. in random mode...)
}

void PlayList::setActiveItem(PlayListItem *item, bool ensureVisible, bool selectItem)
{
	PlayListItem *oldActiveItem = activeItem_;
	activeItem_ = item;
	
	if (activeItem_)
	{
		activeID_ = item->id();
		activeMediaID_ = item->media()->mediaID();
	}
	else
	{
		activeID_ = 0;
		activeMediaID_ = 0;
	}
	
	if (oldActiveItem)
		oldActiveItem->repaint();

	if (activeItem_)
	{
		activeItem_->repaint();

		if (selectItem)
		{
			setCurrentItem(activeItem_);
			setSelected(activeItem_, true);
		}

		if (ensureVisible)
			ensureItemVisible(activeItem_);		
	}
}

void PlayList::setActiveItemPaused(bool y)
{
	activeItemPaused_ = y;
	if (activeItem_)
		activeItem_->repaint();
}

void PlayList::setCurrentItemActive()
{
	setActiveItem(static_cast<PlayListItem *>(currentItem()));
	setActiveItemPaused(false);
}

int PlayList::itemIndex(QListViewItem* item)
{
	if (this->childCount() == 0)
		return -1;
	
	int i = 0;
	for (QListViewItemIterator it(this); it.current() && it.current() != item; ++it, ++i)
		;

	return i;	
}

void PlayList::saveColumnOrder(const QCString &configName, const QCString &groupName)
{
	Config cfg(configName, Config::File);
	cfg.setGroup(groupName);
	
	for (int i = 0; i < columns(); ++i)
	{
		int section = header()->mapToSection(i);
		cfg.writeEntry(
			"section_" + QString::number(i), 
			section != params.sortedColumn ?
					QString::number(section) :
					QString("%1,%2").arg(section).arg(params.isSortAscending ? 1 : 0)
		);
	}
}

void PlayList::saveColumnWidth(const QCString &configName, const QCString &groupName)
{
	Config cfg(configName, Config::File);
	cfg.setGroup(groupName);
	
	for (int i = 0; i < columns(); ++i)
		cfg.writeEntry("width_" + QString::number(i), columnWidth(header()->mapToSection(i)));
}

void PlayList::loadColumnOrder(const QCString &configName, const QCString &groupName)
{
	Config cfg(configName, Config::File);
	cfg.setGroup(groupName);

	for (int i = 0; i < columns(); ++i)
	{
		QString key = "section_" + QString::number(i);
		if (cfg.hasKey(key))
		{
			QStringList args = cfg.readListEntry(key, ',');
			int section = args[0].toInt();
			header()->moveSection(section, i);
			if (args.count() == 2)
				setSortParams(section, args[1].toInt() == 1);
		}
	}
}

void PlayList::loadColumnWidth(const QCString &configName, const QCString &groupName)
{
	Config cfg(configName, Config::File);
	cfg.setGroup(groupName);

	for (int i = 0; i < columns(); ++i)
	{
		QString key = "width_" + QString::number(i);
		if (cfg.hasKey(key))
			setColumnWidth(header()->mapToSection(i), cfg.readNumEntry(key));
	}
}

void PlayList::setBackgroundColors(const QColor &evenColor, const QColor &oddColor)
{
	oddRowColorGroup_.setColor(QColorGroup::Base, oddColor);
	evenRowColorGroup_.setColor(QColorGroup::Base, evenColor);
}

void PlayList::setForegroundColor(const QColor &textColor)
{
	oddRowColorGroup_.setColor(QColorGroup::Text, textColor);
	oddRowColorGroup_.setColor(QColorGroup::Foreground, textColor);	
	evenRowColorGroup_.setColor(QColorGroup::Text, textColor);
	evenRowColorGroup_.setColor(QColorGroup::Foreground, textColor);
}

void PlayList::setPlayIndicatorImage(const QPixmap &image)
{
	playIndicatorImage_ = &image;
}

void PlayList::setPauseIndicatorImage(const QPixmap &image)
{
	pauseIndicatorImage_ = &image;	
}

void PlayList::setSkinMode(const SkinModeInformation &modeInfo)
{
	if (modeInfo.playListFont().font.isUsed())
		setFont(modeInfo.playListFont().font);
	else
		unsetFont();

	// set general palette:
	QPalette palette = QApplication::palette();

	if (modeInfo.playListFont().fontColor.isUsed())
	{
		palette.setColor(QColorGroup::Text, modeInfo.playListFont().fontColor);
		palette.setColor(QColorGroup::Foreground, modeInfo.playListFont().fontColor);
	}

	if (modeInfo.playListBackgroundColorSelected().isUsed())
	{
		palette.setColor(QColorGroup::Highlight, modeInfo.playListBackgroundColorSelected());
		palette.setColor(QColorGroup::HighlightedText, modeInfo.playListFontColorSelected());
	}

	// set even and odd colors for items:
	if (modeInfo.playListBackgroundColorEven().isUsed())
	{
		if (modeInfo.playListBackgroundColorOdd().isUsed())
			setBackgroundColors(modeInfo.playListBackgroundColorEven(), modeInfo.playListBackgroundColorOdd());
		else
			setBackgroundColors(modeInfo.playListBackgroundColorEven(), modeInfo.playListBackgroundColorEven());
		
		palette.setColor(QColorGroup::Base, modeInfo.playListBackgroundColorEven());
	}
	else
		setBackgroundColors(palette.active().base(), palette.active().base());
	
	setPalette(palette);

	setPlayIndicatorImage(modeInfo.playListPlayIndicatorImage());
	setPauseIndicatorImage(modeInfo.playListPauseIndicatorImage());

	if (modeInfo.playListHeaderFont().font.isUsed())
		header()->setFont(modeInfo.playListHeaderFont().font);
	else
		header()->unsetFont();
		
	if (modeInfo.playListHeaderColor().isUsed())
	{
		QPalette palette(modeInfo.playListHeaderColor());
		
		if (modeInfo.playListHeaderFont().fontColor.isUsed())
		{
			palette.setColor(QColorGroup::Text, modeInfo.playListHeaderFont().fontColor);
			palette.setColor(QColorGroup::ButtonText, modeInfo.playListHeaderFont().fontColor);
			palette.setColor(QColorGroup::Foreground, modeInfo.playListHeaderFont().fontColor);
		}

		header()->setPalette(palette);
	}
	else
		header()->unsetPalette();
			
	if (modeInfo.playListScrollBarColor().isUsed())
	{
		horizontalScrollBar()->setPalette(QPalette(modeInfo.playListScrollBarColor()));
		verticalScrollBar()->setPalette(QPalette(modeInfo.playListScrollBarColor()));
	}
	else
	{
		horizontalScrollBar()->unsetPalette();
		verticalScrollBar()->unsetPalette();
	}
}

void PlayList::cleanUpMediaCache()
{
	mediaDatabase_->compactDB();
}

void PlayList::saveMediaCache()
{
	if (mediaDatabase_->hasChanged())
	{
		DPRINTF("Saving media cache...");
		mediaDatabase_->save(this);
	}
}

void PlayList::setPlayListSource(const QString &name)
{
	if (name != playListSource_)
	{
		// We are about to switch the playlist source table.
		// So, make sure the current order is saved to the table...
		ensurePlayListValidInDB();
		playListSource_ = name;
		mediaDatabaseFinalizingStatements(mediaDatabase_->db());
		mediaDatabasePreparingStatements(mediaDatabase_->db());
		reloadView();
	}
}

void PlayList::setViewSource(const QString &name)
{
	DENTERMETHOD("PlayList::setViewSource(\"%s\")", (const char*)name.utf8());
	// We are about to switch the view source table.
	// So, make sure the current order is saved to the table...
	ensurePlayListValidInDB();
	viewSource_ = name;
	updateView();
	DEXITMETHOD("PlayList::setViewSource(\"%s\")", (const char*)name.utf8());
}

void PlayList::mediaDatabasePreparingStatements(sqlite3 *db)
{
	QString query = "INSERT INTO " + playListSource_ + " VALUES(?1, ?2, ?3);";
	sqlite3_prepare(db, (const char *)query.utf8(), -1, &stmtInsertMediaIDIntoPlaylist, 0);
}

void PlayList::mediaDatabaseFinalizingStatements(sqlite3 *db)
{
	sqlite3_finalize(stmtInsertMediaIDIntoPlaylist); 
	stmtInsertMediaIDIntoPlaylist = NULL;
}

void PlayList::itemSelectedStateChanged(PlayListItem *item, bool selected)
{
	DPRINTF("==============================");
	
	if (selected)
	{
		DPRINTF("item selected %d", (uint)item);
		selectedItems_.append(item);
	}
	else
	{
		DPRINTF("item deselected %d", (uint)item);
		selectedItems_.remove(item);
	}
	
	DPRINTF("selectedItems_.count() = %d", selectedItems_.count());
	DPRINTF("-----------------------------");
}
