/***************************************************************************
 *   Copyright (C) 2005 by Debajyoti Bera <dbera.web@gmail.com>            *
 *   Copyright (C) 2005 Novell, Inc.                                       *
 *                                                                         *
 *   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.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA           *
 ***************************************************************************/

#include "beaglesearch.h"
#include <tdemessagebox.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <tqapplication.h>
#include <kurl.h>

struct PropertyInfo
{
	int tilegroup;
	const char* identifier;
	const char* label;
};

static const PropertyInfo propertiesInfo[] =
{
/*       { BeagleSearch::Application, "fixme:Name", I18N_NOOP("Name: %1")"<br>" },
       { BeagleSearch::Application, "fixme:Comment", I18N_NOOP("Comment: %1")"<br>" },
       { BeagleSearch::Application, "fixme:Categories", I18N_NOOP("Categories: %1")"<br>" },*/
       { BeagleSearch::Application, "fixme:Name", 0 },
       { BeagleSearch::Audio, "fixme:title", 0 },
       { BeagleSearch::Audio, "fixme:album", I18N_NOOP("Album: %1")"<br>" },
       { BeagleSearch::Audio, "fixme:ExactFilename", I18N_NOOP("Title: %1")"<br>" },
       { BeagleSearch::Audio, "fixme:artist", I18N_NOOP("Artist: %1")"<br>" },
       { BeagleSearch::Image, "fixme:width", I18N_NOOP("Width: %1")"&nbsp;&nbsp;&nbsp;" },
       { BeagleSearch::Image, "fixme:height", I18N_NOOP("Height: %1")"&nbsp;&nbsp;&nbsp;" },
       { BeagleSearch::Image, "fixme:colortype", I18N_NOOP("Color type: %1")"<br>" },
       { BeagleSearch::Contact, "fixme:Name", 0 },
       { BeagleSearch::Contact, "fixme:Email1", 0 },
       { BeagleSearch::Contact, "vCard:FN", 0 },
       { BeagleSearch::Contact, "vCard:EMAIL", 0 },
       { BeagleSearch::Contact, "fixme:BusinessPhone", I18N_NOOP("Business phone: %1")"<br>" },
       { BeagleSearch::Contact, "fixme:HomePhone", I18N_NOOP("Home phone: %1")"<br>" },
       { BeagleSearch::Contact, "fixme:MobilePhone", I18N_NOOP("Mobile phone: %1")"<br>" },
       { BeagleSearch::Contact, "fixme:ImAim", I18N_NOOP("AIM: %1")"<br>" },
       { BeagleSearch::Contact, "fixme:ImYahoo", I18N_NOOP("Yahoo: %1")"<br>" },
       { BeagleSearch::Contact, "fixme:ImMsn", I18N_NOOP("MSN: %1")"<br>" },
       { BeagleSearch::Contact, "fixme:ImJabber", I18N_NOOP("Jabber: %1")"<br>" },
       { BeagleSearch::Contact, "fixme:ImIcq", I18N_NOOP("ICQ: %1")"<br>" },
       { BeagleSearch::Contact, "fixme:ImGroupWise", I18N_NOOP("GroupWise: %1")"<br>" },
//       { BeagleSearch::Conversations, "fixme:speakingto", I18N_NOOP("From: %1")"<br>" },
       { BeagleSearch::Conversations, "fixme:speakingto", 0 },
       { BeagleSearch::Conversations, "fixme:protocol", I18N_NOOP("Protocol: %1")"<br>" },
       { BeagleSearch::Conversations, "fixme:starttime", I18N_NOOP("Start time: %1")"<br>" },
       { BeagleSearch::Conversations, "fixme:endtime", I18N_NOOP("End time: %1")"<br>" },
       { BeagleSearch::Conversations, "dc:title", 0 },
       { BeagleSearch::Conversations, "fixme:from", 0 },
       { BeagleSearch::Conversations, "fixme:date", 0 },
       { BeagleSearch::Conversations, "fixme:folder", I18N_NOOP("Folder: %1")"&nbsp;" },
       { BeagleSearch::Conversations, "fixme:account", I18N_NOOP("(%1)")"<br>" },
/*       { BeagleSearch::Conversations, "fixme:isAnswered", I18N_NOOP("Was answered.")"&nbsp;&nbsp;&nbsp;" },
       { BeagleSearch::Conversations, "fixme:isSeen", I18N_NOOP("Was seen.")"&nbsp;&nbsp;&nbsp;" },
       { BeagleSearch::Conversations, "fixme:hasAttachments", I18N_NOOP("Has Attachments.")"&nbsp;&nbsp;&nbsp;" },*/
// Presentation
       { BeagleSearch::Documents, "dc:title", I18N_NOOP("Title: %1")"<br>" },
       { BeagleSearch::Documents, "dc:author", I18N_NOOP("Author: %1")"<br>" },
       { BeagleSearch::Documents, "fixme:slide-count", I18N_NOOP("Slides: %1")"&nbsp;&nbsp;&nbsp;" },
// Text Document
       { BeagleSearch::Documents, "fixme:page-count", I18N_NOOP("Pages: %1")"&nbsp;&nbsp;&nbsp;" },
       { BeagleSearch::Documents, "fixme:word-count", I18N_NOOP("Words: %1") },
       { BeagleSearch::Feed, "dc:title", 0 },
       { BeagleSearch::Feed, "dc:identifier", 0 },
/*       { BeagleSearch::Feed, "dc:date", I18N_NOOP("Date: %1")"<br>" },
       { BeagleSearch::Feed, "dc:source", I18N_NOOP("Source: %1")"<br>" },*/
       { BeagleSearch::Website, "dc:title", 0 },
       { BeagleSearch::Website, "Title", 0 },
       { BeagleSearch::Note, "dc:title", 0 },
// Packages (ebuild, RPM, etc.)
       { BeagleSearch::Packages, "dc:title", I18N_NOOP("Title: %1")"<br>" },
       { BeagleSearch::Packages, "dc:subject", 0 },
       { BeagleSearch::Packages, "fixme:category", I18N_NOOP("Category: %1")"<br>" },
       { BeagleSearch::Packages, "fixme:version", I18N_NOOP("Version: %1")"<br>" },
       { BeagleSearch::Packages, "dc:source", 0 },
       { BeagleSearch::Packages, "fixme:packager_name", I18N_NOOP("Packager name: %1")"<br>" },
       { BeagleSearch::Packages, "fixme:packager_email", I18N_NOOP("Packager email: %1")"<br>" },
       { BeagleSearch::Packages, "fixme:size", 0 },
       { BeagleSearch::Packages, "fixme:install_time", 0 },
       { BeagleSearch::Packages, "fixme:contents_byte_count", 0 },
       { BeagleSearch::Unknown, "dc:author", I18N_NOOP("Author: %1")"<br>" },
       { BeagleSearch::Unknown, "dc:title", I18N_NOOP("Title: %1")"<br>" },
// Calendar
       { BeagleSearch::Unknown, "fixme:summary", 0 },
       { BeagleSearch::Unknown, "fixme:starttime", 0 },
       { BeagleSearch::Unknown, "fixme:endtime", 0 },
       { BeagleSearch::Unknown, "fixme:location", I18N_NOOP("Location: %1")"<br>" },
       { BeagleSearch::Unknown, "fixme:page-count", I18N_NOOP("Pages: %1")"<br>" },
//       { BeagleSearch::, "", I18N_NOOP(": %1")+"<br>" },
//       { "", "", I18N_NOOP("") },
       { 0, 0, 0}
};

