/***************************** LICENSE START ***********************************

 Copyright 2013 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "MvQFolderModel.h"

#include <QApplication>
#include <QDateTime>
#include <QDebug>
#include <QFont>
#include <QFontMetrics> 
#include <QMimeData>
#include <QPainter>
#include <QPixmap>

#include "IconClass.h"
#include "IconInfo.h"
#include "Path.h"

#include "Folder.h"
#include "FolderInfo.h"
#include "FolderSearchData.h"

#include "IconFactory.h"

#include "MvQFolderItemProperty.h"
#include "MvQIconProvider.h"
#include "MvQTheme.h"
#include "MvQFileInfo.h"

#include <assert.h>
#include <map>

QHash<IconObject::IconStatus,QColor> MvQFolderModel::statusColour_;
QColor MvQFolderModel::fadedColour_; 
QString MvQFolderModel::infoNameColour_; 
QString MvQFolderModel::infoTypeColour_;
QString MvQFolderModel::infoSizeColour_;
QString MvQFolderModel::infoTimeColour_;
QString MvQFolderModel::infoLinkLabelColour_;
QString MvQFolderModel::infoLinkTargetColour_;
			
MvQFolderModel::MvQFolderModel(QObject *parent) : 
    QAbstractItemModel(parent) 
{ 
	init();
}


MvQFolderModel::MvQFolderModel(int iconSize,QObject *parent) : 
    QAbstractItemModel(parent)
{ 
	init();
	itemProp_->setIconSize(iconSize);
}

void MvQFolderModel::init()
{	
  	searchData_=0;
	folder_=0 ,
    	initialScanIsOn_=false,
    	lastArrived_=0;
    
    	itemProp_=new MvQFolderItemProperty();

	if(statusColour_.empty())
	{
	  	//statusColour_[IconObject::DefaultStatus]=QColor(0,0,90);
		statusColour_[IconObject::DefaultStatus]=MvQTheme::colour("itemview_main","text"); //          QColor(30,30,30);
		statusColour_[IconObject::ReadyStatus]=QColor(0,205,0);
		statusColour_[IconObject::WaitingStatus]=QColor(255,188,46);
		statusColour_[IconObject::ErrorStatus]=QColor(255,0,0);  
		
		fadedColour_=MvQTheme::colour("itemview_main","faded_text");
	
	  	infoNameColour_=MvQTheme::colour("icon_info","name").name();
	  	infoTypeColour_=MvQTheme::colour("icon_info","type").name();
		infoSizeColour_=MvQTheme::colour("icon_info","size").name();
		infoTimeColour_=MvQTheme::colour("icon_info","time").name();
		infoLinkLabelColour_=MvQTheme::colour("icon_info","linkLabel").name();
		infoLinkTargetColour_=MvQTheme::colour("icon_info","linkTarget").name();
	}
}
  
MvQFolderModel::~MvQFolderModel()
{
	saveFolderInfo();
	if(folder_)
	{  	
		folder_->removeObserver(this);
		((IconObject*)folder_)->removeObserver((IconObserver*)this);
	}
	
	delete itemProp_;
}

bool MvQFolderModel::isDataSet() const
{
	return (folder_ == 0) ? false : true;		
}

void MvQFolderModel::folderIsAboutToChange()
{
	beginResetModel();
}

bool MvQFolderModel::setFolder(Folder *folder,int iconSize)
{
	bool retVal=false;
	
	if(folder == folder_)
	  	return false;
	
	saveFolderInfo();
	
	beginResetModel();
	
	//Clear all the items!!!
	items_.clear();
	
	if(folder_)
	{
	  	folder_->removeObserver(this);
		((IconObject*)folder_)->removeObserver((IconObserver*)this);
	}	
		
	folder_=folder;
	lastArrived_=0;
	
	if(folder_)
	{
	  	//We are not an observer at the moment so arrived() will
		//not be called!
		folder_->scan();
		
		//Load items_ from folder
		loadItems();
		
		//Now we become an observer. Any changes afterwards will end up 
		//in arrived(), gone() etc. observer methods!
		folder_->addObserver(this);	
		((IconObject*)folder_)->addObserver((IconObserver*)this);		
		
		if(iconSize != -1)
		  	itemProp_->setIconSize(iconSize);		
		
		retVal=true;
	}
	
	//Update the search status
	updateSearchData(false);
	
	endResetModel();
	
	return retVal;
}

bool MvQFolderModel::setFolder(QString fullName,int iconSize)
{
	if(folder_ && QString::fromStdString(folder_->fullName()) == fullName)
		return false; 
  
  	return setFolder(Folder::folder(fullName.toStdString(),false),iconSize);
}		

bool MvQFolderModel::setFolder(const QModelIndex& index,int iconSize)
{	
	IconObject*   obj=objectFromIndex(index);
	if(obj && obj->isFolder())
	{	
  		setFolder(static_cast<Folder*>(obj),iconSize);
		return true;
	}	  
	
	return false;
}	
	
	
bool MvQFolderModel::setFolderToParent(int iconSize)
{	
	if(!folder_) return false;
	
	IconObject*   obj=folder_->parent();
	if(obj && obj->isFolder())
	{
  		return setFolder(static_cast<Folder*>(obj),iconSize);
	}
	
	return false;
}	
	
int MvQFolderModel::columnCount( const QModelIndex& parent) const
{
   	if(!isDataSet())
		return 0;
	
	 return parent.isValid() ? 0 : 6;
}


int MvQFolderModel::rowCount( const QModelIndex& index) const
{
    	if(!isDataSet())
		return 0;
	
	if (!index.isValid() )
	{
		return items_.count();
	}
	else
	{
		return  0;
	}
}

QVariant MvQFolderModel::data( const QModelIndex& index, int role ) const
{
	if (role != Qt::DisplayRole && role != Qt::DecorationRole  && 
	    role != Qt::ForegroundRole  &&  role != Qt::FontRole && 
	    role != ClassFilterRole && role != PositionRole && role != EditorRole &&
 	    role != Qt::EditRole && role != Qt::UserRole)
	{  	
		return QVariant();
	}
	
  	if ( !index.isValid())
	{
		return QVariant();
	}
	
	IconObject *obj=objectFromIndex(index);
	if(!obj)
		return QString();
	
	//First column: icon + name
	if(index.column() == 0)
	{	
		switch(role)
		{
		case Qt::DecorationRole:  			
		{
		  	if(!hasSearchData() || !searchData_->isSet() || searchData_->match(obj))
			{			  
		  		return MvQIconProvider::pixmap(obj,itemProp_->iconSize());
			}
			else
			{
			  	return MvQIconProvider::greyedOutPixmap(obj,itemProp_->iconSize());
			}	
		}	
		
		case Qt::DisplayRole:
		case Qt::EditRole: 
	  		return QString::fromStdString(obj->name());
		
		case Qt::ForegroundRole:
			{
	  			if(!hasSearchData() || !searchData_->isSet() || searchData_->match(obj))
				{
				  	QHash<IconObject::IconStatus,QColor>::const_iterator it=statusColour_.find(obj->status());
					if(it != statusColour_.end())
				  		return it.value();
					else
				  		return QColor();
				}
				else
				{
				  	return fadedColour_;
				}
				
				
			}
			break;	
			
		case ClassFilterRole:
		  	return isAccepted(obj);
			
		case PositionRole:
			//We get the reference position from the object
		  	return itemProp_->referencePosition(obj);
		
		case EditorRole:
		  	return (obj->editor())?true:false;	
			
		case Qt::FontRole:
			{
		  		if(obj->isLink())
				{
				  	QFont font=itemProp_->font();
					font.setItalic(true);
					return font;
				}
				else
				  	return itemProp_->font();
			}			 
		default:
		  	return QString();
		} 	
	}
	else if(index.column() == 1)
	{
		if(role == Qt::DisplayRole)
	  		return QString::fromStdString(obj->editorClass().name()); 
	}  
	else if(index.column() == 2)
	{
		if(role == Qt::DisplayRole)
	  		return formatFileSize(obj->path().sizeInBytes());
		else if(role == Qt::UserRole)
		{
		  	qint64 qs=obj->path().sizeInBytes();
			return qs;
 		}
	}  
	else if(index.column() == 3)
	{
		if(role == Qt::DisplayRole)
	  		return formatFileDate(obj->path().lastModified());
		  	  
	} 
	else if(index.column() == 4)
	{
		if(role == Qt::DisplayRole)
	  		return QString::fromStdString(obj->path().owner());
		  	  
	} 
	else if(index.column() == 5)
	{
		if(role == Qt::DisplayRole)
	  		return QString::fromStdString(obj->path().permissions());
		  	  
	} 
	
	
	if(index.column() >=1 && index.column() <=5)
	{			
		if(role == Qt::ForegroundRole)
		{
	  		return QColor(70,70,70);
	  		
	  		/*QMap<IconObject::IconStatus,QColor>::const_iterator it=statusColour_.find(obj->status());
			if(it != statusColour_.end())
				  return it.value();
			else
				  return QColor(100,100,100);*/
		}
		else if(role ==Qt::FontRole)
		  	return itemProp_->font(); 
	}
			
	return QString();
}

