//
// C++ Implementation: searchmanager
//
// Description:
//
//
// Author: Thach Nguyen <thach.nguyen@rmit.edu.au>, (C) 2006
//
// Copyright: See COPYING file that comes with this distribution
//
// Based on tellico

#include <config.h>

#include "z3950searcher.h"
#include "searchmanager.h"
#include "z3950connection.h"

#include "xslthandler.h"

#include "bibfile.h"
#include "filters/bibprogs.h"
#include "bibentrytable.h"
#include "grs1importer.h"

#include <klocale.h>
#include <kstandarddirs.h>
#include <kapplication.h>
#include <klineedit.h>
#include <knuminput.h>
#include <kconfig.h>
#include <kcombobox.h>
#include <kaccelmanager.h>

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

#include <iostream>

#include <config.h>


Z3950Searcher::Z3950Searcher(QObject* parent_, const char* name_)
        : searcher(parent_, name_)
{
    m_conn = 0;
#if HAVE_XSLT
    m_MARC21XMLHandler = 0;
    m_UNIMARCXMLHandler = 0;
#endif
    m_waitingRetrieveRange = false;
}


Z3950Searcher::Z3950Searcher(QString source_, QString host_, uint port, QString db_, QString syntax_, QString charset_, QObject* parent_, const char* name_): searcher(parent_, name_), m_host(host_), m_port(port), m_dbname(db_), m_sourceCharSet(charset_), m_syntax(syntax_)
{
    m_name = source_;
    m_conn = 0;
#if HAVE_XSLT	
    m_MARC21XMLHandler = 0;
    m_UNIMARCXMLHandler = 0;
#endif	
    m_waitingRetrieveRange = false;
}

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

QString Z3950Searcher::defaultName()
{
    return i18n("z39.50");
}


QString Z3950Searcher::source() const
{
	if (!m_name.isEmpty())
		return m_name;
	return m_host.isEmpty() ? defaultName() : m_host;
}

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

void Z3950Searcher::readConfig(KConfig* config_, const QString& group_)
{
    // keep a pointer to config so the syntax can be saved
    m_config = config_;
    m_configGroup = group_;

    KConfigGroupSaver groupSaver(config_, group_);
    QString s = config_->readEntry("Name", defaultName());
    if(!s.isEmpty())
    {
        m_name = s;
    }
    s = config_->readEntry("Host");
    if(!s.isEmpty())
    {
        m_host = s;
    }
    int p = config_->readNumEntry("Port", Z3950_DEFAULT_PORT);
    if(p > 0)
    {
        m_port = p;
    }
    s = config_->readEntry("Database");
    if(!s.isEmpty())
    {
        m_dbname = s;
    }
    s = config_->readEntry("User");
    if(!s.isEmpty())
    {
        m_user = s;
    }
    s = config_->readEntry("Password");
    if(!s.isEmpty())
    {
        m_password = s;
    }
    s = config_->readEntry("Charset", "utf-8");
    if(!s.isEmpty())
    {
        m_sourceCharSet = s;
    }
    s = config_->readEntry("Syntax", "mods");
    if(!s.isEmpty())
    {
        m_syntax = s;
    }

    s = config_->readEntry("esn", "f");
    if(!s.isEmpty())
    {
        m_esn = s;
    }


}

