/***************************************************************************
              searchspymodel.cpp  -  Search Spy Model implementation
                             -------------------
    begin                : Sat Nov 3 2007
    copyright            : (C) 2007 by Edward Sheldrake
    email                : ejs1920@yahoo.co.uk
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 "searchspymodel.h"

/* have to do all the sorting ourself */
#include <QtAlgorithms>

/** Initialises values */
SearchSpyItem::SearchSpyItem( QString search )
{
	text = search;
	count = 1;
	lastseen = QDateTime::currentDateTime();
	
	// invalid initial value
	index = -1;
}

/** */
SearchSpyModel::SearchSpyModel( QObject * parent ) : QAbstractItemModel( parent )
{
	sortColumn = 0;
	sortOrder = Qt::AscendingOrder;
}

/** delete all items in model */
SearchSpyModel::~SearchSpyModel()
{
	clear();
}

/** */
int SearchSpyModel::rowCount( const QModelIndex & /* index */ ) const
{
	return itemList.size();
}

/** */
int SearchSpyModel::columnCount( const QModelIndex & /* index */ ) const
{
	return 3;
}

/** */
QVariant SearchSpyModel::data( const QModelIndex & index, int role ) const
{
	if ( index.isValid() ) 
	{
		SearchSpyItem * item = itemList.value( index.row() );
		
		if ( item )
		{
			if ( role == Qt::DisplayRole )
			{
				switch ( index.column() )
				{
					case 0:	return item->text;
					case 1: return item->count;
					case 2: return item->lastseen.toString( Qt::TextDate );
				}
			}
			else if ( role == Qt::TextAlignmentRole )
			{
				if ( index.column() == 1 )
				{
					return Qt::AlignRight;
				}
			}
		}
	}
	
	return QVariant();
}

/** */
QVariant SearchSpyModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
	if ( (orientation == Qt::Horizontal) && (role == Qt::DisplayRole) )
	{
		switch ( section )
		{
			case 0: return tr("Text");
			case 1: return tr("Count");
			case 2: return tr("Time");
		}
	}

	return QVariant();
}

/** */
bool SpyItemQueryLessThan( const SearchSpyItem * l, const SearchSpyItem * r )
{
	return QString::localeAwareCompare( l->text, r->text ) < 0;
}

/** */
bool SpyItemCountLessThan( const SearchSpyItem * l, const SearchSpyItem * r )
{
	return ( l->count < r->count );
}

/** */
bool SpyItemTimeLessThan( const SearchSpyItem * l, const SearchSpyItem * r )
{
	return ( l->lastseen < r->lastseen );
}

/** */
bool SpyItemQueryGreaterThan( const SearchSpyItem * l, const SearchSpyItem * r )
{
	return QString::localeAwareCompare( l->text, r->text ) > 0;
}

/** */
bool SpyItemCountGreaterThan( const SearchSpyItem * l, const SearchSpyItem * r )
{
	return ( l->count > r->count );
}

/** */
bool SpyItemTimeGreaterThan( const SearchSpyItem * l, const SearchSpyItem * r )
{
	return ( l->lastseen > r->lastseen );
}

/** */
void SearchSpyModel::sort( int column, Qt::SortOrder order )
{
	emit layoutAboutToBeChanged();
	
	// save vales so we can insert new items at correct place
	sortColumn = column;
	sortOrder = order;
	
	if ( order == Qt::AscendingOrder )
	{
		switch ( column )
		{
			case 0:	qStableSort( itemList.begin(), itemList.end(), SpyItemQueryLessThan );
				break;
			case 1: qStableSort( itemList.begin(), itemList.end(), SpyItemCountLessThan );
				break;
			case 2: qStableSort( itemList.begin(), itemList.end(), SpyItemTimeLessThan );
				break;
		}
	}
	else if ( order == Qt::DescendingOrder )
	{
		switch ( column )
		{
			case 0: qStableSort( itemList.begin(), itemList.end(), SpyItemQueryGreaterThan );
				break;
			case 1: qStableSort( itemList.begin(), itemList.end(), SpyItemCountGreaterThan );
				break;
			case 2: qStableSort( itemList.begin(), itemList.end(), SpyItemTimeGreaterThan );
				break;
		}
	}
	
	for ( int i = 0; i < itemList.size(); ++i )
	{
		itemList.at(i)->index = i;
	}
	
	emit layoutChanged();
}

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

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

/** */
void SearchSpyModel::search( QString query )
{
	SearchSpyItem * item = itemHash.value( query );
	
	if ( item )
	{
		item->count++;
		item->lastseen = QDateTime::currentDateTime();
		
		QModelIndex topLeft = createIndex( item->index, 1 );
		QModelIndex bottomRight = createIndex( item->index, 2 );
		
		emit dataChanged( topLeft, bottomRight );
		
		/* move item to new position, simplest way is to re-sort */
		if ( sortColumn > 0 )
		{
			sort( sortColumn, sortOrder );
		}
	}
	else
	{
		emit layoutAboutToBeChanged();
		
		item = new SearchSpyItem( query );
		itemHash[ query ] = item;
		
		// find position in list for new item
		QList<SearchSpyItem*>::iterator it;
				
		if ( sortOrder == Qt::AscendingOrder )
		{
			switch ( sortColumn )
			{
				case 0:	it = qLowerBound( itemList.begin(), itemList.end(), item, SpyItemQueryLessThan );
					break;
				case 1:	it = qLowerBound( itemList.begin(), itemList.end(), item, SpyItemCountLessThan );
					break;
				case 2:	it = qLowerBound( itemList.begin(), itemList.end(), item, SpyItemTimeLessThan );
					break;
			}
		}
		else if ( sortOrder == Qt::DescendingOrder )
		{
			switch ( sortColumn )
			{
				case 0:	it = qLowerBound( itemList.begin(), itemList.end(), item, SpyItemQueryGreaterThan );
					break;
				case 1:	it = qLowerBound( itemList.begin(), itemList.end(), item, SpyItemCountGreaterThan );
					break;
				case 2:	it = qLowerBound( itemList.begin(), itemList.end(), item, SpyItemTimeGreaterThan );
					break;
			}
		}
		
		int i;
		if ( it == itemList.begin() )
		{
			i = 0;
		}
		else if ( it == itemList.end() )
		{
			i = itemList.size();
		}
		else
		{
			i = (*it++)->index;
		}
		
		item->index = i;
		itemList.insert( i, item );
		
		i++;
		
		// fix indexes
		for ( ; i < itemList.size(); ++i )
		{
			itemList.at(i)->index++;
		}
		
		emit layoutChanged();
	}
}

/** */
void SearchSpyModel::clear()
{
	emit layoutAboutToBeChanged();
	
	/* temp list to avoid having deleted pointers in the containers briefly */
	QList<SearchSpyItem*> tmp = itemList;
	itemList.clear();
	itemHash.clear();
	
	for ( QList<SearchSpyItem*>::const_iterator it = tmp.constBegin(); it != tmp.constEnd(); ++it )
	{
		delete *it;
	}
	
	emit layoutChanged();
}
