/***************************************************************************
                          AudioCompilationTreeView.cpp  -  description
                             -------------------
    begin                : Sun Nov 18 2001
    copyright            : (C) 2001 by Alex Podolsky
    email                : alexpod@users.sf.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.                                   *
 *                                                                         *
 ***************************************************************************/
#include <iostream.h>
// QT includes
#include <qdir.h>
#include <qstringlist.h>
#include <qcheckbox.h>
#include <qpopupmenu.h>
#include <qpushbutton.h>
#include <qclipboard.h>
#include <qapp.h>
#include <qfileinfo.h>
#include <qdragobject.h>
#include <qregexp.h>
#include <qevent.h>
#include <qheader.h>

// KDE includes
#include <kurl.h>
#include <kiconloader.h>
#include <klocale.h>
//#include <kconfig.h>
#include <kapplication.h>
#include <kio/job.h>
#include <kpropsdlg.h>
#include <kopenwith.h>
#include <kio/netaccess.h>
#include <kio/jobclasses.h>
#include <kmessagebox.h>
#include <klocale.h>
#include <klistview.h>
#include <kmimemagic.h>
#include <kdirsize.h>
#include <kfiledialog.h>
#include <kmimetype.h>
#include <kfilemetainfo.h>

// CDBO includes
#include "AudioCompilationTreeView.h"
#include "AudioCompilationTreeView.moc"
#include "CompilationTreeView.h"
#include "AudioItem.h"
#include "../AudioEstimatePnl.h"
#include "../../CdboCompilationView.h"