bool MvQFolderModel::setData( const QModelIndex& index, const QVariant & value, int role) 
{		
	if(!index.isValid() || (role != Qt::EditRole  &&  role != PositionRole) || index.column() != 0)
	{
		return false;	
	}
	
	IconObject* obj=objectFromIndex(index);
	
	if(role == Qt::EditRole)
	{  	  
		qDebug() << "Edit:"  << index << value.toString();
		  	
		string new_name=value.toString().toStdString();
	
		if(obj && obj->name() != new_name)
		{  
			QString oriName=QString::fromStdString(obj->name());
	  	
			//This will call the renamed() observer method!
			return obj->rename(new_name);
		}
	}
	else if(role == PositionRole)
	{
		QPoint viewRefPos=value.toPoint();
			
		//Compute the stored reference position from the view reference postion.
		QPoint refPos=itemProp_->storedPosition(viewRefPos);				
		obj->info().position(refPos.x(),refPos.y()); 
		return true;
	}
	
	
	return false;
}

QVariant MvQFolderModel::headerData(const int section, const Qt::Orientation orient , const int role ) const
{
	if ( orient != Qt::Horizontal || role != Qt::DisplayRole)
  		return QVariant();
	
  	if(section == 0)
	  	return "Name";	
	else if(section == 1)
	  	return "Type";
	else if(section == 2)
	  	return "Size";
	else if(section == 3)
	  	return "Date";
	else if(section == 4)
	  	return "Owner";
	else if(section == 5)
	  	return "Permissions";
	
	return QVariant();
}


