//
// C++ Implementation: sciencedirectsearcher
//
// Description: 
//
//
// Author: Thach Nguyen <thach.nguyen@rmit.edu.au>, (C) 2008
//
// Copyright: See COPYING file that comes with this distribution
//
//
#include "sciencedirectsearcher.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 <kaccelmanager.h>
#include <knuminput.h>
#include <kstandarddirs.h>
#include <ktempfile.h>

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

#include <iostream>

//#define TESTING

namespace {
  static const char* SCIENCEDIRECT_BASE_URL = "http://www.sciencedirect.com";
  static const char* SCIENCEDIRECT_SEARCH_CGI = "science";
 
}


ScienceDirectSearcher::ScienceDirectSearcher(QObject *parent, const char *name)
	: searcher(parent, name),m_step(Begin), m_started(false)
{
}


ScienceDirectSearcher::~ScienceDirectSearcher()
{
}
QString ScienceDirectSearcher::defaultName() {
	return i18n("ScienceDirect");
}

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

void ScienceDirectSearcher::readConfig(KConfig* config_, const QString& group_) {

}


void ScienceDirectSearcher::saveConfig(KConfig* config){
}


QString ScienceDirectSearcher::searchField(SearchKey key, QString data){
	QString field;
	QString str = data;
	str.simplifyWhiteSpace();
	switch(key)
        {
		case All:
			field = QString::fromLatin1("ALL(");
			break;
		case Author:
			field = QString::fromLatin1("AUTHORS(");
			break;
		case Title:
			field = QString::fromLatin1("TITLE(");
			break;
		case Keyword:
			field = QString::fromLatin1("KEYWORDS(");
			break;
		case Abstract:
			field = QString::fromLatin1("ABSTRACT(");
			break;
		default:
            stop();
            return 0;
	}
	field += str;
	field += ")";
	return field;
}

QString ScienceDirectSearcher::boolOperator(int n){
	QString str;
	switch(n){
		case 0:
			str = " AND ";
			break;
		case 1:
			str = " OR ";
			break;
		case 2:
			str = " AND NOT ";
			break;
	}
	return str;
}

void ScienceDirectSearcher::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(key1, value1); 
    }
	

    if (!value2.isEmpty() )
    {
        if (!queryString.isEmpty())
			queryString += boolOperator(operator1) + searchField(key2, value2);
		else
			queryString = searchField(key2, value2);
    }

    if (!value3.isEmpty() )
    {
	 	if (!queryString.isEmpty())
			queryString += boolOperator(operator2) + searchField(key3, value3);
		else
			queryString = searchField(key3, value3);

    }
	search(queryString);

}


void ScienceDirectSearcher::search(QString queryString){

	m_started = true;
    m_data.truncate(0);


    QString str;
    m_query = queryString;
    if (m_query.isEmpty())
    {
        stop();
        return;
    }
    
    m_step = StartPage;
	httpRequest("");
		
}

void ScienceDirectSearcher::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 ScienceDirectSearcher::slotData(KIO::Job*, const QByteArray& data_) {
	QDataStream stream(m_data, IO_WriteOnly | IO_Append);
	stream.writeRawBytes(data_.data(), data_.size());
}

void ScienceDirectSearcher::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 << "ScienceDirectSearcher::slotComplete() - no data\n";
		stop();
		return;
	}

  switch(m_step) {
	  case StartPage:
		  processStartPage();
		  break;
	  case AdvancedSearchPage:
		  processAdvancedSearchPage();
		  break;
	  case ExpertSearchPage:
		  processExpertSearchPage();
		  break;
	  case Search:
		  searchResults();
		  break;
	  case Fetch:
		  fetchResults();
		  break;
	  case FetchBibtex:
		  fetchBibtex();
		  break;
	  default:
		  std::cerr << "ScienceDirectSearcher::slotComplete() - wrong step = " << m_step << "\n";
		  break;
  }
}
void ScienceDirectSearcher::processStartPage(){
	QString str;
	str = QString::fromUtf8(m_data, m_data.size());

#ifdef TESTING
	//Testing

    QFile f(QString::fromLatin1("/home/s9510300/tmp/sciencedirect1.html"));
    if(f.open(IO_ReadOnly)) {
    	QTextStream stream( &f );
    	str = stream.read(); 
    	f.close();
    }
	/////
#endif
	int posStart = -1;

	posStart = str.find( "<a href=\"/science?_ob=MiamiSearchURL&_method=requestForm" );
    if ( posStart < 0 )
    {
        signalMessage ( i18n ( "You are not allowed to access the advanced search feature of ScienceDirect." ), 1 );
		stop();
		return;
    }

	int posEnd = str.find( "\" target=\"_top\" name=\"Search\" alt=\"Search\" title=\"Search\">", posStart + 43 );
    str = str.mid( posStart + 18, posEnd - posStart - 18 );

	m_step = AdvancedSearchPage;	
	httpRequest(str);

}

