//
// C++ Implementation: scitationsearcher
//
// Description: 
//
//
// Author: Thach Nguyen <thach.nguyen@rmit.edu.au>, (C) 2008
//
// Copyright: See COPYING file that comes with this distribution
//
//
#include "scitationsearcher.h"
#include "searchmanager.h"
#include "bibentrytable.h"

#include <klocale.h>
#include <kio/job.h>
#include <kstandarddirs.h>
#include <kconfig.h>
#include <kcombobox.h>
#include <klineedit.h>
#include <kcombobox.h>
#include <kurllabel.h>
#include <kdialogbase.h>
#include <kaccelmanager.h>
#include <knuminput.h>
#include <kstandarddirs.h>
#include <ktempfile.h>

#include <qdom.h>
#include <qregexp.h>
#include <qlabel.h>
#include <qfile.h>
#include <qhbox.h>
#include <qlayout.h>
#include <qwhatsthis.h>

#include <iostream>

namespace {
  static const char* SCITATION_BASE_URL = "http://scitation.aip.org";
  static const char* SCITATION_SEARCH_CGI = "vsearch/servlet/VerityServlet";
  static const char* SCITATION_GET_ABS_CGI= "getabs/servlet/GetCitation";
}


ScitationSearcher::ScitationSearcher(QObject *parent, const char *name)
	: searcher(parent, name),m_step(Begin), m_started(false)
{
	m_db = QString::fromLatin1("Scitation");
}


ScitationSearcher::~ScitationSearcher()
{
}
QString ScitationSearcher::defaultName() {
	return i18n("Scitation");
}

QString ScitationSearcher::source() const {
	return m_name.isEmpty() ? defaultName() : m_name;
}

void ScitationSearcher::readConfig(KConfig* config_, const QString& group_) {
    KConfigGroupSaver groupSaver(config_, group_);
    m_db = config_->readEntry("Database", QString::fromLatin1("Scitation"));

}


void ScitationSearcher::saveConfig(KConfig* config){
     config->writeEntry("Database", m_db);
}



QString ScitationSearcher::searchField(QString value1, SearchKey key){
	QString str;


	if ( !value1.isEmpty() )
	{
		
		switch ( key )
		{
			case All:
				str = value1; 
				break;
			case Title:
	            		str = value1 + QString::fromLatin1(" <IN> title");
        	   		break;

	        	case Author:
        	   		 str = value1 + QString::fromLatin1(" <IN> author");
        		 break;

			case Journal:
		         	str = value1 + QString::fromLatin1(" <IN> journal");
		         break;

	        	case Keyword:
            			str = value1 + QString::fromLatin1(" <IN> keywords");
            			break;

	        	case Abstract:
        			str = value1 + QString::fromLatin1(" <IN> abs");
            			break;

			default:
            			stop();
            			return QString();
		}
	}
	return str;

}



QString ScitationSearcher::searchBool(int op){
	QString str;
	switch(op)
            {
            case 0:
                str = QString::fromLatin1(" <AND> ");
                break;
            case 1:
                str += QString::fromLatin1(" <OR> ");
                break;
            case 2:
                str += QString::fromLatin1(" <NOT> ");
                break;
            default:
                stop();
                return QString();
            }
	return str;
}


void ScitationSearcher::search(SearchKey key1, SearchKey key2, SearchKey key3 , const QString& value1, const QString& value2, const QString& value3, int operator1, int operator2) {
 
	QString queryString;

	if (!value1.isEmpty()){
		queryString = searchField(value1, key1) ;
	}
	if (!value2.isEmpty()){
		if (!queryString.isEmpty() )
			queryString = QString::fromLatin1("(") + queryString + QString::fromLatin1(")") +
					searchBool(operator1) + QString::fromLatin1("(") + searchField(value2, key2) + QString::fromLatin1(")");
		else
			queryString = searchField(value2, key2);
	}
	if (!value3.isEmpty()){
		if (!queryString.isEmpty() )
			queryString = QString::fromLatin1("(") + queryString + QString::fromLatin1(")") +
					searchBool(operator2) + QString::fromLatin1("(") + searchField(value3, key3) + QString::fromLatin1(")");
		else
			queryString = searchField(value3, key3);
	}
	search(queryString);

}


