/* Copyright (C) 2005 J.F.Dockes 
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include "autoconfig.h"

#include <QtXml/QXmlDefaultHandler>

#include "ssearch_w.h"

#include "guiutils.h"
#include "debuglog.h"
#include "xmltosd.h"
#include "smallut.h"
#include "recoll.h"

using namespace std;
using namespace Rcl;

class SDHXMLHandler : public QXmlDefaultHandler {
public:
    SDHXMLHandler()
        : isvalid(false)
    {
	resetTemps();
    }
    bool startElement(const QString & /* namespaceURI */,
		      const QString & /* localName */,
		      const QString &qName,
		      const QXmlAttributes &attributes);
    bool endElement(const QString & /* namespaceURI */,
		    const QString & /* localName */,
		    const QString &qName);
    bool characters(const QString &str)
    {
	currentText += str;
	return true;
    }

    // The object we set up
    RefCntr<SearchData> sd;
    bool isvalid;

private:
    void resetTemps() 
    {
	currentText = whatclause = "";
	text.clear();
	field.clear();
	slack = 0;
	d = m = y = di.d1 = di.m1 = di.y1 = di.d2 = di.m2 = di.y2 = 0;
	hasdates = false;
	exclude = false;
    }

    // Temporary data while parsing.
    QString currentText;
    QString whatclause;
    string field, text;
    int slack;
    int d, m, y;
    DateInterval di;
    bool hasdates;
    bool exclude;
};

bool SDHXMLHandler::startElement(const QString & /* namespaceURI */,
				const QString & /* localName */,
				const QString &qName,
				const QXmlAttributes &attrs)
{
    LOGDEB2(("SDHXMLHandler::startElement: name [%s]\n", 
	     (const char *)qName.toUtf8()));
    if (qName == "SD") {
        // Advanced search history entries have no type. So we're good
        // either if type is absent, or if it's searchdata
        int idx = attrs.index("type");
        if (idx >= 0 && attrs.value(idx).compare("searchdata")) {
            LOGDEB(("XMLTOSD: bad type\n"))
                return false;
        }
	resetTemps();
	// A new search descriptor. Allocate data structure
	sd = RefCntr<SearchData>(new SearchData);
	if (sd.isNull()) {
	    LOGERR(("SDHXMLHandler::startElement: out of memory\n"));
	    return false;
	}
    }	
    return true;
}

bool SDHXMLHandler::endElement(const QString & /* namespaceURI */,
                               const QString & /* localName */,
                               const QString &qName)
{
    LOGDEB2(("SDHXMLHandler::endElement: name [%s]\n", 
	     (const char *)qName.toUtf8()));

    if (qName == "CLT") {
	if (currentText == "OR") {
	    sd->setTp(SCLT_OR);
	}
    } else if (qName == "CT") {
	whatclause = currentText.trimmed();
    } else if (qName == "NEG") {
	exclude = true;
    } else if (qName == "F") {
	field = base64_decode(qs2utf8s(currentText.trimmed()));
    } else if (qName == "T") {
	text = base64_decode(qs2utf8s(currentText.trimmed()));
    } else if (qName == "S") {
	slack = atoi((const char *)currentText.toUtf8());
    } else if (qName == "C") {
	SearchDataClause *c;
	if (whatclause == "AND" || whatclause.isEmpty()) {
	    c = new SearchDataClauseSimple(SCLT_AND, text, field);
	    c->setexclude(exclude);
	} else if (whatclause == "OR") {
	    c = new SearchDataClauseSimple(SCLT_OR, text, field);
	    c->setexclude(exclude);
	} else if (whatclause == "EX") {
	    // Compat with old hist. We don't generete EX (SCLT_EXCL) anymore
	    // it's replaced with OR + exclude flag
	    c = new SearchDataClauseSimple(SCLT_OR, text, field);
	    c->setexclude(true);
	} else if (whatclause == "FN") {
	    c = new SearchDataClauseFilename(text);
	    c->setexclude(exclude);
	} else if (whatclause == "PH") {
	    c = new SearchDataClauseDist(SCLT_PHRASE, text, slack, field);
	    c->setexclude(exclude);
	} else if (whatclause == "NE") {
	    c = new SearchDataClauseDist(SCLT_NEAR, text, slack, field);
	    c->setexclude(exclude);
	} else {
	    LOGERR(("Bad clause type [%s]\n", qs2utf8s(whatclause).c_str()));
	    return false;
	}
	sd->addClause(c);
	whatclause = "";
	text.clear();
	field.clear();
	slack = 0;
	exclude = false;
    } else if (qName == "D") {
	d = atoi((const char *)currentText.toUtf8());
    } else if (qName == "M") {
	m = atoi((const char *)currentText.toUtf8());
    } else if (qName == "Y") {
	y = atoi((const char *)currentText.toUtf8());
    } else if (qName == "DMI") {
	di.d1 = d;
	di.m1 = m;
	di.y1 = y;
	hasdates = true;
    } else if (qName == "DMA") {
	di.d2 = d;
	di.m2 = m;
	di.y2 = y;
	hasdates = true;
    } else if (qName == "MIS") {
	sd->setMinSize(atoll((const char *)currentText.toUtf8()));
    } else if (qName == "MAS") {
	sd->setMaxSize(atoll((const char *)currentText.toUtf8()));
    } else if (qName == "ST") {
	string types = (const char *)currentText.toUtf8();
	vector<string> vt;
	stringToTokens(types, vt);
	for (unsigned int i = 0; i < vt.size(); i++) 
	    sd->addFiletype(vt[i]);
    } else if (qName == "IT") {
	string types(qs2utf8s(currentText));
	vector<string> vt;
	stringToTokens(types, vt);
	for (unsigned int i = 0; i < vt.size(); i++) 
	    sd->remFiletype(vt[i]);
    } else if (qName == "YD") {
	string d;
	base64_decode(qs2utf8s(currentText.trimmed()), d);
	sd->addClause(new SearchDataClausePath(d));
    } else if (qName == "ND") {
	string d;
	base64_decode(qs2utf8s(currentText.trimmed()), d);
	sd->addClause(new SearchDataClausePath(d, true));
    } else if (qName == "SD") {
	// Closing current search descriptor. Finishing touches...
	if (hasdates)
	    sd->setDateSpan(&di);
	resetTemps();
        isvalid = true;
    } 
    currentText.clear();
    return true;
}


