//
// C++ Implementation: srusearcher
//
// Description:
//
//
// Author: Thach Nguyen <thach.nguyen@rmit.edu.au>, (C) 2006
//
// Copyright: See COPYING file that comes with this distribution
//
//Adapted from Tellico

#include "srusearcher.h"
#include "searchmanager.h"
#include "bibfile.h"
#include "xslthandler.h"
#include "filters/bibprogs.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 <qdom.h>
#include <qregexp.h>
#include <qlabel.h>
#include <qfile.h>
#include <qlayout.h>
#include <qwhatsthis.h>


namespace
{
    static const int SRU_DEFAULT_PORT = 7090;
}


SRUSearcher::SRUSearcher(QObject* parent_, const char* name_)
        : searcher(parent_, name_), m_step(Begin),  m_job(0),m_started(false)
{
#if HAVE_XSLT
    m_MARC21XMLHandler = 0;
    m_UNIMARCXMLHandler = 0;
#endif
}

SRUSearcher::SRUSearcher(QString source_, QString host_, uint port, QString path_, QString syntax_,  QObject *parent_, const char *name_)
        : searcher(parent_, name_), m_step(Begin), m_host(host_), m_port(port), m_path(path_), m_format(syntax_), m_job(0), m_started(false)
{
    m_name = source_;
#if HAVE_XSLT
    m_MARC21XMLHandler = 0;
    m_UNIMARCXMLHandler = 0;
#endif
}


SRUSearcher::~SRUSearcher()
{
#if HAVE_XSLT
    if (m_MARC21XMLHandler)
    {
        delete m_MARC21XMLHandler;
        m_MARC21XMLHandler = 0;
    }
    if (m_UNIMARCXMLHandler)
    {
        delete m_UNIMARCXMLHandler;
        m_UNIMARCXMLHandler = 0;
    }
#endif
}

QString SRUSearcher::defaultName()
{
    return i18n("SRU Server");
}

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

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

void SRUSearcher::readConfig(KConfig* config_, const QString& group_)
{
    KConfigGroupSaver groupSaver(config_, group_);
    m_host = config_->readEntry("Host");
    int p = config_->readNumEntry("Port", SRU_DEFAULT_PORT);
    if(p > 0)
    {
        m_port = p;
    }
    m_path = config_->readEntry("Path");
    // used to be called Database
    if(!m_path.startsWith(QChar('/')))
    {
        m_path.prepend('/');
    }
    m_format = config_->readEntry("Format", QString::fromLatin1("mods"));
    m_name = config_->readEntry("Name", defaultName());

}


void SRUSearcher::saveConfig(KConfig* config_)
{
    config_->writeEntry("Host", m_host);
    config_->writeEntry("Port", m_port);
    config_->writeEntry("Path", m_path);
    config_->writeEntry("Format", m_format);
}



void SRUSearcher::search(SearchKey key1, SearchKey key2, SearchKey key3 , const QString& value1, const QString& value2, const QString& value3, int operator1, int operator2)
{
    QString str;
    QString query = QString();
    if (!value1.isEmpty())
    {

        str = QChar('"') + value1 + QChar('"');
        switch(key1)
        {
        case All:
            query = str;
            break;

        case Title:
            query = QString::fromLatin1("title=") + str;
            break;

        case Author:
            query = QString::fromLatin1("author=") + str;

            break;

        case Subject:
            query = QString::fromLatin1("subject=") + str;
            break;

        case Year:
            query = QString::fromLatin1("date=") + str;
            break;

        default:
            stop();
            return;
        }
    }

    if (!value2.isEmpty() )
    {
        if (!query.isEmpty())
        {
            switch(operator1)
            {
            case 0:
                query += QString::fromLatin1(" and ");
                break;
            case 1:
                query += QString::fromLatin1(" or ");
                break;
            case 2:
                query += QString::fromLatin1(" not ");
                break;
            default:
                stop();
                return;
            }

        }
        str = QChar('"') + value2 + QChar('"');
        switch(key2)
        {
        case All:
            query += str;
            break;

        case Title:
            query += QString::fromLatin1("title=") + str;
            break;

        case Author:
            query += QString::fromLatin1("author=") + str;

            break;

        case Subject:
            query += QString::fromLatin1("subject=") + str;
            break;

        case Year:
            query += QString::fromLatin1("publicationYear=") + str;
            break;

        default:
            stop();
            return;
        }

    }

    if (!value3.isEmpty() )
    {
        if (!query.isEmpty())
        {
            switch(operator2)
            {
            case 0:
                query += QString::fromLatin1(" and ");
                break;
            case 1:
                query += QString::fromLatin1(" or ");
                break;
            case 2:
                query += QString::fromLatin1(" not ");
                break;
            default:
                stop();
                return;
            }

        }
        str = QChar('"') + value3 + QChar('"');
        switch(key3)
        {
        case All:
            query = +str;
            break;

        case Title:
            query += QString::fromLatin1("title=") + str;
            break;

        case Author:
            query += QString::fromLatin1("author=") + str;

            break;

        case Keyword:
            query += QString::fromLatin1("subject=") + str;
            break;

        case Year:
            query += QString::fromLatin1("publicationYear=") + str;
            break;

        default:
            stop();
            return;
        }

    }
	search(query);
}