QModelIndex MvQFolderModel::index( int row, int column, const QModelIndex & /*parent*/ ) const
{
   	if(!isDataSet())
	{
		return QModelIndex();
	}

	return createIndex(row,column,(void*)0);
}


QModelIndex MvQFolderModel::parent( const QModelIndex & /*index*/ ) const
{
    return QModelIndex();
}

Qt::ItemFlags MvQFolderModel::flags ( const QModelIndex & index) const
{
	Qt::ItemFlags defaultFlags;
	
	defaultFlags=Qt::ItemIsEnabled | Qt::ItemIsSelectable;
		   
	if(index.column() == 0)
	{
		IconObject* obj=objectFromIndex(index);
		if(obj && !obj->locked() && !obj->editor())
		{
			return	Qt::ItemIsEditable | defaultFlags;	
		}  
	}	   		   
	return defaultFlags;
}
 
IconObject* MvQFolderModel::objectFromIndex(const QModelIndex & index) const
{
  	if ( !index.isValid())
	{
		return 0;
	}
	
	if(index.row() >=0 && index.row() < items_.count())
	  	return items_.at(index.row());
	
	return 0;
}

QModelIndex MvQFolderModel::indexFromObject(IconObject *obj)
{
  	if(!obj)
	  	return QModelIndex();
  
	int i=items_.indexOf(obj);
	return (i >=0)?createIndex(i,0,(void*)0):QModelIndex();
}