void ScitationSearcher::search(QString queryString){
    m_started = true;

    m_data.truncate(0);
    m_query = queryString;
    if (m_query.isEmpty())
    {
        stop();
        return;
    }


   
    m_url = KURL(SCITATION_BASE_URL);
    m_url.addPath(QString::fromLatin1(SCITATION_SEARCH_CGI));

    if (m_db == QString::fromLatin1("SPIN") )
	   m_url.addQueryItem(QString::fromLatin1("KEY"), "SPIN");
    else if (m_db == QString::fromLatin1("Scitation+SPIN") )
    	   m_url.addQueryItem(QString::fromLatin1("KEY"), "ALL");	
    else
	   m_url.addQueryItem(QString::fromLatin1("KEY"), "FREESR");
/*

    QStringList list = QStringList::split ( "&", m_query );
    for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it )
    {
	QString field = ( *it );
	QString code = field.section( '=', 0, 0 );
	QString value = field.section( '=', 1, 1 );	
	m_url.addQueryItem(code, value);
    }
*/
    m_url.addQueryItem(QString::fromLatin1("possible1"), m_query);
    m_url.addQueryItem(QString::fromLatin1("sort"), "chron");
    m_url.addQueryItem(QString::fromLatin1("maxdisp"), "1");
    m_url.addQueryItem(QString::fromLatin1("smode"), "results");

    m_step = Search;

    m_job = KIO::get(m_url, false, false);
    connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
            SLOT(slotData(KIO::Job*, const QByteArray&)));
    connect(m_job, SIGNAL(result(KIO::Job*)),
            SLOT(slotComplete(KIO::Job*)));
	
}

void ScitationSearcher::stop() {
	if(!m_started) {
		return;
	}
	if(m_job) {
		m_job->kill();
		m_job = 0;
	}
	m_started = false;
	m_data.truncate(0);
	m_step = Begin;
	emit signalDone(this);
}

void ScitationSearcher::slotData(KIO::Job*, const QByteArray& data_) {
	QDataStream stream(m_data, IO_WriteOnly | IO_Append);
	stream.writeRawBytes(data_.data(), data_.size());
}

void ScitationSearcher::slotComplete(KIO::Job* job_) {
  // since the fetch is done, don't worry about holding the job pointer
	m_job = 0;

	if(job_->error()) {
		emit signalMessage(job_->errorString(), 0);
		stop();
		return;
	}

	if(m_data.isEmpty()) {
 		std::cerr << "ScitationSearcher::slotComplete() - no data\n";
		stop();
		return;
	}

  switch(m_step) {
	  case Search:
		  searchResults();
		  break;
	  case Fetch:
		  fetchResults();
		  break;
	  case FetchBibtex:
		  fetchBibtex();
		  break;
	  default:
		  std::cerr << "ScitationSearcher::slotComplete() - wrong step = " << m_step << "\n";
		  break;
  }
}

void ScitationSearcher::searchResults(){
	
	QString str = QString::fromUtf8(m_data, m_data.size());
	
	QRegExp rx ( QString::fromLatin1 ( ".*You found (\\d+).*" ) );

	if ( !rx.exactMatch ( str ) )
	{
		signalMessage ( i18n ( "No matching records were found for your search." ), 1 );
		stop();
	}
	str = rx.cap ( 1 );
	int total = str.toInt();
	m_waitingRetrieveRange = true;
	m_step = Wait;
	if ( total > 0 )
	{
		emit signalQueryResult ( total );
	}
	else
	{
		signalMessage ( i18n ( "No matching records were found for your search." ), 1 );
		stop();
	}


	
}