void SRUSearcher::search(QString query){
	cerr << "Query string = " << query << "\n";
	if (query.isEmpty())
    {
        stop();
        return;
    }

	
	if(m_host.isEmpty() || m_path.isEmpty())
    {
        std::cerr << "SRUFetcher::search() - settings are not set!\n";
        stop();
        return;
    }

    m_data.truncate(0);
    m_started = true;
    m_url = KURL();
    m_url.setProtocol(QString::fromLatin1("http"));
    m_url.setHost(m_host);
    m_url.setPort(m_port);
    m_url.setPath(m_path);

    m_url.addQueryItem(QString::fromLatin1("operation"), QString::fromLatin1("searchRetrieve"));
    m_url.addQueryItem(QString::fromLatin1("version"), QString::fromLatin1("1.1"));
    //  m_url.addQueryItem(QString::fromLatin1("maximumRecords"), QString::number(SRU_MAX_RECORDS));
    //  m_url.addQueryItem(QString::fromLatin1("recordSchema"), m_format);
    
    m_url.addQueryItem(QString::fromLatin1("query"), query);
    std::cerr << m_url.prettyURL() << "\n";
    m_job = KIO::get(m_url, false, false);

    m_step = Search;

    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 SRUSearcher::stop()
{
    if(!m_started)
    {
        return;
    }
    if(m_job)
    {
        m_job->kill();
        m_job = 0;
    }
    m_data.truncate(0);
    m_started = false;
    emit signalDone(this);
}

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

void SRUSearcher::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())
    {
        stop();
        return;
    }

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

void SRUSearcher::searchResults()
{
    QDomDocument dom;
    if(!dom.setContent(m_data, false))
    {
        std::cerr << "SRUSearcher::searchResults() - server did not return valid XML.\n";
        stop();
        return;
    }
    for(QDomNode n = dom.documentElement().firstChild(); !n.isNull(); n = n.nextSibling())
    {
        QDomElement e = n.toElement();
        if(e.isNull())
        {
            continue;
        }
        if(e.tagName() == QString::fromLatin1("zs:numberOfRecords"))
        {
            m_total = e.text().toInt();
            break;
        }
    }
    m_waitingRetrieveRange = true;
    m_step = Wait;
    if (m_total > 0)
        emit signalQueryResult(m_total);
    else
    {
        signalMessage(i18n("No reference was found"), 1);
        stop();
    }
}

void SRUSearcher::retrieveRange(unsigned int min, unsigned int max)
{
    m_waitingRetrieveRange = false;
    if (min < 1 || max < 1 || max < min)
    {
        stop();
        return;
    }
    startRec = min;
    endRec = max;
    currentRec = startRec;
    getRecord(startRec);
}