//Should be called between beginResetModel() and endResetModel() !!
void MvQFolderModel::loadItems()
{
 	 items_.clear();
	 if(!folder_)
	 	return;
	 
	 const map<string,IconObjectH>& kids=folder_->kids();	
	 for(map<string,IconObjectH>::const_iterator it=kids.begin(); it != kids.end(); it++)
		if(isAccepted(it->second))
		{
		  	items_ << it->second;
		}
}  	

//------------------------------------
//
//  Filtering
//
//-------------------------------------

void  MvQFolderModel::setAcceptedClasses(const vector<string>& c)
{
	beginResetModel();
	classes_=c;
	loadItems();
	endResetModel();
}	
  
bool MvQFolderModel::isAccepted(const IconClass& kind) const
{
  	if(classes_.size()>0)
	{
  		for(vector<string>::const_iterator it =  classes_.begin(); it != classes_.end(); ++it)
		{	
			if(kind.canBecome(IconClass::find(*it)) )   
				return true;			
		}
		return false;
	}	
		
	return true;
}  

bool MvQFolderModel::isAccepted(IconObject *obj) const
{
	if(obj)
	{
		if(!obj->visible())
		  	return false;
		
		const IconClass& kind=obj->editorClass();	
	  	return isAccepted(kind);
	}
	
	return false;
}

void MvQFolderModel::saveFolderInfo()
{
	if(folder_)
	  	folder_->saveFolderInfo();
}


QString MvQFolderModel::fullName()
{
	return (folder_)?QString::fromStdString(folder_->fullName()):QString();
}

QString MvQFolderModel::fullName(const QModelIndex & index)
{
	IconObject*   obj=objectFromIndex(index);
	return (obj)?QString::fromStdString(obj->fullName()):QString();
}	

bool MvQFolderModel::isFolder(const QModelIndex & index)
{
	IconObject*   obj=objectFromIndex(index);
	return (obj && obj->isFolder())?true:false;
}	

QModelIndex MvQFolderModel::lastArrived()
{
  	if(lastArrived_)
	{
		return indexFromObject(lastArrived_);	
	}
	
	return QModelIndex();
}

//---------------------------------------
// Icon positions
//---------------------------------------

int MvQFolderModel::iconSize() const
{
  	return itemProp_->iconSize();
}	

void MvQFolderModel::setIconSize(int size)
{
  	if(!itemProp_->setIconSize(size))
	  	return;
			
	emit iconSizeChanged();	
}  

void MvQFolderModel::setPositionHint(const string& cn,int x,int y)
{
	posHint_.className=cn;
	posHint_.x=x;
	posHint_.y=y;
}	

//----------------------------------------------
//
//  FolderObserver methods
//
//----------------------------------------------

void MvQFolderModel::arrived(IconObject* obj)
{
	qDebug() << "arrived"; 
 	if(obj && isAccepted(obj))
	{	  	
	  	//beginInsertRows(), endInsertRows() were just resetting the whole view!
		//It is easier just reset the model because our icon view knows what to do with the
		//icon positions in this case		
		beginResetModel();		
		items_ << obj;		
		lastArrived_=obj;
		
		if((posHint_.className.empty() || posHint_.className == obj->iconClass().name()) &&
		   obj->info().x() == 0 && obj->info().y() == 0)
		{
		  	//positionHint contains the viewRefPos!!
		  	QPoint refPos=itemProp_->storedPosition(QPoint(posHint_.x,posHint_.y));				
			obj->info().position(refPos.x(),refPos.y()); 	
		}
		posHint_.clear();
		
		//Update saerch data
		updateSearchData(false);
		
		
		qDebug() << "reset receivers:" << receivers(SIGNAL(modelReset())); 
		
		endResetModel();
		
		//emit objectArrived(indexFromObject(obj));	
	}
}
  
void MvQFolderModel::gone(IconObject* obj) 
{
  	qDebug() << "gone";
  	if(obj && items_.contains(obj))
	{
	  	beginResetModel();
		items_.removeOne(obj);		
		
		//Update saerch data
		updateSearchData(false);
		
		endResetModel();
	}	
}  
  
