// Copyright (C) 2002 Neil Stevens <neil@qualityassistant.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// THE AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
// AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// 
// Except as contained in this notice, the name(s) of the author(s) shall not be
// used in advertising or otherwise to promote the sale, use or other dealings
// in this Software without prior written authorization from the author(s).

#include <kapplication.h>
#include <kdebug.h>
#include <kfilemetainfo.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kmimetype.h>
#include <ksimpleconfig.h>
#include <kstyle.h>
#include <noatun/app.h>
#include <qcursor.h>
#include <qheader.h>
#include <qpainter.h>
#include <qregexp.h>

#include "branch.h"
#include "cmodule.h"

//////////////////
// FileTreeView //
//////////////////

Hayes::FileTreeView::FileTreeView(QWidget *parent, const char *name)
	: KFileTreeView(parent, name)
	, cache(10, 17, true)
	, mimetypeCache(QString::null)
	, sortDirection(Ascending)
	, special(0)
{
	cache.setAutoDelete(true);

	addColumn(i18n("File"));
	addColumn(i18n("Title"));
	addColumn(i18n("Length"));
	addColumn(i18n("Artist"));
	addColumn(i18n("Album"));
	addColumn(i18n("Date"));
	addColumn(i18n("Comment"));
	addColumn(i18n("Track"));
	// the enabled column, make it last
	addColumn("", 16);
	// don't allow resize
	int column = header()->count() - 1;
	setColumnWidth(column, FileTreeViewItem::checkBoxWidth());
	setColumnWidthMode(column, QListView::Maximum);
	header()->setResizeEnabled(false, column);
	// and display it first
	header()->moveSection(header()->count() - 1, 0);
	header()->setClickEnabled(false, column);
	header()->update();
	triggerUpdate();

	setAllColumnsShowFocus(true);

	connect(header(), SIGNAL(indexChange(int, int, int)), this, SLOT(headerIndexChange(int, int, int)));
	// If changeSortColumn were protected and virtual I'd not have to do this
	disconnect(header(), SIGNAL(sectionClicked(int)));
	connect(header(), SIGNAL(sectionClicked(int)), this, SLOT(myChangeSortColumn(int)));
	connect(this, SIGNAL(moved(QListViewItem *, QListViewItem *, QListViewItem *)),
	        this, SLOT(handleMove(QListViewItem *, QListViewItem *, QListViewItem *)));
	connect(this, SIGNAL(aboutToMove(void)), this, SLOT(customSort(void)));
}

void Hayes::FileTreeView::superSaveLayout(KConfig *config, const QString &group)
{
	saveLayout(config, group);
	config->setGroup(group);
	config->writeEntry("HayesSort", (int)sortDirection);
}

void Hayes::FileTreeView::superRestoreLayout(KConfig *config, const QString &group)
{
	restoreLayout(config, group);
	config->setGroup(group);
	sortDirection = (Sorting)config->readNumEntry("HayesSort", (int)Ascending);
	applySorting(columnSorted());
}

namespace
{
void hideColumn(KListView *listView, int column)
{
	listView->setColumnWidthMode(column, QListView::Manual);
	listView->setColumnWidth(column, 0);
	listView->header()->setResizeEnabled(false, column);
}

void showColumn(KListView *listView, int column)
{
	listView->header()->setResizeEnabled(true, column);
	listView->setColumnWidthMode(column, QListView::Maximum);
	if(listView->columnWidth(column) < 50)
		listView->setColumnWidth(column, 50);
}
}

void Hayes::FileTreeView::setColumns(int columns)
{
	for(int i = 1; i <= maxColumn; ++i)
	{
		if(columns & (1 << (i - 1)))
			showColumn(this, i);
		else
			hideColumn(this, i);
	}
	// don't allow resize of checkbox column
	int column = header()->count() - 1;
	header()->setResizeEnabled(false, column);
}

bool Hayes::FileTreeView::acceptDrag(QDropEvent *e) const
{
	return KListView::acceptDrag(e);
}

QDragObject *Hayes::FileTreeView::dragObject(void)
{
	return KListView::dragObject();
}

void Hayes::FileTreeView::contentsDragEnterEvent(QDragEnterEvent *e)
{
	KListView::contentsDragEnterEvent(e);
}