void ScienceDirectSearcher::processAdvancedSearchPage(){
	QString str = QString::fromUtf8(m_data, m_data.size());

#ifdef TESTING
	//Testing

    QFile f(QString::fromLatin1("/home/s9510300/tmp/sciencedirect2.html"));
    if(f.open(IO_ReadOnly)) {
    	QTextStream stream( &f );
    	str = stream.read(); 
    	f.close();
    }
	/////
#endif

	int posStart = -1;
	posStart = str.find( "<a href=\"/science?_ob=MiamiSearchURL&_method=requestForm&_temp=all_boolSearch.tmpl" );
	posStart = str.find( "<a href=\"/science?_ob=MiamiSearchURL&_method=requestForm&_temp=all_boolSearch.tmpl", posStart + 10 );
    if ( posStart < 0 )
    {
        signalMessage ( i18n ( "There is an error when performing the search." ), 1 );
		stop();
		return;
    }
	int posEnd = str.find( "\">Expert Search</a>", posStart + 81 );
    str = str.mid( posStart + 18, posEnd - posStart - 18 );

	m_step = ExpertSearchPage;	
	httpRequest(str);


}



void ScienceDirectSearcher::processExpertSearchPage(){
	QString str = QString::fromUtf8(m_data, m_data.size());

#ifdef TESTING
	//Testing

    QFile f(QString::fromLatin1("/home/s9510300/tmp/sciencedirect3.html"));
    if(f.open(IO_ReadOnly)) {
    	QTextStream stream( &f );
    	str = stream.read(); 
    	f.close();
    }
	/////
#endif

	int posStart = str.find( "<input type=\"hidden\" name=\"_acct\" value=\"" );
	posStart = str.find( "<input type=\"hidden\" name=\"_acct\" value=\"", posStart + 40 );
    if ( posStart < 0 )
    {
        signalMessage ( i18n ( "There is an error when performing the search." ), 1 );
		stop();
		return;
    }
	int posEnd = str.find( "\"", posStart + 43 );
    m_account = str.mid( posStart + 41, posEnd - posStart - 41 );
    posStart = str.find( "<input type=\"hidden\" name=\"md5\" value=\"", posStart);
    if ( posStart < 0 )
    {
		signalMessage ( i18n ( "There is an error when performing the search." ), 1 );
		stop(); 
		return;
	}
	posEnd = str.find( "\"", posStart + 41 );
    m_md5 = str.mid( posStart + 39, posEnd - posStart - 39 );

	//Now perform the real search
	str = QString("_ob=MiamiSearchURL&_method=submitForm&_acct=%1&_tmp=all_boolSearch.tmpl&md5=%2&test_alid=&source=srcJrl&source=srcBk&Subscribed=0&srcSel=1&DateOpt=2&fromDate=1823&toDate=Present&=submit&SearchText=").arg(m_account).arg(m_md5);
	str = str + m_query;


	m_step = Search;	
	httpRequest(str);

}

void ScienceDirectSearcher::searchResults(){
	QString str = QString::fromUtf8(m_data, m_data.size());

#ifdef TESTING	
	//Testing

    QFile f(QString::fromLatin1("/home/s9510300/tmp/sciencedirect4.html"));
    if(f.open(IO_ReadOnly)) {
    	QTextStream stream( &f );
    	str = stream.read(); 
    	f.close();
    }
	/////
#endif


	int posStart, posEnd;
	
	posStart = str.find( "<span class=\"h3\">" );

	posEnd = str.find( " articles  found for:", posStart + 17 );
	if ( posStart < 0 || posEnd < 0)
    {
		signalMessage ( i18n ( "No matching records were found for your search." ), 1 );
		stop(); 
		return;
	}	

    QString foundStr = str.mid( posStart + 17, posEnd - posStart - 17 );
	foundStr.remove( QChar(','));
	
	m_total = foundStr.toInt();

	m_waitingRetrieveRange = true;
	m_step = Wait;
	if (m_total <= 0){
		signalMessage(i18n("No matching records were found for your search."), 1);
		stop();
		return;
	}

	posStart = str.find( "<input type=\"hidden\" name=\"_ArticleListID\" value=" );
    if ( posStart < 0 )
    {
         signalMessage( i18n("There is an error when performing your search."), 1 );
         stop();
		 return;
    }
    posEnd = str.find( ">", posStart + 50 );
	m_articleListID = str.mid( posStart + 49, posEnd - posStart - 49 );
	posStart = str.find( "<input type=\"hidden\" name=\"md5\" value=\"" );
	posStart = str.find( "<input type=\"hidden\" name=\"md5\" value=\"", posStart + 10 );

    if ( posStart < 0 )
    {
		signalMessage ( i18n ( "There is an error when performing your search." ), 1 );
		stop(); 
		return;
	}
	posEnd = str.find( "\"", posStart + 41 );
    m_md5 = str.mid( posStart + 39, posEnd - posStart - 39 );   


    posStart = str.find( "_userid=" );
    if ( posStart < 0 )
    {
		signalMessage ( i18n ( "There is an error when performing your search." ), 1 );
		stop(); 
		return;
    }
	posEnd = str.find( "&", posStart + 9 );
    m_userID = str.mid( posStart + 8, posEnd - posStart - 8 );
	emit signalQueryResult(m_total, 100);	

	
}