struct HitFlavorInfo
{
	int         tilegroup;
	const char* uri;
	const char* type;
	const char* mimetype;
};

static const HitFlavorInfo hitflavorinfos[] =
{
       { BeagleSearch::Application, 0, 0, "application/x-desktop" },
       { BeagleSearch::Audio, 0, 0, "audio/" },
       { BeagleSearch::Audio, 0, 0, "application/ogg" },
       { BeagleSearch::Contact, 0, "Contact", 0 },
       { BeagleSearch::Conversations, 0, "IMLog", 0 },
       { BeagleSearch::Conversations, 0, "MailMessage", 0 },
       { BeagleSearch::Conversations,"email://", 0, 0 },
       { BeagleSearch::Feed, 0, "FeedItem", 0 },
       { BeagleSearch::Folder, 0, 0, "inode/directory" },
       { BeagleSearch::Folder, 0, 0, "x-directory/normal" },
       { BeagleSearch::Image, "file://", 0, "image/" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.sun.xml.impress.template" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.sun.xml.impress" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.ms-powerpoint" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.oasis.opendocument.presentation" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.oasis.opendocument.presentation" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.sun.xml.calc" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.sun.xml.calc.template" },
       { BeagleSearch::Documents, 0, 0, "application/excel" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.ms-excel" },
       { BeagleSearch::Documents, 0, 0, "application/x-excel" },
       { BeagleSearch::Documents, 0, 0, "application/x-msexcel" },
       { BeagleSearch::Documents, 0, 0, "application/x-gnumeric" },
       { BeagleSearch::Documents, 0, 0, "text/spreadsheet" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.oasis.opendocument.spreadsheet" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.oasis.opendocument.spreadsheet.template" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.sun.xml.writer" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.sun.xml.writer.template" },
       { BeagleSearch::Documents, 0, 0, "application/msword" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.ms-word" },
       { BeagleSearch::Documents, 0, 0, "application/x-msword" },
       { BeagleSearch::Documents, 0, 0, "application/pdf" },
       { BeagleSearch::Documents, 0, 0, "application/x-abiword" },
       { BeagleSearch::Documents, 0, 0, "application/rtf" },
       { BeagleSearch::Documents, 0, 0, "application/x-chm" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.oasis.opendocument.text" },
       { BeagleSearch::Documents, 0, 0, "application/vnd.oasis.opendocument.text.template" },
       { BeagleSearch::Note, "note://", 0, 0 },
       { BeagleSearch::Note, "knotes://", 0, 0 },
       { BeagleSearch::Video, 0, 0, "video/" },
       { BeagleSearch::Website, 0, "Google", 0 },
       { BeagleSearch::Website, 0, "WebHistory", 0 },
       { BeagleSearch::Website, 0, 0, "beagle/x-konq-cache" },
       { BeagleSearch::Packages, "*.ebuild", 0, 0 },
       { BeagleSearch::Packages, "*.rpm", 0, "application/x-rpm" },
       { BeagleSearch::Packages, "*.deb", 0, "application/x-deb" },
       { BeagleSearch::Unknown, "file://", "File", 0 },  // mimetype wildcard must be last
       { 0, 0, 0, 0}
};

BeagleSearch::BeagleSearch(int id, TQObject *parent, TQString term)
	: id (id), kill_me (false), parent (parent)
{
                query = beagle_query_new ();
		beagle_query_set_max_hits(query, 100);
		beagle_query_add_text (query, term.ascii());

		client = beagle_client_new (NULL);
		
		client_mutex = new TQMutex ();

		main_loop = g_main_loop_new (NULL, FALSE);
}

void BeagleSearch::run()
{
    g_signal_connect (query, "hits-added",
                      G_CALLBACK (hits_added_cb),
                      this);
    g_signal_connect (query, "hits-subtracted",
                      G_CALLBACK (hits_subtracted_cb),
                      this);
    g_signal_connect (query, "finished",
                      G_CALLBACK (finished_cb),
                      this);
    beagle_client_send_request_async (client,
                                      BEAGLE_REQUEST (query),
                                      NULL);
    g_main_loop_run (main_loop);
    kdDebug () << "Finished query ..." << endl;
 
    bool run = true;
    while (run) {
       g_main_context_iteration(0, false);
       sleep(1);
       client_mutex->lock ();
       run = !kill_me;
       client_mutex->unlock ();
    }
    kdDebug() << "!!! run ending" << endl;

    TQCustomEvent *ev;
    ev =  new TQCustomEvent (KILLME, this);
    tqApp->postEvent(parent, ev);
}

void BeagleSearch::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 *)hits_added_cb,
           this);
   g_signal_handlers_disconnect_by_func (
           query, 
           (void *)hits_subtracted_cb,
           this);
   g_signal_handlers_disconnect_by_func (
           query,
           (void *)finished_cb,
           this);
   g_main_loop_quit (main_loop);
   client_mutex->unlock ();
}

BeagleSearch::~BeagleSearch()
{
	if (! finished ()) {
	    kdDebug () << "Thread " << id << " still running. Waiting.........." << endl;
	    wait ();
	}

        g_object_unref (client);
        g_main_loop_unref (main_loop);
        g_object_unref (query);
        kdDebug() << "Deleting client ..." << id << endl;

	delete client_mutex;
}

TQString *BeagleSearch::get_uri_from_file_hit(BeagleHit *hit)
{
        return new TQString (beagle_hit_get_uri (hit));
}

TQString *BeagleSearch::get_parent_uri_from_file_hit(BeagleHit *hit)
{
	return new TQString (beagle_hit_get_parent_uri (hit));
}

TQString *BeagleSearch::get_source_from_file_hit(BeagleHit *hit)
{
	return new TQString (beagle_hit_get_source (hit));
}

TQString *BeagleSearch::get_uri_from_feed_hit(BeagleHit *hit)
{
        const char *result;
	beagle_hit_get_one_property (hit, "fixme:itemuri", &result);
	return new TQString (result);
}

void BeagleSearch::hits_added_cb (BeagleQuery *query, BeagleHitsAddedResponse *response, BeagleSearch *client) 
{
	GSList *hits, *l;
	gint    i;
	gint    nr_hits;
	BeagleResultList* results;
	results = new BeagleResultList;

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

        // This is necessary only once, not for each item
	BeagleSnippetRequest *snippetrequest = beagle_snippet_request_new();
	beagle_snippet_request_set_query(snippetrequest, query);
	
	hits = beagle_hits_added_response_get_hits (response);
	nr_hits = g_slist_length (hits);
	kdDebug() << "---------- hits added:" << nr_hits << endl;
	for (l = hits, i = 1; l; l = l->next, ++i) {
		//print_hit (BEAGLE_HIT (l->data));
		beagle_result_struct *result = new beagle_result_struct;
		BeagleHit *hit = BEAGLE_HIT (l->data);

		result->hit_type = TQString(beagle_hit_get_type (BEAGLE_HIT (l->data)));

		const char *_mime_type = beagle_hit_get_mime_type(BEAGLE_HIT (l->data));
		const char *mime_type = (_mime_type == NULL ? "-" : _mime_type);

		if (result->hit_type=="MailMessage" && TQString(mime_type)!="message/rfc822") {
			delete result;
			continue;
		}

		result->mime_type = new TQString(mime_type);
		result->score = beagle_hit_get_score(BEAGLE_HIT (l->data));
		result->uri = get_uri_from_file_hit (BEAGLE_HIT (l->data));
		result->tilegroup = Unknown;
		result->parent_uri = get_parent_uri_from_file_hit (BEAGLE_HIT (l->data));
		result->source = get_source_from_file_hit (BEAGLE_HIT (l->data));
		result->client_id = client->id;
#if 0
		kdDebug() << "Properties of " << *(result->uri) << endl;
		GSList *properties, *p;
		gint    i;
		properties = beagle_hit_get_all_properties (hit);
		for (p = properties, i = 1; p; p = p->next, ++i) {
			BeagleProperty* property = (BeagleProperty*)p->data;
			const char* key = beagle_property_get_key( property );
			const char* value = beagle_property_get_value( property );
			kdDebug() << " \"" << key << "\" = \"" << value << "\"" << endl;
		}
		kdDebug() << "parent_uri = " << *(result->parent_uri) << endl;
		kdDebug() << "source = " << *(result->source) << endl;
		kdDebug() << "tilegroup = " << result->tilegroup << endl;
#endif

		for( uint i = 0; hitflavorinfos[i].tilegroup!=0; i++ ) {
			if( (hitflavorinfos[i].uri == 0 || (*(result->uri)).startsWith(hitflavorinfos[i].uri) || (hitflavorinfos[i].uri[0]=='*' && (*(result->uri)).endsWith(hitflavorinfos[i].uri+1))) &&
			    (hitflavorinfos[i].type == 0 || hitflavorinfos[i].type==result->hit_type) &&
			    (hitflavorinfos[i].mimetype == 0 || (*(result->mime_type)).startsWith(hitflavorinfos[i].mimetype))) {
				result->tilegroup = (TileGroup)hitflavorinfos[i].tilegroup;
				break;
			}
		}

		for( uint i = 0; propertiesInfo[i].identifier!=0; i++ ) {
			if( propertiesInfo[i].tilegroup==0 || propertiesInfo[i].tilegroup==result->tilegroup ) {
				GSList *properties = beagle_hit_get_properties( hit, propertiesInfo[i].identifier );
				if (g_slist_length(properties)>0) {
	                                const char *property = (char*)properties->data;
					if (property) {
						if (propertiesInfo[i].label) {
							TQString propertystr = i18n(propertiesInfo[i].label).arg(property);
							result->properties.append(propertystr);
						}
						else
							result->properties.append(TQString(propertiesInfo[i].identifier)+'='+property);
					 }
				}
                        }
		}

		beagle_snippet_request_set_hit(snippetrequest, hit);

		GError *err = NULL;
		BeagleResponse *response;
		response = beagle_client_send_request (client->client, BEAGLE_REQUEST (snippetrequest), &err);
		if (err) {
			g_error_free (err);
			result->snippet = 0;
		}
		else
			result->snippet = new TQString( beagle_snippet_response_get_snippet( BEAGLE_SNIPPET_RESPONSE(response)) );

                if (response)
                        g_object_unref(response);

		BeagleTimestamp *timestamp = beagle_hit_get_timestamp (BEAGLE_HIT (l->data));
		time_t	index_time;
		if (beagle_timestamp_to_unix_time (timestamp, &index_time))
			result->last_index_time = index_time;
		else
			result->last_index_time = 0;
		
		results->append (result);
	}

	g_object_unref(snippetrequest);
	
        // check if we are supposed to be killed
        client->client_mutex->lock ();
        if (client->kill_me) {
           kdDebug () << "Suicide time before sending ..." << endl;
	   client->client_mutex->unlock (); 
	   delete results;
	   return;
        }
        client->client_mutex->unlock ();

	TQCustomEvent *ev =  new TQCustomEvent (RESULTFOUND, results);
	tqApp->postEvent (client->parent, ev);
}

void BeagleSearch::hits_subtracted_cb (BeagleQuery */*query*/, BeagleHitsSubtractedResponse *response, BeagleSearch *client) 
{
	GSList *uris;
	gint    nr_hits;
	BeagleVanishedURIList* vanished;
	vanished = new BeagleVanishedURIList(); 
	vanished->client_id = client->id;
	
        // check if we are supposed to be killed
        client->client_mutex->lock ();
        if (client->kill_me) {
             kdDebug () << "Suicide time before sending ..." << endl;
	     client->client_mutex->unlock ();
	    return;
        }
	client->client_mutex->unlock ();

	uris = beagle_hits_subtracted_response_get_uris (response);
	nr_hits = g_slist_length (uris);
	kdDebug() << "---------- hits subtracted:" << nr_hits << endl;
        while (uris != NULL) {
                char *uri = (char*)uris->data;

                g_print ("removed: %s\n", uri);

		vanished->list.append(TQString(uri));
                uris = uris->next;
        }
	
	TQCustomEvent *ev =  new TQCustomEvent (RESULTGONE, vanished);
	tqApp->postEvent (client->parent, ev);
}

void BeagleSearch::finished_cb (BeagleQuery */*query*/,
	     BeagleFinishedResponse */*response*/,
	     BeagleSearch *client)
{
	kdDebug() << "---------- finished" << endl;

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

	g_main_loop_quit (client->main_loop);

	TQCustomEvent *ev =  new TQCustomEvent (SEARCHOVER, client);
	tqApp->postEvent (client->parent, ev);
}