void ScitationSearcher::retrieveRange(unsigned int min, unsigned int max){
	if (m_step != Wait)
		return;
	m_waitingRetrieveRange = false;
    if ((min < 1 && max < 1) || max < min)
    {
        stop();
        return;
    }
    m_start = min;
    m_end = max;
    last_retrieved = min-1;
    retrieved = 0;

    int page=(min-1)/20+1;
    current_page = page;
  
    m_url = KURL(SCITATION_BASE_URL);
    m_url.addPath(QString::fromLatin1(SCITATION_SEARCH_CGI));
    if (m_db == QString::fromLatin1("SPIN") )
	   m_url.addQueryItem(QString::fromLatin1("KEY"), "SPIN");
    else if (m_db == QString::fromLatin1("Scitation+SPIN") )
    	   m_url.addQueryItem(QString::fromLatin1("KEY"), "ALL");	
    else
	   m_url.addQueryItem(QString::fromLatin1("KEY"), "FREESR");

    m_url.addQueryItem(QString::fromLatin1("possible1"), m_query);
    
    m_url.addQueryItem(QString::fromLatin1("sort"), "chron");
    m_url.addQueryItem(QString::fromLatin1("smode"), "results");
    m_url.addQueryItem(QString::fromLatin1("maxdisp"), "20");
    m_url.addQueryItem(QString::fromLatin1("page"), QString::number(current_page));

    m_data.truncate(0);
    m_step = Fetch;

    m_job = KIO::get(m_url, false, false);
    connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
            SLOT(slotData(KIO::Job*, const QByteArray&)));
    connect(m_job, SIGNAL(result(KIO::Job*)),
            SLOT(slotComplete(KIO::Job*)));
}


void ScitationSearcher::fetchResults(){

    m_url = KURL(SCITATION_BASE_URL);
    m_url.addPath(QString::fromLatin1(SCITATION_GET_ABS_CGI));
    m_url.addQueryItem(QString::fromLatin1("PrefType"), "ARTICLE");
    m_url.addQueryItem(QString::fromLatin1("PrefAction"), "Add+Selected");
    m_url.addQueryItem(QString::fromLatin1("fn"), "view_bibtex2");
    m_url.addQueryItem(QString::fromLatin1("source"), "scitation");
    m_url.addQueryItem(QString::fromLatin1("downloadcitation"), "+Go+");


	int start = -1;
	int stop;
	int end;
	int itemStart;
	int itemEnd;
	int total = m_end - m_start + 1;


	QString str = QString::fromUtf8(m_data, m_data.size());
	
	int index = last_retrieved+1;
	QString searchStr;
	while(start < 0){
		searchStr = QString::number(index)+QString::fromLatin1(".&nbsp;<input type=\"checkbox\" name=\"SelectCheck\" value=");
		start = str.find(searchStr, 0);
		if (start < 0)
			index++;
	} 
	str = str.mid(start);

	
	start = -1;
	QString itemStr;
	
	while (retrieved < total && str.find(QString::fromLatin1(".&nbsp;<input type=\"checkbox\" name=\"SelectCheck\" value="), 0) >= 0 ) {
		start = -1;
		
		while ( start < 0) {
			searchStr = QString::number(index)+QString::fromLatin1(".&nbsp;<input type=\"checkbox\" name=\"SelectCheck\" value=");
			start = str.find(searchStr, 0);
			if (start < 0)
				index++;
		}
		
		stop = str.find(QString::fromLatin1("></td>"), start);
		itemStr = str.mid(start, stop-start+7);
		str = str.mid(stop);
		searchStr = QString::fromLatin1 (".*") + QString::number(index)+ QString::fromLatin1 (".&nbsp;<input type=\"checkbox\" name=\"SelectCheck\" value=\"(.+)\"></td>.*" );
		QRegExp rx (searchStr);
		if ( rx.exactMatch ( itemStr ) )
		{
			itemStr = rx.cap ( 1 );
		}
	

		m_url.addQueryItem(QString::fromLatin1("SelectCheck"), itemStr);		

		index++;
		retrieved++;

	}
    last_retrieved = index-1;
    m_data.truncate(0);
    m_step = FetchBibtex;

    m_job = KIO::get(m_url, false, false);
    connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
            SLOT(slotData(KIO::Job*, const QByteArray&)));
    connect(m_job, SIGNAL(result(KIO::Job*)),
            SLOT(slotComplete(KIO::Job*)));

}