RefCntr<Rcl::SearchData> xmlToSearchData(const string& xml)
{
    SDHXMLHandler handler;
    QXmlSimpleReader reader;
    reader.setContentHandler(&handler);
    reader.setErrorHandler(&handler);

    QXmlInputSource xmlInputSource;
    xmlInputSource.setData(QString::fromUtf8(xml.c_str()));

    if (!reader.parse(xmlInputSource) || !handler.isvalid) {
        LOGERR(("xmlToSearchData: parse failed for [%s]\n", 
                xml.c_str()));
        return RefCntr<SearchData>();
    }
    return handler.sd;
}


// Handler for parsing saved simple search data
class SSHXMLHandler : public QXmlDefaultHandler {
public:
    SSHXMLHandler()
        : isvalid(false)
        {
            resetTemps();
        }
    bool startElement(const QString & /* namespaceURI */,
		      const QString & /* localName */,
		      const QString &qName,
		      const QXmlAttributes &attributes);
    bool endElement(const QString & /* namespaceURI */,
		    const QString & /* localName */,
		    const QString &qName);
    bool characters(const QString &str)
        {
            currentText += str;
            return true;
        }

    // The object we set up
    SSearchDef data;
    bool isvalid;

private:
    void resetTemps() 
        {
            currentText = whatclause = "";
            text.clear();
        }

    // Temporary data while parsing.
    QString currentText;
    QString whatclause;
    string text;
};

bool SSHXMLHandler::startElement(const QString & /* namespaceURI */,
                                 const QString & /* localName */,
                                 const QString &qName,
                                 const QXmlAttributes &attrs)
{
    LOGDEB2(("SSHXMLHandler::startElement: name [%s]\n", 
	     (const char *)qName.toUtf8()));
    if (qName == "SD") {
        // Simple search saved data has a type='ssearch' attribute.
        int idx = attrs.index("type");
        if (idx < 0 && attrs.value(idx).compare("ssearch")) {
            LOGDEB(("XMLTOSSS: bad type\n"));
            return false;
        }
	resetTemps();
    }	
    return true;
}

bool SSHXMLHandler::endElement(const QString & /* namespaceURI */,
                               const QString & /* localName */,
                               const QString &qName)
{
    LOGDEB2(("SSHXMLHandler::endElement: name [%s]\n", 
	     (const char *)qName.toUtf8()));

    currentText = currentText.trimmed();

    if (qName == "SL") {
        stringToStrings(qs2utf8s(currentText), data.stemlangs);
    } else if (qName == "T") {
        base64_decode(qs2utf8s(currentText), data.text);
    } else if (qName == "EX") {
        data.extindexes.push_back(base64_decode(qs2utf8s(currentText)));
    } else if (qName == "SM") {
        if (!currentText.compare("QL")) {
            data.mode = SSearch::SST_LANG;
        } else if (!currentText.compare("FN")) {
            data.mode = SSearch::SST_FNM;
        } else if (!currentText.compare("OR")) {
            data.mode = SSearch::SST_ANY;
        } else if (!currentText.compare("AND")) {
            data.mode = SSearch::SST_ALL;
        } else {
            LOGERR(("BAD SEARCH MODE: [%s]\n", qs2utf8s(currentText).c_str()));
            return false;
        }
    } else if (qName == "AS") {
        stringToStrings(qs2utf8s(currentText), data.autosuffs);
    } else if (qName == "AP") {
        data.autophrase = true;
    } else if (qName == "SD") {
	// Closing current search descriptor. Finishing touches...
	resetTemps();
        isvalid = true;
    } 
    currentText.clear();
    return true;
}

bool xmlToSSearch(const string& xml, SSearchDef& data)
{
    SSHXMLHandler handler;
    QXmlSimpleReader reader;
    reader.setContentHandler(&handler);
    reader.setErrorHandler(&handler);

    QXmlInputSource xmlInputSource;
    xmlInputSource.setData(QString::fromUtf8(xml.c_str()));

    if (!reader.parse(xmlInputSource) || !handler.isvalid) {
        LOGERR(("xmlToSSearch: parse failed for [%s]\n", 
                xml.c_str()));
        return false;
    }
    data = handler.data;
    return true;
}
