//
// C++ Implementation: z3950connection
//
// Description:
//
//
// Author: Thach Nguyen <thach.nguyen@rmit.edu.au>, (C) 2006
//
// Copyright: See COPYING file that comes with this distribution
//
//
#include "z3950connection.h"
#include "searcher.h"
#include "z3950searcher.h"
#include <kapplication.h>
#include <iostream>
#include <fstream>
#include <qstring.h>
#include <klocale.h>

Z3950Connection::Z3950Connection(Z3950Searcher *searcher,
                                 const QString& host,
                                 uint port,
                                 const QString& dbname,
                                 const QString& sourceCharSet,
                                 const QString& syntax,
                                 const QString& esn)
        : QThread()
{
    m_connected = false;
    m_aborted = false;
    m_waitingRetrieveRange = false;
    m_searcher = searcher;
    m_host = QDeepCopy<QString>(host);
    m_port = port;
    m_dbname = QDeepCopy<QString>(dbname);
    m_sourceCharSet = QDeepCopy<QString>(sourceCharSet.left(64));
    m_syntax = QDeepCopy<QString>(syntax);
    m_esn = QDeepCopy<QString>(esn);
#if HAVE_YAZ
	conn = 0;
#endif
}


Z3950Connection::~Z3950Connection()
{
    m_connected = false;
#if HAVE_YAZ
	if (conn)
        delete conn;
#endif
}

void Z3950Connection::setQuery(const QString& query_)
{
    m_pqn = QDeepCopy<QString>(query_);
}

void Z3950Connection::setUserPassword(const QString& user_, const QString& pword_)
{
    m_user = QDeepCopy<QString>(user_);
    m_password = QDeepCopy<QString>(pword_);
}

void Z3950Connection::run()
{
    m_aborted = false;
	
#if HAVE_YAZ

    if(!makeConnection())
    {
        done();
        return;
    }

//    std::cerr << "Starting quering ... \n";
//    std::cerr << "Syntax " << m_syntax.latin1() << "\n";
//    std::cerr << "Element set name " << m_esn.latin1() << "\n";
//    std::cerr << "Query " << m_pqn.latin1() << "\n";
	QString errMsg;
	bool err = false;
    try {
        //Set preferredRecordSyntax and ElementSetName
        if (m_syntax == QString("mods") )
        {
         
			conn->option("preferredRecordSyntax", "xml");
            conn->option("elementSetName", "mods");

        }
        else
        {
           
			conn->option("preferredRecordSyntax", m_syntax.latin1());
            conn->option("elementSetName", m_esn.latin1());
    
		}
		
        ZOOM::resultSet rs(*conn, ZOOM::prefixQuery(m_pqn.latin1()));
        
		if(m_aborted)
        {
            done();
            return;
        }

       const size_t size = rs.size();
       if (size <1)
        {
			KApplication::postEvent((QObject*)m_searcher, new Z3950QueryResult(size) );
			done(i18n("No reference was found."), 1);
			return;	
		}
		else{
			startRange = 0;
			endRange = 0;
//			std::cerr << "found " << size << " records:\n";
            KApplication::postEvent((QObject*)m_searcher, new Z3950QueryResult(size) );
            m_waitingRetrieveRange = true;

            while(m_searcher->isWaitingRetrieveRange() )
            {
            }

			if (startRange > 0 && endRange > 0){
            for (size_t i = startRange; i <= endRange && !m_aborted; ++i)
            {
                const ZOOM::record rec(rs,i-1);
                QString data;
				if(m_syntax == QString::fromLatin1("mods"))
                {
                    data = toString((rec.rawdata()).c_str());
                }
				else if(m_syntax == QString::fromLatin1("grs-1")) { // grs-1
					data = toString((rec.render()).c_str());
				}
                else
                {
                    data = toXML((rec.rawdata()).c_str(), m_sourceCharSet);
                }
                //Signal searcher the data is available
                KApplication::postEvent((QObject*)m_searcher, new Z3950ResultFound(data) );
            }
			}
        }


   } catch (ZOOM::systemException &e) {
        std::cerr << "System error " << e.errcode() << " (" << e.errmsg() << ")" << std::endl;
		errMsg = i18n("System error %1: %2").arg(e.errcode()).arg( (e.errmsg() ).c_str() ); 
		err = true;
		
    }
	catch (ZOOM::bib1Exception e)
    {
        std::cerr << "BIB-1 error " <<
        e.errcode() << " (" << e.errmsg() << "): " << e.addinfo() << std::endl;
		errMsg = i18n("BIB-1 error %1: %2").arg(e.errcode()).arg( (e.errmsg() ).c_str() ); 
		err = true;
    }
	catch (ZOOM::queryException &e)
    {
        std::cerr << "Query error " <<
        e.errcode() << " (" << e.errmsg() << "): " << e.addinfo() << std::endl;
		errMsg = i18n("Query error %1: %2").arg(e.errcode()).arg( (e.errmsg() ).c_str() ); 
		err = true;
    }
	catch (ZOOM::exception &e)
    {
        std::cerr << "Error " <<
        e.errcode() << " (" << e.errmsg() << ")" << std::endl;
		errMsg = i18n("Connection error %1: %2").arg(e.errcode()).arg( (e.errmsg() ).c_str() ); 
		err = true;
    }
	
	if (err){
		done(errMsg, 0);
		return;	
	}
 	
	

#endif
    done();

}