void SRUSearcher::getRecord(unsigned int n)
{
    if(!m_started)
    {
        return;
    }
    if (n > endRec)
    {
        stop();
        return;
    }
    currentRec = n;
    KURL url = m_url;
    url.addQueryItem(QString::fromLatin1("startRecord"), QString::number(n));
    url.addQueryItem(QString::fromLatin1("maximumRecords"), "1");
    if (m_format == QString::fromLatin1("marcxml") )
        url.addQueryItem(QString::fromLatin1("recordSchema"), QString::fromLatin1("marcxml") );
    else if (m_format == QString::fromLatin1("unimarcxml") )
        url.addQueryItem(QString::fromLatin1("recordSchema"), QString::fromLatin1("unimarcxml") );
    else
        url.addQueryItem(QString::fromLatin1("recordSchema"), "mods");

    m_data.truncate(0);
    m_step = Fetch;
//    	std::cerr << url.prettyURL() << endl;
    m_job = KIO::get(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 SRUSearcher::fetchResults()
{

    QString s = QString::fromUtf8(m_data, m_data.size());
    QString str;
    if (m_format == QString::fromLatin1("mods") )
        str = s;
#if HAVE_XSLT
    else if (m_format == QString::fromLatin1("marcxml") )
    {
        QRegExp rx( "<zs:recordData>*</zs:recordData>" );
        rx.setWildcard(true);
        rx.search(s);
        str = rx.cap(0);
        str = str.remove(QString::fromLatin1("<zs:recordData>"));
        str = str.remove(QString::fromLatin1("</zs:recordData>"));

        if(initMARC21Handler())
        {
            str = m_MARC21XMLHandler->applyStylesheet(str);
        }
    }
    else if (m_format == QString::fromLatin1("unimarcxml") )
    {
        QRegExp rx( "<zs:recordData>*</zs:recordData>" );
        rx.setWildcard(true);
        rx.search(s);
        str = rx.cap(0);
        str = str.remove(QString::fromLatin1("<zs:recordData>"));
        str = str.remove(QString::fromLatin1("</zs:recordData>"));

        if(initUNIMARCHandler())
        {
            str = m_UNIMARCXMLHandler->applyStylesheet(str);
        }
    }
#endif

    QFile f(QString::fromLatin1("/tmp/kbib-sru.xml"));
    if(f.open(IO_WriteOnly))
    {
        QTextStream t(&f);
        t << str;
    }
    f.close();

    //Convert to bibtex
    xml2bib("/tmp/kbib-sru.xml", "/tmp/kbib-sru.bib");
	
	
	//Read bibfile
	BibentryTable *entryList = new BibentryTable();
	entryList->readBibfile("/tmp/kbib-sru.bib", false);
	
	for (int i = 0; i < entryList->size(); i++){
		BibEntry *bib = entryList->get_entry(i);
		if (bib)
			emit signalResultFound(new BibEntry(*bib));	
	}
	delete entryList;
	
/*	
    //Read bibfile
    BibEntry *bib;
    FILE *inf = fopen("/tmp/kbib-sru.bib", "r");

    if (!inf)
    {
        stop();
        return;
    }
    while(!feof(inf))
    {
//        bib=read_entry(inf, 0);
        if (bib)
        {
            emit signalResultFound(new BibEntry(*bib));
            delete bib;
        }
    }
*/

    if (currentRec < endRec)
        getRecord(currentRec+1);
    else
        stop();

}

QStringList SRUSearcher::searchKey()
{
    QStringList keyList;
    keyList << searchManager::self()->searchKeyString(All) << searchManager::self()->searchKeyString(Author)
    << searchManager::self()->searchKeyString(Title) << searchManager::self()->searchKeyString(Subject)
    << searchManager::self()->searchKeyString(Year);
    return keyList;
}

SearcherConfigWidget* SRUSearcher::configWidget(QWidget* parent_)
{
    return new SRUConfigWidget(parent_, this);
}

#if HAVE_XSLT
bool SRUSearcher::initMARC21Handler()
{
    if(m_MARC21XMLHandler)
    {
        return true;
    }

    QString xsltfile = locate("appdata", QString::fromLatin1("MARC21slim2MODS3.xsl"));
    if(xsltfile.isEmpty())
    {
        std::cerr << "SRUSearcher::initHandlers() - can not locate MARC21slim2MODS3.xsl." << endl;
        return false;
    }

    KURL u;
    u.setPath(xsltfile);

    m_MARC21XMLHandler = new XSLTHandler(u);
    if(!m_MARC21XMLHandler->isValid())
    {
        std::cerr << "SRUSearcher::initHandlers() - error in MARC21slim2MODS3.xsl." << endl;
        delete m_MARC21XMLHandler;
        m_MARC21XMLHandler = 0;
        return false;
    }
    return true;
}
#endif

#if HAVE_XSLT
bool SRUSearcher::initUNIMARCHandler()
{
    if(m_UNIMARCXMLHandler)
    {
        return true;
    }

    QString xsltfile = locate("appdata", QString::fromLatin1("UNIMARC2MODS3.xsl"));
    if(xsltfile.isEmpty())
    {
        std::cerr  << "SRUSearcher::initHandlers() - can not locate UNIMARC2MODS3.xsl." << endl;
        return false;
    }

    KURL u;
    u.setPath(xsltfile);

    m_UNIMARCXMLHandler = new XSLTHandler(u);
    if(!m_UNIMARCXMLHandler->isValid())
    {
        std::cerr << "SRUSearcher::initHandlers() - error in UNIMARC2MODS3.xsl." << endl;
        delete m_UNIMARCXMLHandler;
        m_UNIMARCXMLHandler = 0;
        return false;
    }
    return true;
}
#endif

SRUConfigWidget::SRUConfigWidget(QWidget* parent_, SRUSearcher* searcher_ /*=0*/)
        : SearcherConfigWidget(parent_)
{
    m_searcher = searcher_;
    QGridLayout* l = new QGridLayout(optionsWidget(), 4, 2);
    l->setSpacing(4);
    l->setColStretch(1, 10);

    int row = -1;
    QLabel* label = new QLabel(i18n("Hos&t: "), optionsWidget());
    l->addWidget(label, ++row, 0);
    m_hostEdit = new KLineEdit(optionsWidget());
    l->addWidget(m_hostEdit, row, 1);
    QString w = i18n("Enter the host name of the server.");
    QWhatsThis::add(label, w);
    QWhatsThis::add(m_hostEdit, w);
    label->setBuddy(m_hostEdit);

    label = new QLabel(i18n("&Port: "), optionsWidget());
    l->addWidget(label, ++row, 0);
    m_portSpinBox = new KIntSpinBox(0, 999999, 1, 7090, 10, optionsWidget());
    l->addWidget(m_portSpinBox, row, 1);
    w = i18n("Enter the port number of the server. The default is %1.").arg(SRU_DEFAULT_PORT);
    QWhatsThis::add(label, w);
    QWhatsThis::add(m_portSpinBox, w);
    label->setBuddy(m_portSpinBox);

    label = new QLabel(i18n("Path: "), optionsWidget());
    l->addWidget(label, ++row, 0);
    m_pathEdit = new KLineEdit(optionsWidget());
    l->addWidget(m_pathEdit, row, 1);
    w = i18n("Enter the path to the database used by the server.");
    QWhatsThis::add(label, w);
    QWhatsThis::add(m_pathEdit, w);
    label->setBuddy(m_pathEdit);

    label = new QLabel(i18n("Format: "), optionsWidget());
    l->addWidget(label, ++row, 0);
    m_formatCombo = new KComboBox(optionsWidget());
    m_formatCombo->insertItem(QString::fromLatin1("MODS"), QString::fromLatin1("MODS"));
#if HAVE_XSLT
    m_formatCombo->insertItem(QString::fromLatin1("MARCXML"), QString::fromLatin1("MARCXML"));
    m_formatCombo->insertItem(QString::fromLatin1("UNIMARCXML"), QString::fromLatin1("UNIMARCXML"));
#endif
    //	m_formatCombo->insertItem(QString::fromLatin1("Dublin Core"), QString::fromLatin1("dc"));
    l->addWidget(m_formatCombo, row, 1);
    w = i18n("Enter the result format used by the server.");
    QWhatsThis::add(label, w);
    QWhatsThis::add(m_formatCombo, w);
    label->setBuddy(m_formatCombo);

    l->setRowStretch(++row, 1);

    if(searcher_)
    {
        m_hostEdit->setText(searcher_->m_host);
        m_portSpinBox->setValue(searcher_->m_port);
        m_pathEdit->setText(searcher_->m_path);
        m_formatCombo->setCurrentText((searcher_->m_format).upper());
    }
    KAcceleratorManager::manage(optionsWidget());
}

void SRUConfigWidget::updateSearcher()
{
    SRUSearcher *s = static_cast<SRUSearcher*>(m_searcher);
    s->m_host = m_hostEdit->text().stripWhiteSpace();
    s->m_port = m_portSpinBox->value();
    s->m_path = m_pathEdit->text().stripWhiteSpace();
    s->m_format = m_formatCombo->currentText().lower();
}


#include "srusearcher.moc"