void Hayes::FileTreeView::contentsDragMoveEvent(QDragMoveEvent *e)
{
	KListView::contentsDragMoveEvent(e);
}

void Hayes::FileTreeView::contentsDragLeaveEvent(QDragLeaveEvent *e)
{
	KListView::contentsDragLeaveEvent(e);
}

void Hayes::FileTreeView::contentsDropEvent(QDropEvent *e)
{
	KListView::contentsDropEvent(e);
}

void Hayes::FileTreeView::headerIndexChange(int col, int, int to)
{
	const int enabledColumn = header()->count() - 1;
	// don't let the user move the enabled column
	if((col == enabledColumn && to != 0) ||
	// don't let the user move any other column to the enabled column's index
	   (col != enabledColumn && to == 0))
	{
		header()->moveSection(enabledColumn, 0);
		header()->update();
		triggerUpdate();
	}
}

void Hayes::FileTreeView::myChangeSortColumn(int col)
{
	int column = header()->mapToLogical(col);

	// sequence of clicks is ascending, descending, custom
	if(columnSorted() != column || sortDirection == Custom)
		sortDirection = Ascending;
	else if(sortDirection == Ascending)
		sortDirection = Descending;
	else
		sortDirection = Custom;

	applySorting(column);
}

void Hayes::FileTreeView::applySorting(int column)
{
	const int enabledColumn = header()->count() - 1;
	bool draggydraggy = false;

	switch(sortDirection)
	{
	case Ascending:
		setSorting(column);
		header()->setSortIndicator(column, true);
		break;
	case Descending:
		if(firstChild()) firstChild()->sort();
		header()->setSortIndicator(column, false);
		break;
	case Custom:
		setSorting(enabledColumn);
		header()->setSortIndicator(-1);
		draggydraggy = true;
		break;
	}

	setItemsMovable(draggydraggy);
	setDragEnabled(draggydraggy);
	setAcceptDrops(draggydraggy);
}

namespace
{
QString getDirectoryFileForURL(KURL url)
{
	url.setFileName(".directory");
	return url.path();
}
}

KSimpleConfig &Hayes::FileTreeView::directoryCache(KURL url)
{
	QString key = getDirectoryFileForURL(url);
	KSimpleConfig *c = cache.find(key);
	if(c) return *c;

	c = new KSimpleConfig(key);
	cache.insert(key, c);
	//kdDebug(66666) << "directory cache: " << key << " create " << (void *)c << endl;
	return *c;
}

QString Hayes::FileTreeView::mimetypes(void)
{
	if(mimetypeCache.isNull())
	{
		mimetypeCache = napp->mimeTypes();
		if(mimetypeCache.isNull()) mimetypeCache = " ";
	}
	return mimetypeCache;
}

void Hayes::FileTreeView::handleMove(QListViewItem *qitem, QListViewItem *, QListViewItem *qafterNow)
{
	// regenerate the X-Sorting for the parent
	FileTreeViewItem *item = static_cast<FileTreeViewItem *>(qitem);
	FileTreeViewItem *afterNow = static_cast<FileTreeViewItem *>(qafterNow);
	FileTreeViewItem *parentItem = static_cast<FileTreeViewItem *>(item->parent());
	kdDebug(66666) << "Putting " << item->url().prettyURL() << " after " << afterNow->url().prettyURL() << endl;

	KSimpleConfig &directory = directoryCache(item->fileItem()->url());
	directory.setGroup("X-Sorting");
	int count = 0;
	for(QListViewItem *i = parentItem->firstChild(); i; i = i->nextSibling())
	{
		if(i != item)
		{
			const KURL url = static_cast<FileTreeViewItem *>(i)->fileItem()->url();
			//kdDebug(66666) << "The " << url.fileName() << "-bone (" << count << ") is connected to the..." << endl;
			directory.writeEntry(url.fileName(), QString::number(count++));
		}

		if(i == afterNow)
		{
			const KURL url = item->fileItem()->url();
			//kdDebug(66666) << "The " << url.fileName() << "-bone (" << count << ") is connected to the..." << endl;
			directory.writeEntry(url.fileName(), QString::number(count++));
		}
	}
	directory.sync();
	parentItem->sort();
}