void Z3950Searcher::search(SearchKey key1, SearchKey key2, SearchKey key3 , const QString& value1, const QString& value2, const QString& value3, int operator1, int operator2)
{
#if HAVE_YAZ
	QString queryString;
    QString svalue = value3;
    QRegExp rx1(QString::fromLatin1("['\"].*\\1"));
    if(!rx1.exactMatch(svalue))
    {
        svalue.prepend('"').append('"');
    }


    if (!value3.isEmpty())
    {
        switch(key3)
        {
        case Title:
            queryString = QString::fromLatin1("@attr 1=4 ") + svalue;
            break;
        case Author:
            queryString = QString::fromLatin1(" @attr 1=1003 ") + svalue;
            break;
        case Keyword:
            queryString = QString::fromLatin1("@attr 1=21 ") + svalue;
            break;
        case Year:
            queryString = QString::fromLatin1("@attr 1=31 ") + svalue;
            break;
        case All:
            queryString = QString::fromLatin1("@attr 1=1016 ") + svalue;
            break;
        default:
            std::cerr << "key not recognized: " << key3 << endl;
            stop();
            return;
        }
    }

    svalue = value2;
    if(!rx1.exactMatch(svalue))
    {
        svalue.prepend('"').append('"');
    }

    if (!value2.isEmpty())
    {
        switch(key2)
        {
        case Title:
            queryString = QString::fromLatin1("@attr 1=4 ") + svalue+ queryString;
            break;
        case Author:
            queryString = QString::fromLatin1(" @attr 1=1003 ") + svalue + queryString;
            break;
        case Keyword:
            queryString = QString::fromLatin1("@attr 1=21 ") + svalue + queryString;
            break;
        case Year:
            queryString = QString::fromLatin1("@attr 1=31 ") + svalue + queryString;
            break;
        case All:
            queryString = QString::fromLatin1("@attr 1=1016 ") + svalue + queryString;
            break;
        default:
            std::cerr << "key not recognized: " << key2 << endl;
            stop();
            return;
        }
        if (!value3.isEmpty())
        {
            switch(operator2)
            {
            case 0:
                queryString = QString::fromLatin1(" @and ") + queryString;
                break;
            case 1:
                queryString = QString::fromLatin1(" @or ") + queryString;
                break;
            default:
                queryString = QString::fromLatin1(" @not ") + queryString;
                break;
            }
        }
    }

    svalue = value1;
    if(!rx1.exactMatch(svalue))
    {
        svalue.prepend('"').append('"');
    }

    if (!value1.isEmpty())
    {
        switch(key1)
        {
        case Title:
            queryString = QString::fromLatin1("@attr 1=4 ") + svalue+ queryString;
            break;
        case Author:
            queryString = QString::fromLatin1(" @attr 1=1004 ") + svalue + queryString;
            break;
        case Keyword:
            queryString = QString::fromLatin1("@attr 1=21 ") + svalue + queryString;
            break;
        case Year:
            queryString = QString::fromLatin1("@attr 1=31 ") + svalue + queryString;
            break;
        case All:
            queryString = QString::fromLatin1("@attr 1=1016 ") + svalue + queryString;
            break;
        default:
            std::cerr << "key not recognized: " << key1 << endl;
            stop();
            return;
        }

        if (value2.isEmpty() && !value3.isEmpty())
        {
            switch(operator2)
            {
            case 0:
                queryString = QString::fromLatin1(" @and ") + queryString;
                break;
            case 1:
                queryString = QString::fromLatin1(" @or ") + queryString;
                break;
            default:
                queryString = QString::fromLatin1(" @not ") + queryString;
                break;
            }
        }
        else if (!value2.isEmpty())
        {
            switch(operator1)
            {
            case 0:
                queryString = QString::fromLatin1(" @and ") + queryString;
                break;
            case 1:
                queryString = QString::fromLatin1(" @or ") + queryString;
                break;
            default:
                queryString = QString::fromLatin1(" @not ") + queryString;
                break;
            }

        }
    }
    //    std::cerr << "Query string: " << m_pqn.latin1() << "\n";

    //  m_pqn = QString::fromLatin1("@attr 1=7 0253333490");

    search(queryString);
#else // HAVE_YAZ
    Q_UNUSED(key1);
    Q_UNUSED(value1);
    Q_UNUSED(key2);
    Q_UNUSED(value2);
    Q_UNUSED(key3);
    Q_UNUSED(value3);
    stop();
    return;
#endif


}

void Z3950Searcher::search(QString queryString){
#if HAVE_YAZ
//	std::cerr << "Query String = " << queryString << "\n";
    m_started = true;
    if(m_host.isEmpty() || m_dbname.isEmpty())
    {
        std::cerr << "Z3950Fetcher::search() - settings are not set!" << endl;
        stop();
        return;
    }
 
    m_started = true;

	m_pqn = queryString;
	
    processSearch();
#else // HAVE_YAZ
	Q_UNUSED(queryString);
    stop();
    return;
#endif	
	
}

void Z3950Searcher::processSearch()
{
    if(m_conn)
    {
        m_conn->wait();
    }
    else
    {
        m_conn = new Z3950Connection(this, m_host, m_port, m_dbname, m_sourceCharSet, m_syntax, m_esn);
        if(!m_user.isEmpty())
        {
            m_conn->setUserPassword(m_user, m_password);
        }
    }
    m_waitingRetrieveRange = true;
    m_conn->setQuery(m_pqn);
    m_conn->start();

}

void Z3950Searcher::stop()
{
    if(!m_started)
    {
        return;
    }
    //  myDebug() << "Z3950Fetcher::stop()" << endl;
    m_started = false;
    if(m_conn)
    {
        m_conn->wait();
    }
    emit signalDone(this);
}


