/*
 * Copyright (C) 2006-2007	Andre Beckedorf
 * 					 		<evilJazz _AT_ katastrophos _DOT_ net>
 *
 * 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 <qstringlist.h>
#include <qlistbox.h>
#include <qhbox.h>
#include <qlistbox.h>
#include <qlabel.h>
#include <qvbox.h>
#include <qtimer.h>
#include <qapplication.h>

#include "playlistoverview.h"
#include "playlistmanager.h"
#include "playlist.h"
#include "mediadatabase.h"
#include "skin.h"

#include "debug.h"

PlayListOverview::PlayListOverview(PlayList *playlist, QWidget *parent, const char *name)
	: QHBox(parent, name),
	playList_(playlist),
	updatingGenres_(false),
	updatingArtists_(false),
	updatingAlbums_(false),
	selectedGenresFilter(""),
	selectedArtistsFilter(""),
	selectedAlbumsFilter("")
{
	connect(playlist, SIGNAL(viewFiltered()), 
			this, SLOT(playListViewFiltered()));
	
	QFont font;
	font.setPixelSize(9);
	QPalette palette(QColor("#696969"));
	
	genreBox_ = new QVBox(this);
	genreHeading_ = new QLabel(genreBox_);
	genreHeading_->setPalette(palette);
	genreHeading_->setFont(font);
	genreHeading_->setAlignment(Qt::AlignCenter | Qt::AlignVCenter | Qt::ExpandTabs);
	genreHeading_->setText("Genres");
	
	genreListBox_ = new QListBox(genreBox_);
	genreListBox_->setFrameStyle(QFrame::NoFrame);
	genreListBox_->setSelectionMode(QListBox::Multi);
	genreListBox_->setHScrollBarMode(QScrollView::AlwaysOff);
	
	// black line separator between list boxes...
	QWidget *dummywidget = new QWidget(this);
	dummywidget->setFixedWidth(1);
	dummywidget->setBackgroundColor(QColor("#000000"));
	
	artistBox_ = new QVBox(this);
	artistHeading_ = new QLabel(artistBox_);
	artistHeading_->setPalette(palette);
	artistHeading_->setFont(font);
	artistHeading_->setAlignment(Qt::AlignCenter | Qt::AlignVCenter | Qt::ExpandTabs);
	artistHeading_->setText("Artists");
	
	artistListBox_ = new QListBox(artistBox_);
	artistListBox_->setFrameStyle(QFrame::NoFrame);
	artistListBox_->setSelectionMode(QListBox::Multi);
	artistListBox_->setHScrollBarMode(QScrollView::AlwaysOff);

	// black line separator between list boxes...
	dummywidget = new QWidget(this);
	dummywidget->setFixedWidth(1);
	dummywidget->setBackgroundColor(QColor("#000000"));
	
	albumBox_ = new QVBox(this);
	albumHeading_ = new QLabel(albumBox_);
	albumHeading_->setPalette(palette);
	albumHeading_->setFont(font);
	albumHeading_->setAlignment(Qt::AlignCenter | Qt::AlignVCenter | Qt::ExpandTabs);
	albumHeading_->setText("Albums");
	
	albumListBox_ = new QListBox(albumBox_);
	albumListBox_->setFrameStyle(QFrame::NoFrame);
	albumListBox_->setSelectionMode(QListBox::Multi);
	albumListBox_->setHScrollBarMode(QScrollView::AlwaysOff);
	
	font.setPixelSize(8);
	
	genreListBox_->setFont(font);
	artistListBox_->setFont(font);
	albumListBox_->setFont(font);
	
	connect(genreListBox_, SIGNAL(selectionChanged()),
			this, SLOT(genreListBoxSelectionChanged()));
	
	connect(artistListBox_, SIGNAL(selectionChanged()),
			this, SLOT(artistListBoxSelectionChanged()));
	
	connect(albumListBox_, SIGNAL(selectionChanged()),
			this, SLOT(albumListBoxSelectionChanged()));
	
	updateTimer_ = new QTimer();
	connect(updateTimer_, SIGNAL(timeout()), this, SLOT(updateView()));
	
	genreListBox_->installEventFilter(this);
	artistListBox_->installEventFilter(this);
	albumListBox_->installEventFilter(this);
	
	this->setFocusPolicy(QWidget::StrongFocus);
	this->setFocusProxy(genreListBox_);
}

PlayListOverview::~PlayListOverview()
{
	delete updateTimer_;
}

void PlayListOverview::setSkinMode(const SkinModeInformation &modeInfo)
{
	if (modeInfo.playListOverviewFont().font.isUsed())
	{
		genreListBox_->setFont(modeInfo.playListOverviewFont().font);
		artistListBox_->setFont(modeInfo.playListOverviewFont().font);
		albumListBox_->setFont(modeInfo.playListOverviewFont().font);
	}
	else
	{
		genreListBox_->unsetFont();
		artistListBox_->unsetFont();
		albumListBox_->unsetFont();
	}

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

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

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

	// set even and odd colors for items:
	if (modeInfo.playListOverviewBackgroundColor().isUsed())
		palette.setColor(QColorGroup::Base, modeInfo.playListOverviewBackgroundColor());
	
	genreListBox_->setPalette(palette);
	artistListBox_->setPalette(palette);
	albumListBox_->setPalette(palette);

	if (modeInfo.playListOverviewHeaderFont().font.isUsed())
	{
		genreHeading_->setFont(modeInfo.playListOverviewHeaderFont().font);
		artistHeading_->setFont(modeInfo.playListOverviewHeaderFont().font);
		albumHeading_->setFont(modeInfo.playListOverviewHeaderFont().font);
	}
	else
	{
		genreHeading_->unsetFont();
		artistHeading_->unsetFont();
		albumHeading_->unsetFont();
	}
		
	if (modeInfo.playListOverviewHeaderColor().isUsed())
	{
		QPalette palette(modeInfo.playListOverviewHeaderColor());
		
		if (modeInfo.playListOverviewHeaderFont().fontColor.isUsed())
		{
			palette.setColor(QColorGroup::Text, modeInfo.playListOverviewHeaderFont().fontColor);
			palette.setColor(QColorGroup::ButtonText, modeInfo.playListOverviewHeaderFont().fontColor);
			palette.setColor(QColorGroup::Foreground, modeInfo.playListOverviewHeaderFont().fontColor);
		}

		genreHeading_->setPalette(palette);
		artistHeading_->setPalette(palette);
		albumHeading_->setPalette(palette);
	}
	else
	{
		genreHeading_->unsetPalette();
		artistHeading_->unsetPalette();
		albumHeading_->unsetPalette();
	}
			
	if (modeInfo.playListOverviewScrollBarColor().isUsed())
	{
		genreListBox_->horizontalScrollBar()->setPalette(QPalette(modeInfo.playListOverviewScrollBarColor()));
		genreListBox_->verticalScrollBar()->setPalette(QPalette(modeInfo.playListOverviewScrollBarColor()));
		artistListBox_->horizontalScrollBar()->setPalette(QPalette(modeInfo.playListOverviewScrollBarColor()));
		artistListBox_->verticalScrollBar()->setPalette(QPalette(modeInfo.playListOverviewScrollBarColor()));
		albumListBox_->horizontalScrollBar()->setPalette(QPalette(modeInfo.playListOverviewScrollBarColor()));
		albumListBox_->verticalScrollBar()->setPalette(QPalette(modeInfo.playListOverviewScrollBarColor()));
	}
	else
	{
		genreListBox_->horizontalScrollBar()->unsetPalette();
		genreListBox_->verticalScrollBar()->unsetPalette();
		artistListBox_->horizontalScrollBar()->unsetPalette();
		artistListBox_->verticalScrollBar()->unsetPalette();
		albumListBox_->horizontalScrollBar()->unsetPalette();
		albumListBox_->verticalScrollBar()->unsetPalette();
	}
}

void PlayListOverview::showEvent(QShowEvent *e)
{
	if (!e->spontaneous() && isDirty_)
		playListViewFiltered();
}

bool PlayListOverview::eventFilter(QObject *object, QEvent *event)
{
	if (event->type() == QEvent::KeyPress) 
	{
		QKeyEvent *e = static_cast<QKeyEvent *>(event);
		int key = e->key();
	
		QWidget *newFocusedWidget = NULL;
	
		if (object == genreListBox_)
		{
			if (key == Qt::Key_Right)
				newFocusedWidget = artistListBox_; 
			else if (key == Qt::Key_Left)
				emit focusOutLeft();
		}
		else if (object == artistListBox_)
		{
			if (key == Qt::Key_Right)
				newFocusedWidget = albumListBox_; 
			else if (key == Qt::Key_Left)
				newFocusedWidget = genreListBox_; 
		}
		else if (object == albumListBox_)
		{
			if (key == Qt::Key_Right)
				emit focusOutRight();
			else if (key == Qt::Key_Left)
				newFocusedWidget = artistListBox_; 
		}
	
		if (newFocusedWidget)
		{
			newFocusedWidget->setFocus();
			e->accept();
			return true;
		}		
	
		return QHBox::eventFilter(object, event);
	}
	
	if (event->type() == QEvent::FocusIn)
		setFocusProxy(static_cast<QWidget *>(object));
	
	return QHBox::eventFilter(object, event);
}

QString PlayListOverview::buildFilter(QListBox *list, const QString &unknown)
{
	QString filter = "";
	int selectedCount = 0;

	if (list->count() > 1 && !list->isSelected(0))
		for (int i = 1; i < list->count(); ++i)
			if (list->isSelected(i))
			{
				++selectedCount;
				if (list->item(i)->text() == unknown)
					filter += (selectedCount > 1 ? QString(", ") : QString("")) + "NULL, \"\"";
				else
					filter += (selectedCount > 1 ? QString(", ") : QString("")) + "\"" + dbSafeString(list->item(i)->text()) + "\"";
			}
	
	return filter;
}

int PlayListOverview::selectionCount(QListBox *list)
{
	int selectedCount = 0;
	
	for (int i = 1; i < list->count(); ++i)
		if (list->isSelected(i))
			++selectedCount;
	
	return selectedCount;
}

void PlayListOverview::playListViewFiltered()
{
	DENTERMETHOD("PlayListOverview::playListViewFiltered()");
	
	// if the overview widget is invisible and no genre, artist or album filter is set, we can just set 
	// it into dirty state and thus speed up processing. Once the overview widget is shown again, we check for isDirty
	// in the showEvent method and update the lists' content accordingly...
	if (!isVisible() && genreListBox_->isSelected(0) && artistListBox_->isSelected(0) && albumListBox_->isSelected(0))
		isDirty_ = true;
	else
	{
		isDirty_ = false;
		
		sqlite3_exec(playList_->mediaDatabase()->db(),
			"DROP TABLE IF EXISTS overview;"
			"CREATE TEMPORARY TABLE overview AS "
				"SELECT DISTINCT artist, album, genre FROM playlist_view;", NULL, NULL, NULL);
	
		updateGenres(false);
	}
		
	DEXITMETHOD("PlayListOverview::playListViewFiltered()");	
}

void PlayListOverview::buildFilterAndCreateFilteredTable(QString &selectedFilter, const QString &unknown, QListBox *listbox, const QString &srctable, const QString &dsttable, const QString &what, const QString &wherecol)
{
	// build new filter from current selection...
	selectedFilter = buildFilter(listbox, unknown);
	DPRINTF("selectedFilter : %s", (const char*)selectedFilter.utf8());
	
	QString query =
		"DROP TABLE IF EXISTS " + dsttable + ";"
		"CREATE TEMPORARY TABLE " + dsttable + " AS "
			"SELECT DISTINCT " + what + " FROM " + srctable +
			(selectedFilter.length() > 0 ? " WHERE " + wherecol + " IN (" + selectedFilter + ");" : QString(";"));

	DPRINTF("query : %s", (const char*)query.utf8());	
	sqlite3_exec(playList_->mediaDatabase()->db(), query.utf8(), NULL, NULL, NULL);
}

void PlayListOverview::beginUpdateOn(QWidget *widget)
{
	widget->setUpdatesEnabled(false);
	widget->blockSignals(true);
}

void PlayListOverview::endUpdateOn(QWidget *widget)
{
	widget->blockSignals(false);
	widget->setUpdatesEnabled(true);
}

void PlayListOverview::doUpdateOnListBox(QListBox *listbox, const QString &colname, const QString &srctable, const QString &unknown, const QString &one, const QString &more)
{
	QString selectedFilter = buildFilter(listbox, unknown);
	DPRINTF("selectedFilter : %s", (const char*)selectedFilter.utf8());

	//	NOTE: This query might yield incorrect results if the optimization settings at compile time are too low. Probably a bug in SQLite...
	QString query = "SELECT DISTINCT " + colname + ", " + (selectedFilter.length() > 0 ? colname + " IN (" + selectedFilter + ")" : QString("0")) + 
					" AS selected FROM " + srctable + " ORDER BY " + colname + " ASC;";

	DPRINTF("query : %s", (const char*)query.utf8());
	
	beginUpdateOn(listbox);
	
	listbox->clearSelection();
	listbox->clear();
	
	sqlite3_stmt *vm;
	sqlite3_prepare(playList_->mediaDatabase()->db(), query.utf8(), -1, &vm, 0);

	int selcount = 0;
	while (sqlite3_step(vm) == SQLITE_ROW)
	{
		listbox->insertItem(QString::fromUtf8((const char *)sqlite3_column_text(vm, 0)));

		if (sqlite3_column_int(vm, 1))
		{
			listbox->setSelected(listbox->count() - 1, true);
			++selcount;
		}
		else
			listbox->setSelected(listbox->count() - 1, false);
	}
	sqlite3_finalize(vm);
	
	if (listbox->count() != 1)
		listbox->insertItem(more.arg(listbox->count()), 0);
	else
		listbox->insertItem(one, 0);

	if (listbox->count() > 1 && (listbox->text(1).isNull() || listbox->text(1).isEmpty()))
		listbox->changeItem(unknown, 1);
		
	if (selcount == 0)
		listbox->setSelected(0, true);
}

void PlayListOverview::updateGenres(bool interactive)
{
	DENTERMETHOD("PlayListOverview::updateGenres()");
	
	DTIMERINIT(time);
	
	doUpdateOnListBox(genreListBox_, "genre", "overview", tr("(Undefined genre)"), tr("All (1 genre)"), tr("All (%1 genres)"));
	
	DTIMERPRINT(time, "updateGenres");

	updateArtists(interactive);
	
	endUpdateOn(genreListBox_);
	
	DEXITMETHOD("PlayListOverview::updateGenres()");
}

void PlayListOverview::updateArtists(bool interactive)
{
	DENTERMETHOD("PlayListOverview::updateArtists()");
	
	DTIMERINIT(time);

	buildFilterAndCreateFilteredTable(selectedGenresFilter, tr("(Undefined genre)"), genreListBox_, "overview", "overview_genre_filtered", "artist, album", "genre");
	doUpdateOnListBox(artistListBox_, "artist", "overview_genre_filtered", tr("(Unknown artist)"), tr("All (1 artist)"), tr("All (%1 artists)"));
	
	DTIMERPRINT(time, "updateArtists");
	
	updateAlbums(interactive);
	
	endUpdateOn(artistListBox_);
	
	DEXITMETHOD("PlayListOverview::updateArtists()");
}

void PlayListOverview::updateAlbums(bool interactive)
{
	DENTERMETHOD("PlayListOverview::updateAlbums()");
	
	DTIMERINIT(time);

	buildFilterAndCreateFilteredTable(selectedArtistsFilter, tr("(Unknown artist)"), artistListBox_, "overview_genre_filtered", "overview_artist_filtered", "album", "artist");
	doUpdateOnListBox(albumListBox_, "album", "overview_artist_filtered", tr("(Unknown album)"), tr("All (1 album)"), tr("All (%1 albums)"));
	
	DTIMERPRINT(time, "updateAlbums");

	// if we're returning into the event loop because this should be interactive,
	// we need to finalize and re-prepare statements NOW, since the schema changed
	// and something may access those prepared statements in the meantime before updateView is
	// triggered by the timer...
	if (interactive)
		playList_->mediaDatabase()->ensurePreparedStatementsAreValid();

	// if we're entering updateView directly (i.e. non-interactively), the prepared statements will be taken care of there...		
	updateView(interactive);
	
	endUpdateOn(albumListBox_);
	
	DEXITMETHOD("PlayListOverview::updateAlbums()");	
}

void PlayListOverview::updateView(bool interactive)
{
	if (interactive)
	{
		if (!updateTimer_->isActive())
			updateTimer_->stop();

		updateTimer_->start(500, TRUE);
	}
	else
		updateView();
}

void PlayListOverview::updateView()
{
	DENTERMETHOD("PlayListOverview::updateView()");
	
	selectedAlbumsFilter = buildFilter(albumListBox_, tr("(Unknown album)"));

	if (selectedGenresFilter.length() > 0 || selectedArtistsFilter.length() > 0 || selectedAlbumsFilter.length() > 0)
	{
		QString query = 
			"DROP TABLE IF EXISTS playlist_view_overview_filtered;"
			"CREATE TEMPORARY TABLE playlist_view_overview_filtered AS "
				"SELECT * FROM playlist_view WHERE ";
	
		if (selectedGenresFilter.length() > 0)
		{
			query += "genre IN (" + selectedGenresFilter + ")";
			if (selectedArtistsFilter.length() > 0 || selectedAlbumsFilter.length() > 0)
				query += " AND ";
		}
		
		if (selectedArtistsFilter.length() > 0)
		{
			query += "artist IN (" + selectedArtistsFilter + ")";
			if (selectedAlbumsFilter.length() > 0)
				query += " AND ";
		}
		
		if (selectedAlbumsFilter.length() > 0)
			query += "album IN (" + selectedAlbumsFilter + ")";

		query += ";";

		DTIMERINIT(time);
		sqlite3_exec(playList_->mediaDatabase()->db(), query.utf8(), NULL, NULL, NULL);
		// the schema changed, so we need to finalize and re-prepare statements...
		playList_->mediaDatabase()->ensurePreparedStatementsAreValid();
		DTIMERPRINT(time, (const char*)query.utf8());
		
		playList_->setViewSource("playlist_view_overview_filtered");
	}
	else // no overview filtering...
	{
		QString query = "DROP TABLE IF EXISTS playlist_view_overview_filtered;";
		DTIMERINIT(time);
		sqlite3_exec(playList_->mediaDatabase()->db(), query.utf8(), NULL, NULL, NULL);
		// the schema changed, so we need to finalize and re-prepare statements...
		playList_->mediaDatabase()->ensurePreparedStatementsAreValid();
		DTIMERPRINT(time, (const char*)query.utf8());
				
		playList_->setViewSource("playlist_view");
	}
	
	DEXITMETHOD("PlayListOverview::updateView()");	
}

void PlayListOverview::validateSelection(QListBox *list)
{
	if (list->currentItem() == 0 && list->isSelected(0) || selectionCount(list) == 0)
	{
		list->clearSelection();
		list->setSelected(0, TRUE);
		list->setCurrentItem(0);
	}
	else
		list->setSelected(0, FALSE);
}

void PlayListOverview::genreListBoxSelectionChanged()
{
	DENTERMETHOD("PlayListOverview::genreListBoxSelectionChanged()");
	if (updatingGenres_) 
	{
		DEXITMETHOD("PlayListOverview::genreListBoxSelectionChanged()");
		return;
	}
	updatingGenres_ = true;
	validateSelection(genreListBox_);
	updateArtists(true);
	updatingGenres_ = false;
	DEXITMETHOD("PlayListOverview::genreListBoxSelectionChanged()");
}

void PlayListOverview::artistListBoxSelectionChanged()
{
	DENTERMETHOD("PlayListOverview::artistListBoxSelectionChanged()");
	if (updatingArtists_)
	{
		DEXITMETHOD("PlayListOverview::artistListBoxSelectionChanged()");
		return;
	}
	updatingArtists_ = true;
	validateSelection(artistListBox_);
	updateAlbums(true);
	updatingArtists_ = false;
	DEXITMETHOD("PlayListOverview::artistListBoxSelectionChanged()");
}

void PlayListOverview::albumListBoxSelectionChanged()
{
	DENTERMETHOD("PlayListOverview::albumListBoxSelectionChanged()");
	if (updatingAlbums_)
	{
		DEXITMETHOD("PlayListOverview::albumListBoxSelectionChanged()");
		return;
	}
	updatingAlbums_ = true;
	validateSelection(albumListBox_);
	updateView(true);
	updatingAlbums_ = false;
	DEXITMETHOD("PlayListOverview::albumListBoxSelectionChanged()");
}

void PlayListOverview::resetFilter()
{
	albumListBox_->selectAll(false);
	artistListBox_->selectAll(false);
	genreListBox_->selectAll(false);
}