void Hayes::FileTreeView::movableDropEvent(QListViewItem *parent, QListViewItem *afterMe)
{
	// don't allow cross-directory drops
	FileTreeViewItem *item = static_cast<FileTreeViewItem *>(currentItem());
	if(parent == item->parent())
	{
		kdDebug(66666) << "Ready to roll: moving " << item->url().prettyURL() << endl;
		KListView::movableDropEvent(parent, afterMe);
	}
	else
	{
		// hmm... never before got to use this in a kdDebug.
		kdDebug(66666) << "bzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzt." << endl;
	}
}

void Hayes::FileTreeView::customSort(void)
{
	kdDebug(66666) << "There has been a great disturbance in the force.  Like a million FileTreeViewItems were dragged, and then suddenly dropped." << endl;
}

void Hayes::FileTreeView::setSpecialItem(FileTreeViewItem *item)
{
	if(special) special->setSpecial(false);

	if(!item || item->listView() != this)
		special = 0;
	else
		special = item;

	if(special) special->setSpecial(true);

	updateContents();
}

Hayes::FileTreeViewItem *Hayes::FileTreeView::specialItem(void)
{
	return special;
}

void Hayes::FileTreeView::clear(void)
{
	special = 0;
	KFileTreeView::clear();
}

void Hayes::FileTreeView::takeItem(QListViewItem *item)
{
	if(item == special) special = 0;
	KFileTreeView::takeItem(item);
}

//////////////////////
// FileTreeViewItem //
//////////////////////

Hayes::FileTreeViewItem::FileTreeViewItem(FileTreeView *parent, KFileItem *item, Branch *branch)
	: KFileTreeViewItem(parent, item, branch)
	, special(false)
	, on(true)
	, check(false)
{
}

Hayes::FileTreeViewItem::FileTreeViewItem(FileTreeViewItem *parent, KFileItem *item, Branch *branch)
	: KFileTreeViewItem(parent, item, branch)
	, special(false)
	, check(true)
{
	// only allow the user to enable items that aRts can support
	if(supported())
	{
		KSimpleConfig &directory = static_cast<FileTreeView *>(listView())->directoryCache(item->url());
		directory.setGroup("X-Playlist");
		on = directory.readBoolEntry(item->url().fileName(), true);
	}
	else
	{
		on = false;
	}
}

int Hayes::FileTreeViewItem::width(const QFontMetrics &metrics, const QListView *view, int col) const
{
	// do check item
	if(listView()->header()->mapToIndex(col) == 0)
	{
		return checkBoxWidth();
	}
	else
	{
		return KFileTreeViewItem::width(metrics, view, col);
	}
}

int Hayes::FileTreeViewItem::checkBoxWidth(void)
{
	QStyle &style = kapp->style();
	static const int totalMargin = 10;
	return style.pixelMetric(QStyle::PM_IndicatorWidth) + totalMargin;
}

void Hayes::FileTreeViewItem::setSpecial(bool b)
{
	if (special != b)
	{
		if (special = b)
			setPixmap(0, QPixmap(SmallIcon("noatunplay")));
		else
			setPixmap(0, KMimeType::mimeType(fileItem()->mimetype())->pixmap((KIcon::Small)));
	}
}

bool Hayes::FileTreeViewItem::isSpecial(void) const
{
	return special;
}

void Hayes::FileTreeViewItem::setOn(bool b)
{
	on = b;

	KSimpleConfig &directory = static_cast<FileTreeView *>(listView())->directoryCache(fileItem()->url());
	directory.setGroup("X-Playlist");
	// write entry if disabled, clear if enabled
	// no sense bloating the .directory files
	if(on)
		directory.deleteEntry(fileItem()->url().fileName());
	else
		directory.writeEntry(fileItem()->url().fileName(), on);
	directory.sync();

	listView()->triggerUpdate();
}

bool Hayes::FileTreeViewItem::isOn(void) const
{
	return on;
}

void Hayes::FileTreeViewItem::toggle(void)
{
	setOn(!on);
}

