/*****************************************************************

   Copyright (c) 2006 Debajyoti Bera <dbera.web@gmail.com>

   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; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.

******************************************************************/

#include "beaglesearch.h"

#include <qdatetime.h>
#include <qmutex.h>
#include <qstringlist.h>
#include <qapplication.h>
#include <time.h>

void beagle_init ()
{
    g_type_init ();
}

// ---------------- Hit ---------------------------

Hit::Hit (BeagleHit *_hit) : processed (false)
{
    hit = beagle_hit_ref (_hit);
}

Hit::~Hit ()
{
    beagle_hit_unref (hit);
    if (! processed)
	return;
    QDictIterator<QStringList> it (property_map);
    for( ; it.current(); ++it )
        ((QStringList *)it.current())->clear ();

}

void Hit::processProperties ()
{
    processed = true;
    GSList *prop_list = beagle_hit_get_all_properties (hit);
    GSList *it;
    property_map.setAutoDelete (true);
    for (it = prop_list; it; it = it->next) {
        BeagleProperty *property = (BeagleProperty *) it->data;
        QString key = QString::fromUtf8 (beagle_property_get_key (property));
        if (! property_map [key])
            property_map.insert (key, new QStringList ());
        property_map [key]->append (QString::fromUtf8 (beagle_property_get_value (property)));
    }
    g_slist_free (prop_list);
}

const QString Hit::operator[] (QString prop_name)
{
    if (! processed)
	processProperties ();

    QStringList *prop_list = property_map [prop_name];
    if (! prop_list)
        return QString::null;
    if (prop_list->count () != 1)
        return QString::null;
    return (QString)prop_list->first ();
}

// ---------------- BeagleSearch ------------------

BeagleSearchResult::BeagleSearchResult(int client_id)
  : client_id (client_id), total (0)
{
    hitlist = new QPtrList<Hit>;
    hitlist->setAutoDelete (true);
}


BeagleSearchResult::~BeagleSearchResult()
{
    // everything is set to autodelete
}

void BeagleSearchResult::addHit(BeagleHit *_hit)
{
    Hit *hit = new Hit (_hit);
    hitlist->prepend (hit);
}

const QPtrList<Hit> *BeagleSearchResult::getHits () const
{
    return hitlist;
}


static int total_hits;

static void print_feed_item_hit (BeagleHit *hit)
{
    const char *text;

    if (beagle_hit_get_one_property (hit, "dc:title", &text))
        g_print ("Blog: %s\n", text);
}

static void print_file_hit (BeagleHit *hit)
{
    g_print ("File: %s, (%s)\n", beagle_hit_get_uri (hit), beagle_hit_get_mime_type (hit));
}

static void print_other_hit (BeagleHit *hit)
{
    const char *text;

    g_print ("%s (%s)", beagle_hit_get_uri (hit),
             beagle_hit_get_source (hit));
    if (beagle_hit_get_one_property (hit, "dc:title", &text))
        g_print ("title = %s\n", text);
}

static void print_hit (BeagleHit *hit) 
{
    if (strcmp (beagle_hit_get_type (hit), "FeedItem") == 0) {
        print_feed_item_hit (hit);
    } 
    else if (strcmp (beagle_hit_get_type (hit), "File") == 0) {
        print_file_hit (hit);
    } else {
        print_other_hit (hit);
    }
}

// ---------------- BeagleSearchClient ------------------

void BeagleSearchClient::run ()
{
    kdDebug () << "Starting query ..." << endl;

    QTime query_timer;
    query_timer.start ();
    
    g_signal_connect (query, "hits-added",
                      G_CALLBACK (hitsAddedSlot),
                      this);
    g_signal_connect (query, "finished",
                      G_CALLBACK (finishedSlot),
                      this);
    beagle_client_send_request_async (client,
                                      BEAGLE_REQUEST (query),
                                      NULL);
    g_main_loop_run (main_loop);
    kdDebug () << "Finished query ..." << endl;
    
    QCustomEvent *ev;
    if (collate_results) {
	result->query_msec = query_timer.elapsed ();
    
	ev =  new QCustomEvent (RESULTFOUND, result);
        QApplication::postEvent (object, ev);
    }

    ev =  new QCustomEvent (KILLME, this);
    QApplication::postEvent (object, ev);

}

void BeagleSearchClient::stopClient ()
{
   if (finished ())
       return; // duh!
   kdDebug () << "Query thread " << id << " not yet finished ..." << endl;
   // get ready for suicide
   client_mutex->lock ();
   kill_me = true;
   g_signal_handlers_disconnect_by_func (
           query, 
           (void *)hitsAddedSlot,
           this);
   g_signal_handlers_disconnect_by_func (
           query,
           (void *)finishedSlot,
           this);
   g_main_loop_quit (main_loop);
   client_mutex->unlock ();
}

