/***************************************************************************
    begin                : Fri Sep 13 2002
    copyright            : (C) 2002 - 2004 by Scott Wheeler
    email                : wheeler@kde.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <kurldrag.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kdebug.h>
#include <tdepopupmenu.h>
#include <kiconloader.h>
#include <tdeconfig.h>
#include <tdeaction.h>
#include <kurl.h>

#include "collectionlist.h"
#include "playlistcollection.h"
#include "splashscreen.h"
#include "stringshare.h"
#include "cache.h"
#include "actioncollection.h"
#include "tag.h"
#include "viewmode.h"
#include "coverinfo.h"
#include "covermanager.h"

using namespace ActionCollection;

////////////////////////////////////////////////////////////////////////////////
// static methods
////////////////////////////////////////////////////////////////////////////////

CollectionList *CollectionList::m_list = 0;

CollectionList *CollectionList::instance()
{
    return m_list;
}

void CollectionList::initialize(PlaylistCollection *collection)
{
    if(m_list)
	return;

    // We have to delay initilaization here because dynamic_cast or comparing to
    // the collection instance won't work in the PlaylistBox::Item initialization
    // won't work until the CollectionList is fully constructed.

    m_list = new CollectionList(collection);
    m_list->setName(i18n("Collection List"));

    FileHandleHash::Iterator end = Cache::instance()->end();
    for(FileHandleHash::Iterator it = Cache::instance()->begin(); it != end; ++it)
	new CollectionListItem(*it);

    SplashScreen::update();

    // The CollectionList is created with sorting disabled for speed.  Re-enable
    // it here, and perform the sort.
    TDEConfigGroup config(TDEGlobal::config(), "Playlists");
    
    SortOrder order = Descending;
    if(config.readBoolEntry("CollectionListSortAscending", true))
	order = Ascending;

    m_list->setSortOrder(order);
    m_list->setSortColumn(config.readNumEntry("CollectionListSortColumn", 1));

    m_list->sort();

    collection->setupPlaylist(m_list, "folder_sound");
}

////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////

PlaylistItem *CollectionList::createItem(const FileHandle &file, TQListViewItem *, bool)
{
    // It's probably possible to optimize the line below away, but, well, right
    // now it's more important to not load duplicate items.

    if(m_itemsDict.find(file.absFilePath()))
	return 0;

    PlaylistItem *item = new CollectionListItem(file);
    
    if(!item->isValid()) {
	kdError() << "CollectionList::createItem() -- A valid tag was not created for \""
		  << file.absFilePath() << "\"" << endl;
	delete item;
	return 0;
    }

    setupItem(item);

    return item;
}

void CollectionList::clearItems(const PlaylistItemList &items)
{
    for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it) {
	Cache::instance()->remove((*it)->file());
	clearItem(*it, false);
    }

    dataChanged();
}

void CollectionList::setupTreeViewEntries(ViewMode *viewMode) const
{
    TreeViewMode *treeViewMode = dynamic_cast<TreeViewMode *>(viewMode);
    if(!treeViewMode) {
	kdWarning(65432) << "Can't setup entries on a non-tree-view mode!\n";
	return;
    }

    TQValueList<int> columnList;
    columnList << PlaylistItem::ArtistColumn;
    columnList << PlaylistItem::GenreColumn;
    columnList << PlaylistItem::AlbumColumn;

    TQStringList items;
    for(TQValueList<int>::Iterator colIt = columnList.begin(); colIt != columnList.end(); ++colIt) {
	items.clear();
	for(TagCountDictIterator it(*m_columnTags[*colIt]); it.current(); ++it)
	    items << it.currentKey();

	treeViewMode->addItems(items, *colIt);
    }
}

void CollectionList::slotNewItems(const KFileItemList &items)
{
    TQStringList files;

    for(KFileItemListIterator it(items); it.current(); ++it)
	files.append((*it)->url().path());

    addFiles(files);
    update();
}

void CollectionList::slotRefreshItems(const KFileItemList &items)
{
    for(KFileItemListIterator it(items); it.current(); ++it) {
	CollectionListItem *item = lookup((*it)->url().path());

	if(item) {
	    item->refreshFromDisk();

	    // If the item is no longer on disk, remove it from the collection.

	    if(item->file().fileInfo().exists())
		item->repaint();
	    else
		clearItem(item);
	}
    }

    update();
}

void CollectionList::slotDeleteItem(KFileItem *item)
{
    CollectionListItem *listItem = lookup(item->url().path());
    if(listItem)
	clearItem(listItem);
}

////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

void CollectionList::clear()
{
    int result = KMessageBox::warningContinueCancel(this, 
	i18n("Removing an item from the collection will also remove it from "
	     "all of your playlists. Are you sure you want to continue?\n\n"
	     "Note, however, that if the directory that these files are in is in "
	     "your \"scan on startup\" list, they will be readded on startup."));

    if(result == KMessageBox::Continue) {
	Playlist::clear();
	emit signalCollectionChanged();
    }
}

void CollectionList::slotCheckCache()
{
    PlaylistItemList invalidItems;

    for(TQDictIterator<CollectionListItem>it(m_itemsDict); it.current(); ++it) {
	if(!it.current()->checkCurrent())
	    invalidItems.append(*it);
	processEvents();
    }

    clearItems(invalidItems);
}

void CollectionList::slotRemoveItem(const TQString &file)
{
    clearItem(m_itemsDict[file]);
}

void CollectionList::slotRefreshItem(const TQString &file)
{
    m_itemsDict[file]->refresh();
}

////////////////////////////////////////////////////////////////////////////////
// protected methods
////////////////////////////////////////////////////////////////////////////////

CollectionList::CollectionList(PlaylistCollection *collection) :
    Playlist(collection, true),
    m_itemsDict(5003),
    m_columnTags(15, 0)
{
    new TDEAction(i18n("Show Playing"), TDEShortcut(), ActionCollection::actions(), "showPlaying");

    connect(action("showPlaying"), TQ_SIGNAL(activated()), this, TQ_SLOT(slotShowPlaying()));

    connect(action<TDEToolBarPopupAction>("back")->popupMenu(), TQ_SIGNAL(aboutToShow()),
	    this, TQ_SLOT(slotPopulateBackMenu()));
    connect(action<TDEToolBarPopupAction>("back")->popupMenu(), TQ_SIGNAL(activated(int)),
	    this, TQ_SLOT(slotPlayFromBackMenu(int)));
    setSorting(-1); // Temporarily disable sorting to add items faster.

    m_columnTags[PlaylistItem::ArtistColumn] = new TagCountDict(5001, false);
    m_columnTags[PlaylistItem::ArtistColumn]->setAutoDelete(true);

    m_columnTags[PlaylistItem::AlbumColumn] = new TagCountDict(5001, false);
    m_columnTags[PlaylistItem::AlbumColumn]->setAutoDelete(true);

    m_columnTags[PlaylistItem::GenreColumn] = new TagCountDict(5001, false);
    m_columnTags[PlaylistItem::GenreColumn]->setAutoDelete(true);

    polish();
}

CollectionList::~CollectionList()
{
    TDEConfigGroup config(TDEGlobal::config(), "Playlists");
    config.writeEntry("CollectionListSortColumn", sortColumn());
    config.writeEntry("CollectionListSortAscending", sortOrder() == Ascending);

    // The CollectionListItems will try to remove themselves from the
    // m_columnTags member, so we must make sure they're gone before we
    // are.

    clearItems(items());
    for(TagCountDicts::Iterator it = m_columnTags.begin(); it != m_columnTags.end(); ++it)
	delete *it;
}

void CollectionList::contentsDropEvent(TQDropEvent *e)
{
    if(e->source() == this)
	return; // Don't rearrange in the CollectionList.
    else 
	Playlist::contentsDropEvent(e);
}

void CollectionList::contentsDragMoveEvent(TQDragMoveEvent *e)
{
    if(canDecode(e) && e->source() != this)
	e->accept(true);
    else
	e->accept(false);
}

TQString CollectionList::addStringToDict(const TQString &value, unsigned column)
{
    if(column > m_columnTags.count() || value.stripWhiteSpace().isEmpty())
	return TQString();

    int *refCountPtr = m_columnTags[column]->find(value);
    if(refCountPtr)
	++(*refCountPtr);
    else {
	m_columnTags[column]->insert(value, new int(1));
	emit signalNewTag(value, column);
    }

    return value;
}

TQStringList CollectionList::uniqueSet(UniqueSetType t) const
{
    int column;

    switch(t)
    {
    case Artists:
        column = PlaylistItem::ArtistColumn;
    break;
    
    case Albums:
        column = PlaylistItem::AlbumColumn;
    break;
    
    case Genres:
        column = PlaylistItem::GenreColumn;
    break;

    default:
	return TQStringList();
    }

    if((unsigned) column >= m_columnTags.count())
	return TQStringList();

    TagCountDictIterator it(*m_columnTags[column]);
    TQStringList list;

    for(; it.current(); ++it)
	list += it.currentKey();

    return list;
}

void CollectionList::removeStringFromDict(const TQString &value, unsigned column)
{
    if(column > m_columnTags.count() || value.isEmpty())
	return;

    int *refCountPtr = m_columnTags[column]->find(value);
    if(refCountPtr) {
	--(*refCountPtr);
	if(*refCountPtr == 0) {
	    emit signalRemovedTag(value, column);
	    m_columnTags[column]->remove(value);
	}
    }
}

////////////////////////////////////////////////////////////////////////////////
// CollectionListItem public methods
////////////////////////////////////////////////////////////////////////////////

void CollectionListItem::refresh()
{
    int offset = static_cast<Playlist *>(listView())->columnOffset();
    int columns = lastColumn() + offset + 1;

    data()->local8Bit.resize(columns);
    data()->cachedWidths.resize(columns);

    for(int i = offset; i < columns; i++) {
	int id = i - offset;
	if(id != TrackNumberColumn && id != LengthColumn) {        
	    // All columns other than track num and length need local-encoded data for sorting        

	    TQCString lower = text(i).lower().local8Bit();

	    // For some columns, we may be able to share some strings

	    if((id == ArtistColumn) || (id == AlbumColumn) ||
	       (id == GenreColumn)  || (id == YearColumn)  ||
	       (id == CommentColumn))
	    {
		lower = StringShare::tryShare(lower);

		if(id != YearColumn && id != CommentColumn && data()->local8Bit[id] != lower) {
		    CollectionList::instance()->removeStringFromDict(data()->local8Bit[id], id);
		    CollectionList::instance()->addStringToDict(text(i), id);
		}
	    }

	    data()->local8Bit[id] = lower;
	}

	int newWidth = width(listView()->fontMetrics(), listView(), i);
	data()->cachedWidths[i] = newWidth;

	if(newWidth != data()->cachedWidths[i])
	    playlist()->slotWeightDirty(i);
    }

    file().coverInfo()->setCover();

    if(listView()->isVisible())
	repaint();

    for(PlaylistItemList::Iterator it = m_children.begin(); it != m_children.end(); ++it) {
	(*it)->playlist()->update();
	(*it)->playlist()->dataChanged();
	if((*it)->listView()->isVisible())
	    (*it)->repaint();
    }

    CollectionList::instance()->dataChanged();
    emit CollectionList::instance()->signalCollectionChanged();
}

PlaylistItem *CollectionListItem::itemForPlaylist(const Playlist *playlist)
{
    if(playlist == CollectionList::instance())
	return this;

    PlaylistItemList::ConstIterator it;
    for(it = m_children.begin(); it != m_children.end(); ++it)
	if((*it)->playlist() == playlist)
	    return *it;
    return 0;
}

void CollectionListItem::updateCollectionDict(const TQString &oldPath, const TQString &newPath)
{
    CollectionList *collection = CollectionList::instance();

    if(!collection)
	return;

    collection->removeFromDict(oldPath);
    collection->addToDict(newPath, this);
}

void CollectionListItem::repaint() const
{
    TQListViewItem::repaint();
    for(PlaylistItemList::ConstIterator it = m_children.begin(); it != m_children.end(); ++it)
	(*it)->repaint();
}

////////////////////////////////////////////////////////////////////////////////
// CollectionListItem protected methods
////////////////////////////////////////////////////////////////////////////////

CollectionListItem::CollectionListItem(const FileHandle &file) :
    PlaylistItem(CollectionList::instance()),
    m_shuttingDown(false)
{
    CollectionList *l = CollectionList::instance();
    if(l) {
	l->addToDict(file.absFilePath(), this);

	data()->fileHandle = file;

	if(file.tag()) {
	    refresh();
	    l->dataChanged();
	    // l->addWatched(m_path);
	}
	else
	    kdError() << "CollectionListItem::CollectionListItem() -- Tag() could not be created." << endl;
    }
    else
	kdError(65432) << "CollectionListItems should not be created before "
		       << "CollectionList::initialize() has been called." << endl;

    SplashScreen::increment();
}

CollectionListItem::~CollectionListItem()
{
    m_shuttingDown = true;

    for(PlaylistItemList::ConstIterator it = m_children.begin();
	it != m_children.end();
	++it)
    {
        (*it)->playlist()->clearItem(*it);
    }

    CollectionList *l = CollectionList::instance();
    if(l) {
	l->removeFromDict(file().absFilePath());
	l->removeStringFromDict(file().tag()->album(), AlbumColumn);
	l->removeStringFromDict(file().tag()->artist(), ArtistColumn);
	l->removeStringFromDict(file().tag()->genre(), GenreColumn);
    }
}

void CollectionListItem::addChildItem(PlaylistItem *child)
{
    m_children.append(child);
}

void CollectionListItem::removeChildItem(PlaylistItem *child)
{
    if(!m_shuttingDown)
	m_children.remove(child);
}

bool CollectionListItem::checkCurrent()
{
    if(!file().fileInfo().exists() || !file().fileInfo().isFile())
	return false;

    if(!file().current()) {
	file().refresh();
	refresh();
    }

    return true;
}

#include "collectionlist.moc"