void Hayes::FileTreeViewItem::setVolume(int v)
{
	KSimpleConfig &directory = static_cast<FileTreeView *>(listView())->directoryCache(fileItem()->url());
	directory.setGroup("X-Playlist-Volume");
	// write entry if < 100, clear if 100
	// no sense bloating the .directory files
	if(v == 100)
		directory.deleteEntry(fileItem()->url().fileName());
	else
		directory.writeEntry(fileItem()->url().fileName(), v);
	directory.sync();
}

bool Hayes::FileTreeViewItem::hasVolume(void) const
{
	KSimpleConfig &directory = static_cast<FileTreeView *>(listView())->directoryCache(fileItem()->url());
	directory.setGroup("X-Playlist-Volume");
	return directory.hasKey(fileItem()->url().fileName());
}

int Hayes::FileTreeViewItem::volume(void) const
{
	KSimpleConfig &directory = static_cast<FileTreeView *>(listView())->directoryCache(fileItem()->url());
	directory.setGroup("X-Playlist-Volume");
	return directory.readNumEntry(fileItem()->url().fileName(), 100);
}

QString Hayes::FileTreeViewItem::key(int col, bool) const
{
	FileTreeView *tree = static_cast<FileTreeView *>(listView());
	QString t;

	// in custom mode don't use the text, but the .directory Sorting key instead
	if(tree->sorting() == FileTreeView::Custom)
	{
		KSimpleConfig &directory = static_cast<FileTreeView *>(listView())->directoryCache(fileItem()->url());
		directory.setGroup("X-Sorting");
		t = directory.readEntry(fileItem()->url().fileName(), fileItem()->url().fileName());
	}
	else
	{
		t = text(col);
	}

	QRegExp re("^(\\d*)(.*)");
	re.search(t);
	QString numberPart = *re.capturedTexts().at(1);
	QString otherPart = *re.capturedTexts().at(2);

	// if it starts with a number pad the number with zeros
	if(!numberPart.isEmpty()) otherPart.prepend(numberPart.rightJustify(500, '0'));

	// and get a case-insensitive sort
	return otherPart.lower();
}

bool Hayes::FileTreeViewItem::supported(void) const
{
	// TODO: disable if the whole dir is unusable?
	// would require opening the dir, so probably not.
	return isDir() || static_cast<FileTreeView *>(listView())->mimetypes().contains(fileItem()->mimetype());
}

namespace
{
kndbgstream & operator <<(kndbgstream &s, const QRect &)
{
	return s;
}

kndbgstream & operator <<(kndbgstream &s, const QPoint &)
{
	return s;
}

kdbgstream & operator <<(kdbgstream &s, const QRect &r)
{
	return s << QString("(%1, %2, %3, %4)").arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height());
}

kdbgstream & operator <<(kdbgstream &s, const QPoint &p)
{
	return s << QString("(%1, %2)").arg(p.x()).arg(p.y());
}
}

void Hayes::FileTreeViewItem::paintCell(QPainter *painter, const QColorGroup &group, int column, int width, int alignment)
{
	const bool checkBoxCell = listView()->header()->mapToIndex(column) == 0;

	KFileTreeViewItem::paintCell(painter, group, column, width, alignment);

	QStyle &style = kapp->style();
	// do check item
	if(check && checkBoxCell && supported())
	{
		// oh, though I badly would like for Hayes to expose the broken
		// QStyle API by using drawControl, I don't think it'll be very
		// effective.  Another way must be used instead.
		int s = (int)QStyle::Style_Enabled;
		s |= on ? QStyle::Style_On : QStyle::Style_Off;
		// Is there even a guarantee that this will even look like a check box widget?
		// Or will it miss finishing code that is done in drawControl?
		style.drawPrimitive(QStyle::PE_Indicator, painter, checkBoxRect(), group, s);
	}
}

void Hayes::FileTreeViewItem::activate(void)
{
	// fakePos is so horked up as to be unusable
	QPoint pos = listView()->mapFromGlobal(QCursor::pos()) -
	             listView()->itemRect(this).topLeft();
	pos.rx() -= listView()->itemMargin();
	pos.ry() -= listView()->header()->height();

	kdDebug(66666) << "checkBoxRect: " << checkBoxRect()
	               << " checkBoxCellRect: " << checkBoxCellRect()
	               << " pos " << pos << endl;

	QPoint fakePos;
	bool activated = activatedPos(fakePos);

	if(!activated || !checkBoxCellRect().contains(pos))
		KFileTreeViewItem::activate();
	else if(check && checkBoxRect().contains(pos))
		toggle();
}