void MvQFolderModel::position(IconObject*,int,int)
{
	    
}   

void MvQFolderModel::renamed(IconObject* obj,const string& oriName)
{
    	qDebug() << "renamed()";
    
  	if(isAccepted(obj))
	{
		//Update saerch data
		updateSearchData(false);
		
		//We notify the view about the renaming
	  	emit objectRenamed(indexFromObject(obj),QString::fromStdString(oriName));	  	
	}  
}  

void MvQFolderModel::waiting(IconObject* obj)
{	
  	objectChanged(obj);
} 
 
void MvQFolderModel::error(IconObject* obj)	
{
  	objectChanged(obj);
} 
 
void MvQFolderModel::modified(IconObject* obj)	
{
  	objectChanged(obj);
} 
 
void MvQFolderModel::ready(IconObject* obj)	
{
	objectChanged(obj);
}  
 
void MvQFolderModel::opened(IconObject* obj)
{
  	objectChanged(obj);
} 

void MvQFolderModel::closed(IconObject* obj)
{
  	objectChanged(obj);
}
 
void MvQFolderModel::objectChanged(IconObject* obj)
{
	if(obj)
	{
		QModelIndex index=indexFromObject(obj);
		emit dataChanged(index,index);
	}
}

void MvQFolderModel::highlight(IconObject*)
{
}

//This is an iconobserver method. It is called when the folder itself (e.g. name)
//has been changed/
void MvQFolderModel::iconChanged(IconObject* obj)
{
	if(obj == folder_)
	{
	  	emit folderChanged(folder_);
	}
}


void MvQFolderModel::createNewFolder()
{
	//This will call in the end the arrived() observer method!
  	IconClass::find("FOLDER").createOne(folder_); 
}  

QString MvQFolderModel::objectInfo( const QModelIndex & index)
{	
  	IconObject* obj=objectFromIndex(index);	
	if(obj)
	{		
		//Icon name
		QString txt="<b><font color=\""   + infoNameColour_ + "\">";
		
		if(obj->isLink())
		{
			txt+="<i>";
		}
		
		txt+=QString::fromStdString(obj->name());
		
		if(obj->isLink())
		{
			txt+="</i>";
		}
		
		txt+="</font> "; 
		
		//icon class
		txt+=" <font color=\"" + infoTypeColour_ + "\">(" +QString::fromStdString(obj->editorClass().name()) + ") </font> ";
		
		//file size
		if(!obj->isFolder())
		{  
			txt+="<font color=\"" + infoSizeColour_ + "\">" + formatFileSize(obj->path().sizeInBytes()) + " </font> ";
		}
	
		//date and tyme
		txt+=" <font color=\"" +  infoTimeColour_ + "\">" + formatFileDate(obj->path().lastModified()) + " </font> ";
		
		//symlink target
		if(obj->isLink())
		{
			txt+="<font color=\"" +  infoLinkLabelColour_ +  "\"> link: </font> ";
			txt+="<font color=\"" +  infoLinkTargetColour_ +  "\"><i>" + QString::fromStdString(obj->path().symlinkTarget()) + " </i></font> ";
		}
		
		txt+="</b>";
		
		return txt;
	}
	
	return QString();
}	

QString MvQFolderModel::formatFileSize(qint64 size) const
{	
    return MvQFileInfo::formatSize(size);
}

QString MvQFolderModel::formatFileDate(time_t t) const
{	
  	QDateTime dt=QDateTime::fromTime_t(t);
	return dt.toString("yyyy-MM-dd hh:mm");
}


//---------------------------------------------------------
//
// Filter/search
//
//---------------------------------------------------------

void MvQFolderModel::setSearchData(FolderSearchData *data)
{
	searchData_=data;	
		
	//Register owner
	searchData_->setOwner(this);
	
	updateSearchData(true);
}