AudioCompilationTreeView::AudioCompilationTreeView( QWidget *parent, const char *name) :
		KListView(parent, name){

	setAcceptDrops(true);
	setDropVisualizer(true);
	//setItemsMovable(true);
	setSelectionMode(QListView::Extended);
	
	addColumn(  i18n("Track")  );
	header()->setClickEnabled( false, header()->count() - 1 );
	addColumn(   i18n("Title")  );
	header()->setClickEnabled( false, header()->count() - 1 );
	addColumn(   i18n("Artist") );
	header()->setClickEnabled( false, header()->count() - 1 );
	addColumn(   i18n("Length") );
	header()->setClickEnabled( false, header()->count() - 1 );
	addColumn(   i18n("File Type") );
	header()->setClickEnabled( false, header()->count() - 1 );
	addColumn(   i18n("Location") );
	header()->setClickEnabled( false, header()->count() - 1 );

    	
    	//setColumnAlignment(1, AlignRight);
    	setColumnAlignment(3, AlignHCenter);
    	
	setColumnWidth(0,45);
    	setColumnWidth(1,190);
    	setColumnWidth(2,100);
    	setColumnWidth(3,60);
	setColumnWidth(4,100);
    	setColumnWidth(5,320);
	totJobs = 0;
	multy = false;
	setAllColumnsShowFocus(true);
	setSorting(0);
	
	setItemsRenameable(true);
	//setRenameable(0, false);
	//setRenameable(1, true);
	//setRenameable(2, true);

	updateSettings();
	
	////////////////////////////////////////////////////////////////////////////
	fullMenu = new QPopupMenu();
	fullMenu -> setCheckable(true);
	
	fullMenu -> insertItem( i18n("Track Properties"),   this ,SLOT(slotProperties()));
	fullMenu -> insertItem( i18n("Preview With..."), this ,SLOT(openWithSlot()));
	//fullMenu ->insertItem(BarIcon("up", KIcon::SizeMedium ), i18n("Move Up"), this ,SLOT(moveUp()));
	//fullMenu ->insertItem(BarIcon("down", KIcon::SizeMedium ), i18n("Move Down"), this ,SLOT(moveDown()));
	
	fullMenu -> insertSeparator();
	fullMenu->insertItem(UserIcon("cross"), i18n("Remove Track(s)"), this ,SLOT(remove()));
	
	fullMenu -> insertSeparator();
	fullMenu ->insertItem(UserIcon("isoSetup"), i18n("Settings"), this ,SLOT(compilSettingsSlot()));
	fullMenu ->insertItem(UserIcon("burn"), i18n("Create Audio CD"), parent ,SLOT(createCD()));
		
	////////////////////////////////////////////////////////////////////////////
	shortMenu = new QPopupMenu();
	shortMenu ->insertItem(UserIcon("isoSetup"), i18n("Settings"), this ,SLOT(compilSettingsSlot()));
	shortMenu ->insertItem(UserIcon("burn"), i18n("Create Audio CD"), parent ,SLOT(createCD()));
	shortMenu -> insertSeparator();
	removeAllIndex = shortMenu -> insertItem(UserIcon("cross"), i18n("Remove All Tracks"),   this ,SLOT(removeAll()));
	//////////////////////////////////////////////////////////////////////
	
	connect( this, SIGNAL(rightButtonPressed(QListViewItem*, const QPoint&, int)),
					 this, SLOT(popupMenuSlot(QListViewItem*, const QPoint&, int)));
	connect(  this, SIGNAL(dropped (QDropEvent *, QListViewItem *)),
						this, SLOT(dropOcurred(QDropEvent *, QListViewItem *)));
	connect(  this, SIGNAL(doubleClicked ( QListViewItem *)),
						this, SLOT(trackDoubleClicked ( QListViewItem *)));

}
//================================================
AudioCompilationTreeView::~AudioCompilationTreeView(){
}
//================================================
void AudioCompilationTreeView::setEstimate(AudioEstimatePnl *e) {
	if(!e) return;
	
	estimate = e;
	connect(estimate->getRecalculateBtn(), SIGNAL(clicked ()),
						this, SLOT(reload()));

}
//==============================================================
void AudioCompilationTreeView::updateSettings(){
	
	/*config = new KConfig("cdbakeovenrc");
	config->setGroup("Audio Read Options");
	cdboDtct = config->readBoolEntry("CDBO Audio Files Detection", true);
    	if(cdboDtct)
    		extList = QStringList::split(QRegExp(" "),
				config->readEntry("Supported Audio Extensions",
					".mp3 .aiff .au .wav .ogg .raw .cdr .sun"));
	delete config;*/
	
}
//==============================================================
void AudioCompilationTreeView::popupMenuSlot(QListViewItem *item, const QPoint &point, int){	
	if(!item){
		if(childCount() == 0)
			shortMenu->setItemEnabled(removeAllIndex, false);
		else
			shortMenu->setItemEnabled(removeAllIndex, true);
		
		shortMenu->popup(point);
	}
	else{
		setSelected(item, true);
		fullMenu->popup( point);
	}
}
//==============================================================
QString AudioCompilationTreeView::currentItemPath(){
	if (childCount() == 0) return "";
	
	return ((AudioItem *)currentItem())->getFilePath();
}
//============================================================
QString AudioCompilationTreeView::currentItemName(){
	if (childCount() == 0) return "";
	
	return ((AudioItem *)currentItem())->getFileName();
}
//============================================================
void AudioCompilationTreeView::removeAll(){
	clear();
	fList.clear();
	nameList.clear();
	estimate->reset();
	updateSettings();
	emit modified();
}
//===========================================================
void AudioCompilationTreeView::remove(){
	QList<QListViewItem> list = selectedItems();
	QString length, mType;
	int secs;
	for(QListViewItem *item = list.first(); item; item = list.next()){
		
		secs = 0;
		length = ((AudioItem *)item)->getLength();
		if(length != "Unknown"){
			secs = length.left(length.find(":")).toInt() * 60;
			secs += length.right(length.length() - length.find(":") -1).toInt();
		}
	
		mType = ((AudioItem *)item)->getType();
		if(mType.find("MPEG") != -1)
			estimate->subtract(secs,AudioEstimatePnl::mp3Track);
		else if(mType.find("Ogg") != -1)
			estimate->subtract(secs,AudioEstimatePnl::oggTrack);
		else
			estimate->subtract(secs);	

		
		fList.remove(((AudioItem *)item)->getFilePath());
		nameList.remove(((AudioItem *)item)->getFileName());
		delete item;
	}
	correctPositions();
	//if(firstChild())
		//firstChild()->setSelected(true);
	emit modified();
}
//===========================================================
void AudioCompilationTreeView::correctPositions(){
	AudioItem *item = (AudioItem *)firstChild();
	if(!item)
		return;
	for(int pos = 1; item; pos++, item = (AudioItem *)item->nextSibling())
		item->setTrackPos(pos);
}
//===========================================================
void AudioCompilationTreeView::moveDown(){
	AudioItem *item = (AudioItem *)currentItem();
	if(!item)
		return;
	AudioItem *nextItem = (AudioItem *)item->itemBelow();
	if(!nextItem)
		return;
	int pos = item->getTrackPos();
	item->setTrackPos(nextItem->getTrackPos());
	nextItem->setTrackPos(pos);
	sort();
	clearSelection();
	item->setSelected(true);
	ensureItemVisible(item);
}
//===========================================================
void AudioCompilationTreeView::moveUp(){
	AudioItem *item = (AudioItem *)currentItem();
	if(!item)
		return;
	AudioItem *nextItem = (AudioItem *)item->itemAbove();
	if(!nextItem)
		return;
	int pos = item->getTrackPos();
	item->setTrackPos(nextItem->getTrackPos());
	nextItem->setTrackPos(pos);
	sort();
	clearSelection();
	item->setSelected(true);
	ensureItemVisible(item);
}
//---------------------------------------------------------------------------------------------
void AudioCompilationTreeView::keyPressEvent(QKeyEvent *e){
	if(e->key() == Key_Delete){
		remove();
	}
	if(e->key() == Key_F5){
		reload();
	}
	
	KListView::keyPressEvent(e);
}
//============================================
void AudioCompilationTreeView::slotProperties(){
	if ( !currentItem())
		return;
  	else{
		
		clearSelection();
		currentItem()->setSelected(true);
		ensureItemVisible(currentItem());
  		KPropertiesDialog *propDlg = new KPropertiesDialog( KURL( currentItemPath() ) );
  		connect( propDlg, SIGNAL( applied() ), this , SLOT( reload() ) );
  	}
}
//---------------------------------------------------------------------------------
void AudioCompilationTreeView::openWithSlot(){
	
	QList<QListViewItem> selectedList = selectedItems();
	if(selectedList.count() == 0)
		return;
	QStringList list;
	for(AudioItem *item = (AudioItem *)selectedList.first(); item; item = (AudioItem *)selectedList.next())
		list.append(item->getFilePath());
	
	KFileOpenWithHandler *kfowh = new KFileOpenWithHandler();
	kfowh -> displayOpenWithDialog( KURL::List( list ) );
}
//---------------------------------------------------------------------------------------------
void AudioCompilationTreeView::trackDoubleClicked ( QListViewItem *item){
	if(!item)
		return;
	KApplication::kApplication()->startServiceByDesktopName ( "noatun", ((AudioItem *)item)->getFilePath());
}
//---------------------------------------------------------------------------------------------
void AudioCompilationTreeView::compilSettingsSlot(){
	popUpSettings(CdboCompilationView::DefaultSettings);
}
//====================================
//====================================
//===== ADDING ITEMS ====================
//===================================
//====================================
bool AudioCompilationTreeView::acceptDrag (QDropEvent* event) const{
	if(QTextDrag::canDecode(event))
		return true;
	else
		return false;
}
//====================================
void AudioCompilationTreeView::dropOcurred(QDropEvent * e, QListViewItem */*item*/){
	//setParentForDrop(item);
	QString path;
	if(QTextDrag::decode(e, path)){
		if(path.find("file:") != -1){
			if(addMultipleItems(path))
				e->accept();
			else
				e->ignore();
		}
		else if(addItem(path))
			e->accept();
		else
			e->ignore();
	}	
	else
		e->ignore();	
}
//================================
bool AudioCompilationTreeView::addMultipleItems(QString path){
	bool valid = true;
	multy = true;
	errorAccepting = false;
	errorOverSizeLimit = false;
	QString buff;
	QStringList list(QStringList::split("\n",path));
	for(int i = 0; i < (int)list.count(); i++){
		buff = list[i].replace(QRegExp("file:"), "");
		int p =buff.length()-1;
		if(!buff[p].isLetterOrNumber() && !buff[p].isPunct())
			buff = buff.left(p);
			
		buff.simplifyWhiteSpace();
		if(!addItem(buff)){
			valid = false;
			break;
		}
	}	
	multy = false;
	checkRejected();

	return valid;
}
//================================
bool AudioCompilationTreeView::addMultipleItems(QStringList list){
	bool valid = true;
	multy = true;
	errorAccepting = false;
	errorOverSizeLimit = false;
	QString buff;
	for(int i = 0; i < (int)list.count(); i++){
		buff = list[i];
		
		int p =buff.length()-1;
		if(!buff[p].isLetterOrNumber() && !buff[p].isPunct())
			buff = buff.left(p);
			
		buff.simplifyWhiteSpace();
		if(!addItem(buff)){
			valid = false;
			break;
		}
	}
	multy = false;
	checkRejected();

	return valid;
}
//=========================================
bool AudioCompilationTreeView::addOneItem(QString path){
	errorAccepting = false;
	errorOverSizeLimit = false;
	return addItem(path);
}
//=========================================
bool AudioCompilationTreeView::addItem(QString path){
	bool result = canAccept(path);
	checkRejected();
	return result;
}
//================================
bool AudioCompilationTreeView::canAccept(QString path) {

	QFileInfo d( path );
	if(d.isDir()){
		if(!d.exists() || !d.isReadable()){
			KMessageBox::error(this,
				i18n("Cannot process %1: "\
				"Not a valid path or you do not " \
				"have enough privileges to add this item.").arg(path));
			return false;
		}
		else{
			return pickAudioFromDir(path);
		}
	}

	if(!d.exists() || !d.isReadable()){
		errorAccepting = true;
		return true;
	}
	if(!isValidType(path))
		return true;
	
	
	return tryToAdd(path);
}
///=========================================================
bool AudioCompilationTreeView::pickAudioFromDir( QString path){
	if(path[path.length()-1] != '/')
		path = path + "/";

	KIO::ListJob * listJob = KIO::listRecursive( KURL(path), false);
	listJob->setName(path.utf8());
	emit longProcessStarted(listJob);
	emit haveStatus(i18n("Retrieving audio files from this directory. Please wait..."));

	connect( listJob, SIGNAL(entries( KIO::Job *, const KIO::UDSEntryList& )),
						SLOT( slotEntries( KIO::Job*, const KIO::UDSEntryList& )));
	connect(listJob, SIGNAL(result ( KIO::Job *)),
			this, SLOT(audioPickerDone(KIO::Job *) ));

	totJobs++;
	if(totJobs == 1)
		emit processStarted();
	return true;
}
///=========================================================
void AudioCompilationTreeView::slotEntries( KIO::Job* job, const KIO::UDSEntryList & list ){
	KIO::UDSEntryListConstIterator it = list.begin();
	KIO::UDSEntryListConstIterator end = list.end();
	bool checked = false;
	QString name;
		
	for (; it != end; ++it) {
		KIO::UDSEntry::ConstIterator it2 = (*it).begin();
		for( ; it2 != (*it).end(); it2++ ) {
			switch( (*it2).m_uds ) {
				case KIO::UDS_NAME:{///FIXME:: handle links
					name = job->name() + (*it2).m_str;
					if(isValidType(name)){
						if(!tryToAdd(name)){
							if(job) job->kill();
       						emit longProcessDone(job);
       						longProcessCanceled(job->name());
       						return;
						}
					}
					checked = true;
					break;
				}
				default:
					break;
			}
			if(checked)
				break;
		}
	}
}
//=============================================
bool AudioCompilationTreeView::isValidType(QString path){
	/*if(cdboDtct){
		for(int i =0; i < (int)extList.count(); i++){
			if(path.find(extList[i].stripWhiteSpace(), -extList[i].length(), false) != -1)
				return true;
		}
	}
	else{*/

		KMimeType::Ptr mimetype = KMimeType::findByPath(path);
		//QString mType = mimetype->name().latin1();
		//				cerr << "mimetype->comment().latin1() : " << mimetype->comment().latin1();
		//				cerr << "\n mimetype->name().latin1() = " << mType.latin1() << "\n";
		//KMimeMagic* mime = KMimeMagic::self();
		//QString type = mime->findFileType(path)->mimeType();
		
		//cerr << " type = " << type.latin1() << " \n" ;
		if(mimetype->comment().find("audio") != -1 || mimetype->name().find("audio") != -1)
			return true;
	//}
	return false;
}
//================================
bool AudioCompilationTreeView::tryToAdd(QString path){
	if(fList.contains(path) > 0){
		errorAccepting = true;
		return true;
	}
////////////////////////////////////////
	KFileMetaInfo info(path);
	bool validInfo = info.isValid();
	/*if(!info.isValid()){
		errorAccepting = true;
		return true;
	}*/
	KFileMetaInfoItem infoItem;
	QString trackName = "Unknown";
	if(validInfo){
		infoItem = info.item("Title");
		if(infoItem.isValid()){
			trackName =  infoItem.string();
  			if(trackName == QString::null || trackName == "---")
				trackName = "Unknown";
		}
	}
	if(trackName != "Unknown" && nameList.contains(trackName) > 0){
		errorAccepting = true;
		return true;
	}
/////
	QString artist = "Unknown";
	if(validInfo){
		infoItem = info.item("Artist");
		if(infoItem.isValid()){
			artist =  infoItem.string();
  			if(artist == QString::null || artist == "---")
				artist = "Unknown";
		}
	}
//////
	QString length = "Unknown";
	if(validInfo){
		infoItem = info.item("Length");
		if(infoItem.isValid()){
			length =  infoItem.string();
  			if(length == QString::null || length == "---")
				length = "Unknown";
		}
	}

	return addFileItem(path, artist, trackName, length);
}
//===============================================================
bool AudioCompilationTreeView::addFileItem(QString path, QString artist, QString trackName, QString length){

	AudioItem *lastOne = (AudioItem *)lastChild();
	AudioItem *fItem = new AudioItem( this, path);
	
	if(!lastOne)
		fItem->setTrackPos(1);
	else
		fItem->setTrackPos(lastOne->getTrackPos()+ 1);

	fItem->setFileName( trackName);
	fItem->setLength( length);
	int secs = 0;
	if(length != "Unknown"){
		secs = length.left(length.find(":")).toInt() * 60;
		secs += length.right(length.length() - length.find(":") -1).toInt();
	}
	fItem->setText(2, artist);
	KMimeType::Ptr mimetype = KMimeType::findByPath(path);
	QString mType = mimetype->comment().latin1();
	fItem->setType( mType);

	bool bb;
	if(mType.find("MPEG") != -1)
		bb = estimate->add(secs,AudioEstimatePnl::mp3Track);
	else if(mType.find("Ogg") != -1)
		bb = estimate->add(secs,AudioEstimatePnl::oggTrack);
	else
		bb = estimate->add(secs);
	if(!bb){
		delete fItem;
		errorOverSizeLimit = true;
		return false;
	}
	
	fItem->setFilePath(path);
	fItem->setPixmap(0,BarIcon(KMimeType::iconForURL(path), KIcon::SizeSmall ));
	
	if(trackName != "Unknown")
		nameList.append(trackName);
	fList.append(path);
	ensureItemVisible(fItem);
	emit modified();
	return true;
}
//========================================================
void AudioCompilationTreeView::audioPickerDone(KIO::Job *j){
	totJobs--;
	if ( j->error())
    		return;
    	
    longProcessDone(j);
	if(totJobs > 0)
		return;
	emit processDone();
	emit haveStatus(i18n("Ready."));
	checkRejected();
}
//========================================================
void AudioCompilationTreeView::longProcessCanceled(QString){
	emit processDone();
	emit haveStatus(i18n("Ready."));
	totJobs--;
	multy = false;
	if(totJobs == 0)
		checkRejected();
}
//======================================================
void AudioCompilationTreeView::checkRejected(){
	if(!multy && totJobs == 0){
		if(errorOverSizeLimit){
			KMessageBox::sorry(this,
				i18n("Unable to accept any more files, CD-R(W) size limit has been reached."));
			return;
		}
		if(errorAccepting){
			KMessageBox::detailedSorry(this,
				i18n("Some of the files could not be added to the list."),
				i18n("Possible reasons are: \n"\
					"* Not a valid path or you do not have enough privileges to add this file. \n"\
					"* Track with the same name is already on the list.\n"\
					"* File was not in audio format.\n"\
					"* File is corrupt."));
		}
	}
}
//==============================================================
void AudioCompilationTreeView::reload(){
	emit haveStatus(i18n("Recalculating track list size. Please wait..."));
	QString buff = fList.join("\n");
	QStringList dub = QStringList::split("\n",buff);
	removeAll();
	addMultipleItems(dub);
	emit processDone();
	emit haveStatus(i18n("Ready."));

}