void Z3950Searcher::customEvent(QCustomEvent* event_)
{
    if(!m_conn)
    {
        return;
    }

    if(event_->type() == Z3950ResultFound::uid())
    {
        Z3950ResultFound* e = static_cast<Z3950ResultFound*>(event_);
        handleResult(e->result());
    }

    else if(event_->type() == Z3950QueryResult::uid())
    {
        Z3950QueryResult *e = static_cast<Z3950QueryResult*>(event_);
        m_waitingRetrieveRange = true;
        emit signalQueryResult(e->result());
    }
    else if(event_->type() == Z3950ConnectionDone::uid())
    {

        Z3950ConnectionDone* e = static_cast<Z3950ConnectionDone*>(event_);
        if(e->messageType() > -1)
        {
            emit signalMessage(e->message(), e->messageType());
        }
        m_conn->wait();
        stop();
    }

}

void Z3950Searcher::handleResult(const QString& result_)
{
//	std::cerr << result_.latin1();
	
	if(result_.isEmpty())
    {
        std::cerr << "Z3950Searcher::process() - empty record found, maybe the character encoding or record format is wrong?" << endl;
        return;
    }

    QString str;

	if(m_syntax == QString::fromLatin1("grs-1"))
    {
		//std::cerr << "---------------GRS-1------------------\n";
		//std::cerr << result_.latin1() << "\n";
		GRS1Importer imp(result_);
		BibEntry *bib = imp.import();
		if (bib)
		{
			emit signalResultFound(new BibEntry(*bib));
			delete bib;
		}
		
    }
    else
    {

        if(m_syntax == QString("mods"))
        {
            str = result_;
        }
#if HAVE_XSLT	        
		else if (m_syntax == QString("unimarc") && initUNIMARCHandler())
        {
            str = m_UNIMARCXMLHandler->applyStylesheet(result_);
        }
        else if(initMARC21Handler())
        {
            str = m_MARC21XMLHandler->applyStylesheet(result_);
        }
#endif
        if(str.isEmpty())
        {
            stop();
            return;
        }


        //write result to a tmp file
        QFile tmp_file(QString::fromLatin1("/tmp/kbib-z3950-mods.xml"));
        if(tmp_file.open(IO_WriteOnly ))
        {
            QTextStream t(&tmp_file);
            t.setEncoding(QTextStream::UnicodeUTF8);
            t << str;
        }
        tmp_file.close();
        //Convert mods to bibtex
        xml2bib("/tmp/kbib-z3950-mods.xml", "/tmp/kbib-z3950-mods.bib");
        //Read bibfile
		BibentryTable *entryList = new BibentryTable();
		entryList->readBibfile("/tmp/kbib-z3950-mods.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;
		

    }

}

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

    QString xsltfile = locate("appdata", QString::fromLatin1("MARC21slim2MODS3.xsl"));
    if(xsltfile.isEmpty())
    {
        std::cerr << "Z3950Fetcher::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 << "Z3950Fetcher::initHandlers() - error in MARC21slim2MODS3.xsl." << endl;
        delete m_MARC21XMLHandler;
        m_MARC21XMLHandler = 0;
        return false;
    }
    return true;
}
#endif

#if HAVE_XSLT	
bool Z3950Searcher::initUNIMARCHandler()
{
    if(m_UNIMARCXMLHandler)
    {
        return true;
    }
	QString xsltfile = locate("appdata", QString::fromLatin1("UNIMARC2MODS3.xsl"));
    if(xsltfile.isEmpty())
    {
        std::cerr  << "Z3950Fetcher::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 << "Z3950Fetcher::initHandlers() - error in UNIMARC2MODS3.xsl." << endl;
        delete m_UNIMARCXMLHandler;
        m_UNIMARCXMLHandler = 0;
        return false;
    }
    return true;
}
#endif

void Z3950Searcher::retrieveRange(unsigned int min, unsigned int max)
{
    m_waitingRetrieveRange = false;
    m_conn->retrieveRange(min, max);
}


void Z3950Searcher::saveConfig(KConfig* config_)
{
    config_->writeEntry("Host", m_host);
    config_->writeEntry("Port", m_port);
    config_->writeEntry("Database", m_dbname);
    config_->writeEntry("Charset", m_sourceCharSet);
    if (!m_user.isEmpty())
        config_->writeEntry("User", m_user);
    if (!m_password.isEmpty())
        config_->writeEntry("Password", m_password);
    config_->writeEntry("Syntax", m_syntax);
}


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