QRect Hayes::FileTreeViewItem::checkBoxRect(void) const
{
	QStyle &style = kapp->style();
	const int boxW = style.pixelMetric(QStyle::PM_IndicatorWidth);
	const int boxH = style.pixelMetric(QStyle::PM_IndicatorHeight);
	const int cellW = listView()->header()->sectionRect(listView()->header()->mapToSection(0)).width();
	const int cellH = height();

	int x, y, w, h;
	if(boxW < cellW)
	{
		x = (cellW - boxW) / 2;
		w = boxW;
	}
	else
	{
		x = 0;
		w = cellW;
	}

	if(boxH < cellH)
	{
		y = (cellH - boxH) / 2;
		h = boxH;
	}
	else
	{
		y = 0;
		h = cellH;
	}

	return QRect(x, y, w, h);
}

QRect Hayes::FileTreeViewItem::checkBoxCellRect(void) const
{
	const int w = listView()->header()->sectionRect(listView()->header()->mapToSection(0)).width();
	const int h = height();

	return QRect(0, 0, w, h);
}

////////////
// Branch //
////////////

Hayes::Branch::Branch(FileTreeView *view, const KURL &url, const QString &name)
	: KFileTreeBranch(view, url, name,
	                  KMimeType::mimeType("inode/directory")->pixmap(KIcon::Small),
	                  true, new FileTreeViewItem(view,
	                                             new KFileItem(url, "inode/directory", S_IFDIR),
	                                             this))
{
	// If you're going to mangle your files, you have to take your lumps
	setChildRecurse(false);
	setShowExtensions(true);
	connect(this, SIGNAL(refreshItems(const KFileItemList &)), this, SLOT(refresh(const KFileItemList &)));
}

KFileTreeViewItem *Hayes::Branch::createTreeViewItem(KFileTreeViewItem *parent, KFileItem *fileItem)
{
	if(fileItem->url().fileName() == QString::fromLatin1(".directory")) return 0;

	FileTreeViewItem *newItem = new FileTreeViewItem(static_cast<FileTreeViewItem *>(parent), fileItem, this);
	refresh(fileItem, newItem);
	return newItem;
}

void Hayes::Branch::refresh(const KFileItemList &items)
{
	for(KFileItemListIterator i(items); *i; ++i)
	{
		refresh(*i, static_cast<FileTreeViewItem *>(findTVIByURL((*i)->url())));
	}
}

void Hayes::Branch::refresh(KFileItem *fileItem, FileTreeViewItem *viewItem)
{
	viewItem->setText(0, fileItem->url().fileName());
	viewItem->setPixmap(0, (*fileItem->mimeTypePtr()).pixmap(KIcon::Small));
	if(!fileItem->metaInfo().isValid())
	{
		KFileMetaInfo i(fileItem->url().path(), QString::null, KFileMetaInfo::Everything);
		fileItem->setMetaInfo(i);
	}
	const KFileMetaInfo &info = fileItem->metaInfo();
	if(info.isValid())
	{
		// title, length, artist, album, date, comment
		if(info.contains("Title")) viewItem->setText(1, info.value("Title").toString());
		if(info.contains("Length"))
		{
			const int length = info.value("Length").toInt();
			const QString seconds = QString::number(length % 60).rightJustify(2, '0');
			const int minutes = (length - (length % 60)) / 60;
			QString text("%1:%2");
			viewItem->setText(2, text.arg(minutes).arg(seconds));
		}
		if(info.contains("Artist")) viewItem->setText(3, info.value("Artist").toString());
		if(info.contains("Album")) viewItem->setText(4, info.value("Album").toString());
		if(info.contains("Date")) viewItem->setText(5, info.value("Date").toString());
		if(info.contains("Comment")) viewItem->setText(6, info.value("Comment").toString());
		if(info.contains("Tracknumber")) viewItem->setText(7, info.value("Tracknumber").toString());
	}
}

#include "branch.moc"