bool Z3950Connection::makeConnection()
{
    if(m_connected)
    {
        return true;
    }
#if HAVE_YAZ
	QString errMsg;
    try    {
        conn = new ZOOM::connection(); //m_host.latin1(), m_port);
		conn->option("databaseName", m_dbname.latin1() );
        if (!m_user.isEmpty())
            conn->option("user", m_user.latin1() );
        if (!m_password.isEmpty())
            conn->option("password", m_password.latin1() );
    	m_connected = true;
	conn->connect(m_host.latin1(), m_port);
    }
	catch (ZOOM::systemException &e)
    {
        //std::cerr << "System error " << e.errcode() << " (" << e.errmsg() << ")" << endl;
	errMsg = i18n("System error %1: %2").arg(e.errcode()).arg( (e.errmsg() ).c_str() ); 
        m_connected = false;
    }

	catch (ZOOM::exception &e)
    {
        //std::cerr << "Error " << e.errcode() << " (" << e.errmsg() << ")" << std::endl;
		errMsg = i18n("Connection error %1: %2").arg(e.errcode()).arg( (e.errmsg() ).c_str() ); 
        m_connected = false;
    }
    if (!m_connected){
        std::cerr << errMsg.latin1() << "\n";
		done(errMsg, 0);
		return false;
    }

#endif
    m_connected = true;
    return true;

}

inline
QCString Z3950Connection::toCString(const QString& text_)
{
    return iconv(text_.utf8(), QString::fromLatin1("utf-8"), m_sourceCharSet);
}


inline
QString Z3950Connection::toString(const QCString& text_)
{
    return QString::fromUtf8(iconv(text_, m_sourceCharSet, QString::fromLatin1("utf-8")));
}

QCString Z3950Connection::iconv(const QCString& text_, const QString& fromCharSet_, const QString& toCharSet_)
{
//    std::cerr << "Converting from " << fromCharSet_.latin1() << " to " << toCharSet_.latin1() << "..............\n";
#if HAVE_YAZ
    if(text_.isEmpty())
    {
        return text_;
    }

    if(fromCharSet_ == toCharSet_)
    {
        return text_;
    }

    yaz_iconv_t cd = yaz_iconv_open(toCharSet_.latin1(), fromCharSet_.latin1());
    if(!cd)
    {
        /*    // maybe it's iso 5426, which we sorta support
        	  QString charSetLower = fromCharSet_.lower();
        	  charSetLower.remove('-').remove(' ');
        	  if(charSetLower == Latin1Literal("iso5426")) {
        		  return iconv(Iso5426Converter::toUtf8(text_).utf8(), QString::fromLatin1("utf-8"), toCharSet_);
        	  } else if(charSetLower == Latin1Literal("iso6937")) {
        		  return iconv(Iso6937Converter::toUtf8(text_).utf8(), QString::fromLatin1("utf-8"), toCharSet_);
        	  }
        	  kdWarning() << "Z3950Searcher::iconv() - conversion from " << fromCharSet_
        			  << " to " << toCharSet_ << " is unsupported" << endl;
        */
        return text_;
    }

    const char* input = text_;
    size_t inlen = text_.length();

    size_t outlen = 2 * inlen;  // this is enough, right?
    char* result0 = new char[outlen];
    char* result = result0;

    int r = yaz_iconv(cd, const_cast<char**>(&input), &inlen, &result, &outlen);
    if(r <= 0)
    {
        std::cerr << "Z3950Searcher::iconv() - can't decode buffer" << endl;
        return text_;
    }

    // length is pointer difference
    size_t len = result - result0;

    QCString output = QCString(result0, len+1);
    yaz_iconv_close(cd);
    delete result0;
    return output;
#endif
    return text_;
}

QString Z3950Connection::toXML(const QCString& marc_, const QString& charSet_)
{
	
	std::ofstream marc_file("USMARC.txt");
	marc_file << marc_ << "\n============================\n";
	marc_file.close();
	
#if HAVE_YAZ
    if(marc_.isEmpty())
    {
        std::cerr << "Z3950Searcher::toXML() - empty string" << endl;
        return QString::null;
    }

    yaz_iconv_t cd = yaz_iconv_open("utf-8", charSet_.latin1());
    if(!cd)
    {
        return QString::null;
    }

    yaz_marc_t mt = yaz_marc_create();
    yaz_marc_iconv(mt, cd);
    yaz_marc_xml(mt, YAZ_MARC_MARCXML);

    // first 5 bytes are length
    bool ok;
#if YAZ_VERSION_3_0_0	
    size_t len;
	const char* result;
#else
	int len;
	char* result;
#endif	
	len = marc_.left(5).toInt(&ok);
    if(ok && (len < 25 || len > 100000))
    {
        std::cerr << "Z3950Searcher::toXML() - bad length: " << (ok ? len : -1) << endl;
        return QString::null;
    }

    
    int r = yaz_marc_decode_buf(mt, marc_, -1, &result, &len);
    if(r <= 0)
    {
        std::cerr << "Z3950Searcher::toXML() - can't decode buffer" << endl;
        return QString::null;
    }

    QString output = QString::fromLatin1("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
    output += QString::fromUtf8(QCString(result, len+1), len+1);
    yaz_iconv_close(cd);
    yaz_marc_destroy(mt);

	std::ofstream xml_file("USMARC_XML.txt");
	xml_file << output << "\n";
	xml_file.close();
	
    return output;
#else // no yaz
    return QString::null;
#endif
}

void Z3950Connection::done() {
	kapp->postEvent(m_searcher, new Z3950ConnectionDone());
}

void Z3950Connection::done(const QString& msg_, int type_) {
	if(m_aborted) {
		kapp->postEvent(m_searcher, new Z3950ConnectionDone());
	} else {
		kapp->postEvent(m_searcher, new Z3950ConnectionDone(msg_, type_));
	}
}

void Z3950Connection::retrieveRange(unsigned int min, unsigned int max)
{
    startRange = min;
    endRange = max;
    m_waitingRetrieveRange = false;
}
		