SearcherConfigWidget* Z3950Searcher::configWidget(QWidget* parent_)
{
    return new Z3950ConfigWidget(parent_, this);
}

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

    int row = 0;
    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, Z3950_DEFAULT_PORT, 10, optionsWidget());
    l->addWidget(m_portSpinBox, row, 1);
    w = i18n("Enter the port number of the server. The default is %1.").arg(Z3950_DEFAULT_PORT);
    QWhatsThis::add(label, w);
    QWhatsThis::add(m_portSpinBox, w);
    label->setBuddy(m_portSpinBox);

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

    label = new QLabel(i18n("Ch&aracter set: "), optionsWidget());
    l->addWidget(label, ++row, 0);
    m_charSetCombo = new KComboBox(true, optionsWidget());
    m_charSetCombo->insertItem(QString::null);
    m_charSetCombo->insertItem(QString::fromLatin1("marc8"));
    m_charSetCombo->insertItem(QString::fromLatin1("iso-8859-1"));
    m_charSetCombo->insertItem(QString::fromLatin1("utf-8"));
    l->addWidget(m_charSetCombo, row, 1);
    w = i18n("Enter the character set encoding used by the z39.50 server. The most likely choice "
             "is MARC-8, although ISO-8859-1 is common as well.");
    QWhatsThis::add(label, w);
    QWhatsThis::add(m_charSetCombo, w);
    label->setBuddy(m_charSetCombo);

    label = new QLabel(i18n("&Format: "), optionsWidget());
    l->addWidget(label, ++row, 0);
    m_syntaxCombo = new KComboBox(optionsWidget());
    m_syntaxCombo->insertItem(QString::fromLatin1("MODS"));
#if HAVE_XSLT	    
    m_syntaxCombo->insertItem(QString::fromLatin1("MARC21"));
    m_syntaxCombo->insertItem(QString::fromLatin1("UNIMARC"));
    m_syntaxCombo->insertItem(QString::fromLatin1("USMARC"));
#endif    
	m_syntaxCombo->insertItem(QString::fromLatin1("GRS-1"));
    l->addWidget(m_syntaxCombo, row, 1);
    w = i18n("Enter the data format used by the z39.50 server.");
    QWhatsThis::add(label, w);
    QWhatsThis::add(m_syntaxCombo, w);
    label->setBuddy(m_syntaxCombo);

    label = new QLabel(i18n("&User: "), optionsWidget());
    l->addWidget(label, ++row, 0);
    m_userEdit = new KLineEdit(optionsWidget());
    //m_userEdit->setHint(i18n("Optional"));
    l->addWidget(m_userEdit, row, 1);
    w = i18n("Enter the authentication user name used by the z39.50 database. Most servers "
             "do not need one.");
    QWhatsThis::add(label, w);
    QWhatsThis::add(m_userEdit, w);
    label->setBuddy(m_userEdit);

    label = new QLabel(i18n("Pass&word: "), optionsWidget());
    l->addWidget(label, ++row, 0);
    m_passwordEdit = new KLineEdit(optionsWidget());
    //m_passwordEdit->setHint(i18n("Optional"));
    m_passwordEdit->setEchoMode(QLineEdit::Password);
    l->addWidget(m_passwordEdit, row, 1);
    w = i18n("Enter the authentication password used by the z39.50 database. Most servers "
             "do not need one. The password will be saved in plain text in the Kbib "
             "configuration file.");
    QWhatsThis::add(label, w);
    QWhatsThis::add(m_passwordEdit, w);
    label->setBuddy(m_passwordEdit);

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

    if(searcher_)
    {
        m_hostEdit->setText(searcher_->m_host);
        m_portSpinBox->setValue(searcher_->m_port);
        m_databaseEdit->setText(searcher_->m_dbname);
        m_userEdit->setText(searcher_->m_user);
        m_passwordEdit->setText(searcher_->m_password);
        m_charSetCombo->setCurrentText(searcher_->m_sourceCharSet);
        m_syntax = searcher_->m_syntax;
        m_syntaxCombo->setCurrentText(m_syntax.upper());
    }
    KAcceleratorManager::manage(optionsWidget());
}

void Z3950ConfigWidget::updateSearcher()
{
    Z3950Searcher *s = static_cast<Z3950Searcher*>(m_searcher);
    s->m_host = m_hostEdit->text().stripWhiteSpace();
    s->m_port = m_portSpinBox->value();
    s->m_dbname = m_databaseEdit->text().stripWhiteSpace();
    s->m_user = m_userEdit->text();
    s->m_password = m_passwordEdit->text();
    s->m_sourceCharSet = m_charSetCombo->currentText();
    s->m_syntax = m_syntaxCombo->currentText().lower();
}


//#include "z3950searcher.moc"