void ScienceDirectSearcher::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;
    m_total = max-min+1;
 
    retrieved = 0;

	QString str;
	str = QString("_ob=ArticleListURL&_method=tag&refSource=search&_st=0&_chunk=0&NEXT_LIST=1&view=c&md5=") + m_md5 +  QString("&_ArticleListID=") + m_articleListID + QString("&sisr_search=&sisrterm=&export=Export+Citations&count=%1").arg(m_end);
   	m_step = Fetch;
	httpRequest(str);
}

void ScienceDirectSearcher::httpRequest(QString data){
    m_url = KURL(QString(SCIENCEDIRECT_BASE_URL)+"/"+QString(SCIENCEDIRECT_SEARCH_CGI)+"?"+data);

    m_job = KIO::get( m_url, false, false );

    m_data.truncate(0);

    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*)));
	
//	std::cerr << m_url.url() << "\n";
}

void ScienceDirectSearcher::fetchResults(){
	QString str = QString::fromUtf8(m_data, m_data.size());

	int posStart, posEnd;
	posStart = -1;
	posStart = str.find( "subscription does not entitle" );
	if (posStart >= 0 )
    {
		signalMessage ( i18n ( "You are not allowed to download reference data from ScienceDirect." ), 1 );
		stop(); 
		return;
    }

	posStart = str.find( "<input type=hidden name=_ArticleListID value=" );
    if ( posStart < 0 )
    {
         signalMessage( i18n("There is an error when performing the search."), 1 );
         stop();
		 return;
    }
    posEnd = str.find( ">", posStart + 46 );
	m_articleListID = str.mid( posStart + 45, posEnd - posStart - 45 );
	posStart = str.find( "<input type=hidden name=md5 value=" );
    if ( posStart < 0 )
    {
		signalMessage ( i18n ( "There is an error when performing the search." ), 1 );
		stop(); 
		return;
	}
	posEnd = str.find( ">", posStart + 36 );
    m_md5 = str.mid( posStart + 34, posEnd - posStart - 34 ); 
	
	posStart = str.find( "<input type=hidden name=_userid value=" );
    if ( posStart < 0 )
    {
		signalMessage ( i18n ( "There is an error when performing your search." ), 1 );
		stop(); 
		return;
    }
	posEnd = str.find( ">", posStart + 10 );
    m_userID = str.mid( posStart + 38, posEnd - posStart - 38 );


	str = QString("_ob=DownloadURL&_method=finish&_acct=") + m_account + QString("&_userid=") + m_userID + QString("&_ArticleListID=")
				+ m_articleListID + QString("&md5=") + m_md5 + QString("&format=cite-abs&citation-type=BIBTEX")+QString("&count=%1").arg(m_end);
	
	m_step = FetchBibtex;

	httpRequest(str);
}



void ScienceDirectSearcher::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 = m_start-1; i < entryList->size(); i++ )
	{
		BibEntry *bib = entryList->get_entry ( i );
		if ( bib )
			emit signalResultFound ( new BibEntry ( *bib ) );
	}
	delete entryList;
	stop();
}



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


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


SearcherConfigWidget* ScienceDirectSearcher::configWidget(QWidget* parent_)
{
    return new ScienceDirectConfigWidget(parent_, this);
}


ScienceDirectConfigWidget::ScienceDirectConfigWidget(QWidget* parent_, ScienceDirectSearcher* searcher_ /*=0*/)
        : SearcherConfigWidget(parent_)
{
	m_searcher = searcher_;
	QVBoxLayout* l = new QVBoxLayout ( optionsWidget() );
	l->addWidget ( new QLabel ( i18n ( "This source has no options." ), optionsWidget() ) );
	KURLLabel *urlLab = new KURLLabel( optionsWidget() );
	l->addWidget(urlLab);
	urlLab->setText( "More information about ScienceDirect");
        urlLab->setURL("http://info.sciencedirect.com/about/");
    
	connect(urlLab , SIGNAL( leftClickedURL( const QString& ) ), kapp, SLOT( invokeBrowser( const QString& ) ) );
	l->addStretch();
}