void BeagleSearchClient::hitsAddedSlot (BeagleQuery *query,
                                        BeagleHitsAddedResponse *response,
                                        BeagleSearchClient *bsclient)
{
    GSList *hits, *l;
    gint    i;
    gint    nr_hits;

    // check if we are supposed to be killed
    bsclient->client_mutex->lock ();
    if (bsclient->kill_me) {
        kdDebug () << "Suicide time before processing" << endl;
	bsclient->client_mutex->unlock ();
	return;
    }
    bsclient->client_mutex->unlock ();

    hits = beagle_hits_added_response_get_hits (response);

    nr_hits = g_slist_length (hits);
    total_hits += nr_hits;
    g_print ("Found hits (%d) at %ld:\n", nr_hits, time (NULL));

    BeagleSearchResult *search_result;
    if (! bsclient->collate_results)
        search_result = new BeagleSearchResult (bsclient->id);
    else
        search_result = bsclient->result;
    search_result->total += nr_hits;

    for (l = hits, i = 1; l; l = l->next, ++i) {
        //g_print ("[%d] ", i);
        //print_hit (BEAGLE_HIT (l->data));
        //g_print ("\n");

        search_result->addHit(BEAGLE_HIT (l->data));//hit);
    }
    g_print ("[%ld] hits adding finished \n", time (NULL));

    // check if we are supposed to be killed
    bsclient->client_mutex->lock ();
    if (bsclient->kill_me) {
        kdDebug () << "Suicide time before sending ..." << endl;
	bsclient->client_mutex->unlock ();
	if (! bsclient->collate_results)
	    delete search_result;
	return;
    }
    bsclient->client_mutex->unlock ();

    // time to send back results, if user asked so
    if (bsclient->collate_results)
        return;
    QCustomEvent *ev =  new QCustomEvent (RESULTFOUND, search_result);
    g_print ("[%ld] event notified \n", time (NULL));
    QApplication::postEvent (bsclient->object, ev);
}

void BeagleSearchClient::finishedSlot (BeagleQuery *query,
                                       BeagleFinishedResponse *response,
                                       BeagleSearchClient *bsclient)
{
    // check if we are supposed to be killed
    bsclient->client_mutex->lock ();
    bool should_kill = bsclient->kill_me;
    QObject* receiver = bsclient->object;
    bsclient->client_mutex->unlock ();

    if (should_kill)
	return;

    g_main_loop_quit (bsclient->main_loop);

    if (bsclient->collate_results)
        return; // if we are collating, everything will be send from a central place
    if (receiver) {
        QCustomEvent *ev =  new QCustomEvent (SEARCHOVER, bsclient);
        g_print ("[%ld] query finish notified \n", time (NULL));
        QApplication::postEvent (receiver, ev);
    }
}

// ----------------- BeagleUtil -------------------

BeagleQuery *
BeagleUtil::createQueryFromString (QString query_str,
                                    QStringList &sources_menu,
                                    QStringList &types_menu,
				    int max_hits_per_source)
{
    BeagleQuery *beagle_query = beagle_query_new ();
    beagle_query_set_max_hits (beagle_query, max_hits_per_source); // this is per source!

    kdDebug () << "Creating query from \"" << query_str << "\"" << endl;
    for ( QStringList::Iterator it = sources_menu.begin(); it != sources_menu.end(); ++it )
        beagle_query_add_source (beagle_query, g_strdup ((*it).utf8 ()));

    for ( QStringList::Iterator it = types_menu.begin(); it != types_menu.end(); ++it )
        beagle_query_add_hit_type (beagle_query, g_strdup ((*it).utf8 ()));

    QStringList query_terms;
    QString start_date, end_date;
    QStringList words = QStringList::split (' ', query_str, false);
    for ( QStringList::Iterator it = words.begin(); it != words.end(); ++it ) {
        QStringList key_value_pair = QStringList::split ('=', *it, false);
        if (key_value_pair.count () == 1)
            query_terms += *it;
        else if (key_value_pair.count () == 2) {
            QString key = key_value_pair [0].lower ();
            QString value = key_value_pair [1];
            if (key == "mime")
                beagle_query_add_mime_type (beagle_query, g_strdup (value.utf8 ()));
            else if (key == "type")
                beagle_query_add_hit_type (beagle_query, g_strdup (value.utf8 ()));
            else if (key == "source")
                beagle_query_add_source (beagle_query, g_strdup (value.utf8 ()));
            else if (key == "start")
                start_date = value;
            else if (key == "end")
                end_date = value;
            else
                query_terms += *it;
        } else
            query_terms += *it;
    }

    beagle_query_add_text (beagle_query, g_strdup (query_terms.join (" ").utf8 ()));
    kdDebug () << "Adding query text:" << query_terms.join (" ").utf8 () << endl;

    if (start_date.isNull () && end_date.isNull ())
        return beagle_query;

    //kdDebug () << "Handling dates ..." << endl;
    BeagleQueryPartDate * date_part = beagle_query_part_date_new ();
    if (! start_date.isNull ())
        beagle_query_part_date_set_start_date (date_part, timestringToBeagleTimestamp (start_date));
    if (! end_date.isNull ())
        beagle_query_part_date_set_end_date (date_part, timestringToBeagleTimestamp (end_date));
    beagle_query_add_part (beagle_query, BEAGLE_QUERY_PART (date_part));

    return beagle_query;
}

// timestring format allowed YYYYmmDD
BeagleTimestamp *
BeagleUtil::timestringToBeagleTimestamp(QString timestring)
{
    //kdDebug () << "datetime string:" << timestring << endl;
    // FIXME: error check timestring format
    if (timestring.isNull () || timestring.stripWhiteSpace () == "" || timestring.length() != 8 )
        return beagle_timestamp_new_from_unix_time (QDateTime::currentDateTime ().toTime_t ());
    //QDateTime dt = QDateTime::fromString (timestring, Qt::ISODate);
    struct tm tm_time;
    time_t timet_time;
    time (&timet_time);
    localtime_r (&timet_time, &tm_time);
    strptime (timestring.ascii(), "%Y%m%d", &tm_time);
    tm_time.tm_sec = tm_time.tm_min = tm_time.tm_hour = 0;
    //kdDebug() << asctime (&tm_time) << endl;
    timet_time = mktime (&tm_time);
    return beagle_timestamp_new_from_unix_time (timet_time);
}