void ScitationSearcher::fetchBibtex(){
	QString str = QString::fromUtf8(m_data, m_data.size());
	KTempFile tmpfile;
	tmpfile.setAutoDelete(true);
    	QString fn = tmpfile.name();
	QFile f ( fn );
	if ( f.open ( IO_WriteOnly ) )
	{
		QTextStream t ( &f );
		t << str;
	}
	f.close();
	//Read bibfile
	BibentryTable *entryList = new BibentryTable();
	entryList->readBibfile ( const_cast<char*>(fn.ascii()), false );

	for ( int i = 0; i < entryList->size(); i++ )
	{
		BibEntry *bib = entryList->get_entry ( i );
		if ( bib )
			emit signalResultFound ( new BibEntry ( *bib ) );
	}
	delete entryList;
	int total = m_end - m_start + 1;
	if (retrieved >= total)
		stop();
	else{
		current_page++;
		m_url = KURL(SCITATION_BASE_URL);
    		m_url.addPath(QString::fromLatin1(SCITATION_SEARCH_CGI));
    		if (m_db == QString::fromLatin1("SPIN") )
	  		 m_url.addQueryItem(QString::fromLatin1("KEY"), "SPIN");
    		else if (m_db == QString::fromLatin1("Scitation+SPIN") )
    	   		m_url.addQueryItem(QString::fromLatin1("KEY"), "ALL");	
    		else
	   		m_url.addQueryItem(QString::fromLatin1("KEY"), "FREESR");

		m_url.addQueryItem(QString::fromLatin1("possible1"), m_query);
    
    		m_url.addQueryItem(QString::fromLatin1("sort"), "chron");
    		m_url.addQueryItem(QString::fromLatin1("smode"), "results");
    		m_url.addQueryItem(QString::fromLatin1("maxdisp"), "20");
   		m_url.addQueryItem(QString::fromLatin1("page"), QString::number(current_page));

    		m_data.truncate(0);
   		m_step = Fetch;

    		m_job = KIO::get(m_url, false, false);
    		connect(m_job, SIGNAL(data(KIO::Job*, const QByteArray&)),
            	SLOT(slotData(KIO::Job*, const QByteArray&)));
    		connect(m_job, SIGNAL(result(KIO::Job*)),
            	SLOT(slotComplete(KIO::Job*)));

	}
}


void ScitationSearcher::setSource(const QString s){
	m_name = s ;	
}


QStringList ScitationSearcher::searchKey(){
	QStringList keyList;
	keyList << searchManager::self()->searchKeyString(All)
			<< searchManager::self()->searchKeyString(Author)
			<< searchManager::self()->searchKeyString(Title)
			<< searchManager::self()->searchKeyString(Journal)
			<< searchManager::self()->searchKeyString(Keyword)
			<< searchManager::self()->searchKeyString(Abstract);
	return keyList;
}


SearcherConfigWidget* ScitationSearcher::configWidget(QWidget* parent_)
{
    return new ScitationConfigWidget(parent_, this);
}


ScitationConfigWidget::ScitationConfigWidget(QWidget* parent_, ScitationSearcher* searcher_ /*=0*/)
        : SearcherConfigWidget(parent_)
{
	m_searcher = searcher_;
	QVBoxLayout* l = new QVBoxLayout ( optionsWidget() );

	QHBox* box = new QHBox ( optionsWidget(), "box" );
 	l->addWidget(box);
	box->setSpacing ( KDialog::spacingHint() );

	QLabel* label = new QLabel ( i18n ("Database:" ), box );
	m_dbCombo = new KComboBox ( box);
	m_dbCombo->insertItem(QString::fromLatin1("Scitation"), QString::fromLatin1("Scitation"));
	m_dbCombo->insertItem(QString::fromLatin1("SPIN"), QString::fromLatin1("SPIN"));
	m_dbCombo->insertItem(QString::fromLatin1("Scitation+SPIN"), QString::fromLatin1("Scitation+SPIN"));
	
	QString w = i18n("The database to search. SPIN requires subscription.");
    	QWhatsThis::add(label, w);
    	QWhatsThis::add(m_dbCombo, w);
    	label->setBuddy(m_dbCombo);

	
	l->addStretch();
	if(searcher_)
    	{
        	m_dbCombo->setCurrentText(searcher_->m_db);
	}

	KURLLabel *urlLab = new KURLLabel( optionsWidget() );
	l->addWidget(urlLab);
	urlLab->setText( "More information about Scitation");
        urlLab->setURL("http://scitation.aip.org");
    
	connect(urlLab , SIGNAL( leftClickedURL( const QString& ) ), kapp, SLOT( invokeBrowser( const QString& ) ) );

	KAcceleratorManager::manage(optionsWidget());

}

void ScitationConfigWidget::updateSearcher()
{
    ScitationSearcher *s = static_cast<ScitationSearcher*>(m_searcher);
    s->m_db = m_dbCombo->currentText();
}



#include "scitationsearcher.moc"