void MvQFolderModel::updateSearchData(bool doReset)
{
	if(!hasSearchData())
	{
	  	if(doReset)
		  //reset();  NOT in Qt 5
	      beginResetModel();
	      endResetModel();
		return;
	}
	
	//Clear search results
	searchData_->clearMatch();
  
	if(searchData_->isSet())
	{
	 	 for(int i=0; i< rowCount(); i++)
		{	  		  	
			QModelIndex idx=index(i,0);
			searchData_->load(objectFromIndex(idx));
		}		
	}
	
	//Notify changes
	searchData_->matchChanged();
	
	if(doReset)
	//reset();  NOT in Qt 5
	beginResetModel();
	endResetModel();
}  

bool MvQFolderModel::hasSearchData() const
{
  	if(!searchData_)
	{
	  	return false;
	}
	
	if(searchData_->owner() != this)
	{
	  	searchData_=0;
		return false;
	}
	
	return true;
}	
	

/*
  
  	bool found=false;
  
  	if(filterName_ == name.toStdString() && filterType_ == type.toStdString())
	  	return found;
  
  	filterName_=name.toStdString();
	filterType_=type.toStdString();

	filterMatchLst_.clear();
	
	if(!filterName_.empty() || !filterType_.empty())
	{	  	
		for(int i=0; i< rowCount(); i++)
		{	  		  	
			QModelIndex idx=index(i,0);
			if(IconObject *obj=objectFromIndex(idx))
			{			  
			  	if(obj->match(filterName_,filterType_))
				{
				  	filterMatchLst_ << obj; 
				}	
			}
		}	
	}		
	
	reset();	
	return found;
}*/


//=======================================
//
// MvQFolderFilterModel:
//
//=======================================

MvQFolderFilterModel::MvQFolderFilterModel(QObject *parent) :  QSortFilterProxyModel(parent)
{
}

void MvQFolderFilterModel::setSourceModel(QAbstractItemModel* sourceModel)
{
	QSortFilterProxyModel::setSourceModel(sourceModel);
}

bool MvQFolderFilterModel::filterAcceptsRow(int sourceRow,
         const QModelIndex &/*sourceParent*/) const
{	
	QModelIndex index=sourceModel()->index(sourceRow,0);	
	return sourceModel()->data(index,MvQFolderModel::ClassFilterRole).toBool();
}


bool MvQFolderFilterModel::lessThan(const QModelIndex &left,
                                       const QModelIndex &right) const
{
	QVariant leftData,rightData;	
	if(left.column() == 2)
	{
	  	leftData = sourceModel()->data(left,Qt::UserRole);
     		rightData = sourceModel()->data(right,Qt::UserRole); 
		return leftData.toLongLong() <   rightData.toLongLong();
	}
	else
	  	return QSortFilterProxyModel::lessThan(left,right); 
} 

/*QVariant MvQFolderFilterModel::data(const QModelIndex& index, int role ) const
{
  	if(role == Qt::DecorationRole && matchLst_.count() > 0 )
	{
  		if(MvQFolderModel *md=static_cast<MvQFolderModel*>(sourceModel()))
		{
			if(IconObject *obj=md->objectFromIndex(mapToSource(index)))
			{
			  	if(!matchLst_.contains(obj))
				{
					return md->data(mapToSource(index),MvQFolderModel::GreyedOutRole);  	
				}
			}
		}	
	}
	
	return sourceModel()->data(mapToSource(index),role);
}  */
/*  
bool MvQFolderFilterModel::setFilter(QString name,QString type)
{
  	bool found=false;
  
  	if(name_ == name.toStdString() && type_ == type.toStdString())
	  	return found;
  
  	name_=name.toStdString();
	type_=type.toStdString();

	matchLst_.clear();
	
	MvQFolderModel *md=static_cast<MvQFolderModel*>(sourceModel());
	if(!md)
	  	return found;
	
	if(!name_.empty() || !type_.empty())
	{	  	
		for(int i=0; i< rowCount(); i++)
		{	  		  	
			QModelIndex idx=index(i,0);
			if(IconObject *obj=md->objectFromIndex(mapToSource(idx)))
			{
		  		if(obj->match(name_,type_))
					matchLst_ << obj; 
			}
		}	
	}		
	
	reset();
	
	return found;
}
*/
