/***************************************************************************
 *   Copyright (C) 2006 by Stephan Binner <binner@kde.org>                 *
 *   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; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
 ***************************************************************************/

#include "kickoff-beagle-plugin.h"

#include <tqregexp.h>
#include <tqtimer.h>

#include <kapplication.h>
#include <kdesktopfile.h>
#include <kgenericfactory.h>
#include <kservice.h>

TQString dc_identifier = "dc:identifier";
TQString dc_title = "dc:title";
TQString parent_dc_title = "parent:dc:title";
TQString exactfilename = "beagle:ExactFilename";
TQString fixme_name = "fixme:Name";
TQString beagle_filename = "beagle:Filename";
TQString fixme_attachment_title = "fixme:attachment_title";
TQString fixme_hasattachments = "fixme:hasAttachments";
TQString parent_prefix = "parent:";
TQString fixme_folder = "fixme:folder";
TQString fixme_categories = "fixme:Categories";
TQString fixme_comment = "fixme:Comment";
TQString fixme_width = "fixme:width";
TQString fixme_height = "fixme:height";
TQString fixme_from_address = "fixme:from_address";
TQString fixme_artist = "fixme:artist";
TQString fixme_album = "fixme:album";
TQString dc_source = "dc:source";
TQString dc_publisher = "dc:publisher";
TQString digikam_tag = "digikam:Tag";
TQString fixme_speakingto = "fixme:speakingto";
TQString fixme_starttime = "fixme:starttime";
TQString comma_string = ",";
TQString vCard_FN = "vCard:FN";
TQString vCard_PREFEMAIL = "vCard:PREFEMAIL";
TQString fixme_uid = "fixme:uid";

static CATEGORY getHitCategory (Hit *hit)
{
    TQString hittype = hit->getType();
    TQString hitsource = hit->getSource();

    // if hit source is None, dont handle it. Might be anthrax-envelope :)
    if (hitsource.isNull())
        return OTHER;

    if (hitsource == "documentation")
        return DOCS;

    if (hittype == "IMLog")
        return CHATS;

    // sure shots
    if (hittype == "FeedItem")
        return FEEDS;
    if (hittype == "WebHistory")
        return WEBHIST;
    if (hittype == "MailMessage")
        return MAILS;
    if (hittype == "Note")
        return NOTES;

    // check for applications
    if (hittype == "File" && (*hit) ["beagle:FilenameExtension"] == ".desktop")
        return APPS;

    // check for music
    TQString hitmimetype = hit->getMimeType();
    if (hitsource == "Amarok"
        || hitmimetype.startsWith ("audio")
        || hitmimetype == "application/ogg")
        return MUSIC; // not an exhaustive search

    // check for images from files
    if (hitsource == "Files" && hitmimetype.startsWith ("image"))
        return PICS;

    if (hitsource == "Files" && hitmimetype.startsWith ("video"))
        return VIDEOS;

    if (hitsource == "Files")
        return FILES;

    if (hitsource == "KAddressBook")
        return ACTIONS;

    return OTHER;
}

K_EXPORT_COMPONENT_FACTORY( kickoffsearch_beagle,
                            KGenericFactory<KickoffBeaglePlugin>( "kickoffsearch_beagle" ) )

KickoffBeaglePlugin::KickoffBeaglePlugin(TQObject *parent, const char* name, const TQStringList&)
            : KickoffSearch::Plugin(parent, name ), genericTitle( true )
{
    g_type_init ();
    current_beagle_client = NULL;
}

bool KickoffBeaglePlugin::daemonRunning()
{
    return beagle_util_daemon_is_running();
}

void KickoffBeaglePlugin::query(TQString term, bool _genericTitle)
{
    genericTitle = _genericTitle;
    current_query_str = term;

    // Beagle search
    if (current_beagle_client != NULL) {
	kdDebug () << "Previous client w/id " << current_beagle_client->id << " still running ... ignoring it." << endl;
	current_beagle_client->stopClient ();
    }
    current_beagle_client_id = KApplication::random ();
    kdDebug () << "Creating client with id:" << current_beagle_client_id << endl;

    BeagleClient *beagle_client = beagle_client_new (NULL);
    if (beagle_client == NULL) {
        kdDebug() << "beagle service not running ..." << endl;
        return;
    }

    TQStringList sources, types;
    BeagleQuery *beagle_query = BeagleUtil::createQueryFromString (term, sources, types, 99); // maximum 99 results, if this doesnt work, blame the stars

    current_beagle_client = new BeagleSearchClient (
                                current_beagle_client_id,
                                this,
                                beagle_client,
                                beagle_query,
                                false);
    current_beagle_client->start();
//    kdDebug () << "Query dispatched at " << time (NULL) << endl;
}

void KickoffBeaglePlugin::cleanClientList ()
{
    toclean_list_mutex.lock ();
    BeagleSearchClient *old_client = toclean_client_list.take (0);
    if (old_client != NULL) { // failsafe
	kdDebug () << "Cleanup old client " << old_client->id << endl;
	delete old_client;
    }
    toclean_list_mutex.unlock ();
}

void KickoffBeaglePlugin::customEvent (TQCustomEvent *e)
{
    if (e->type () == RESULTFOUND) {
//        kdDebug () << "Quick query thread at " << time (NULL) << " with current_id=" << current_beagle_client_id <<  " finished ..." << endl;
        BeagleSearchResult *result = (BeagleSearchResult *) e->data ();
        if (current_beagle_client_id != result->client_id) {
            kdDebug () << "Stale result from " << result->client_id << endl;
	    delete result;
	    // FIXME: Should I also free e ?
        } else {
            kdDebug () << "Good results ...total=" << result->total << endl;
            showResults (result);
        }
        //KPassivePopup::message( "This is the message", this );
    } else if (e->type () == SEARCHOVER) {
        BeagleSearchClient *client = (BeagleSearchClient *) e->data ();
	if (client == NULL) {
//	    kdDebug () << "Query finished event at " << time (NULL) << " but client is already deleted" << endl;
	    return;
	}
//        kdDebug () << "Query finished event at " << time (NULL) << " for id=" << client->id << endl;
	if (current_beagle_client_id == client->id) {
	    kickoffSearchInterface()->searchOver();
 	    current_beagle_client = NULL; // important !
	}
    } else if (e->type () == KILLME) {
        BeagleSearchClient *client = (BeagleSearchClient *) e->data ();
	if (client->finished ())
	    delete client;
	else {
	    // add client to cleanup list
	    toclean_list_mutex.lock ();
	    toclean_client_list.append (client);
	    kdDebug () << "Scheduling client to be deleted in 500ms" << endl;
	    toclean_list_mutex.unlock ();
	    TQTimer::singleShot (500, this, TQT_SLOT (cleanClientList ()));
	}
    }
}

// this method decides what to display in the result list
HitMenuItem *KickoffBeaglePlugin::hitToHitMenuItem (int category, Hit *hit)
{
    TQString title, info, mimetype, icon;
    int score = 0;
    KURL uri;

#if 0
    kdDebug() << "*** " << hit->getUri() << endl;
    TQDict<TQStringList> all = hit->getAllProperties();
    TQDictIterator<TQStringList> it( all );
    for( ; it.current(); ++it )
        kdDebug() << it.currentKey() << ": " << *(it.current()) << endl;
#endif

    switch (category) {
	case FILES:
	    {
		uri = hit->getUri ();
		TQString uristr = uri.path ();
	    	title = (*hit) [exactfilename];
	    	int last_slash = uristr.findRev ('/', -1);
                info = i18n("Folder: %1").arg(last_slash == 0 ? "/" 
                        : uristr.section ('/', -2, -2));
	    }
	    break;
        case ACTIONS:
            {
                if (hit->getSource()=="KAddressBook"){
		    title = i18n("Send Email to %1").arg((*hit)[vCard_FN]);
		    info = (*hit)[vCard_PREFEMAIL];
		    uri = "mailto:"+(*hit)[vCard_PREFEMAIL];
		    mimetype = hit->getMimeType ();
		    icon = "mail_new";

		    HitMenuItem * first_item=new HitMenuItem (title, info, uri, mimetype, 0, category, icon, score);
		    kickoffSearchInterface()->addHitMenuItem(first_item);

		    title =i18n("Open Addressbook at %1").arg((*hit)[vCard_FN]);
		    uri = "kaddressbook:/"+(*hit)[fixme_uid];
		    icon = "kaddressbook";
                }
                break;
            }
	case MAILS:
	    {
		TQString prefix = TQString::null;
		bool is_attachment = ((*hit) [parent_prefix + fixme_hasattachments] == "true");
		bool has_parent = (! hit->getParentUri ().isEmpty ());
		bool parent_mbox_file = false;
		if (has_parent)
		    parent_mbox_file = ((*hit) [parent_prefix + fixme_folder] == TQString::null);

		// Logic:
		// If has_parent == false, everything is normal
		// If has_parent == true, parent_mbox_file == false, everything is normal, use uri
		// FIXME: If has_parent == true, parent_mbox_file == true, ???
		// If has_parent == true, is_attachment == true, hit is attach and access with prefix "parent:", use parenturi
		// Else, not attachment (multipart), access with prefix "parent:", use parenturi

		if (has_parent && !parent_mbox_file) {
		    uri = hit->getParentUri ();
		    prefix = parent_prefix;
		    if (is_attachment)
			title = (*hit) [fixme_attachment_title];
		    if (title.isEmpty ())
			title = (*hit) [prefix + dc_title];
		    if (title.isEmpty ())
			title = i18n("No subject");
		    if (is_attachment)
			title = title.prepend (i18n("(Attachment) "));
		    info = (i18n("From %1").arg((*hit) [prefix + fixme_from_address]));
		} else {
		    uri = hit->getUri ();
		    title = (*hit) [dc_title];
		    info = (i18n("From %1").arg((*hit) [fixme_from_address]));
		}
	    }
	    mimetype = "message/rfc822"; // to handle attachment results
	    break;
 	case MUSIC:
	    uri = hit->getUri ();
	    title = (*hit) [exactfilename];
	    {
		TQString artist = (*hit) [fixme_artist];
		TQString album = (*hit) [fixme_album];
		if (! artist.isEmpty ())
		    info = (i18n("By %1").arg(artist));
		else if (! album.isEmpty ())
		    info = (i18n("From Album %1").arg(album));
		else {
		    TQString uristr = uri.path ();
		    int last_slash = uristr.findRev ('/', -1);
                    info = i18n("Folder: %1")
                        .arg(last_slash == 0 ? "/" : uristr.section ('/', -2, -2));
		}
	    }
	    break;
 	case VIDEOS:
	    uri = hit->getUri ();
	    title = (*hit) [exactfilename];
	    {
		TQString uristr = uri.path ();
		int last_slash = uristr.findRev ('/', -1);
                info = i18n("Folder: %1").arg(last_slash == 0 ? "/" : uristr.section ('/', -2, -2));
	    }
	    break;
	case WEBHIST:
	    uri = hit->getUri ();
	    title = (*hit) [dc_title];
	    title = title.replace(TQRegExp("\n")," ");
	    mimetype = "text/html";
	    if (title.isEmpty () || title.stripWhiteSpace ().isEmpty ()) {
		title = uri.prettyURL ();
	    } else {
		info = uri.host () + uri.path ();
	    }
	    break;
	case FEEDS:
	    {
		uri = KURL ((*hit) [dc_identifier]);
	    	title = (*hit) [dc_title];
	    	mimetype = "text/html";
	    	TQString publisher = (*hit) [dc_publisher];
	    	TQString source = (*hit) [dc_source];
	    	if (! publisher.isEmpty ())
	    	    info = publisher;
	    	else if (! source.isEmpty ())
	    	    info = source;
	    }
	    break;
	case PICS:
	    {
		uri = hit->getUri ();
		title = (*hit) [exactfilename];
		TQString width = (*hit) [fixme_width];
		TQString height = (*hit) [fixme_height];
		if (width.isEmpty () || height.isEmpty ()) {
		    TQString uristr = uri.path ();
		    int last_slash = uristr.findRev ('/', -1);
                    info = i18n("Folder: %1")
                        .arg(last_slash == 0 ? "/" : uristr.section ('/', -2, -2));
		    break;
		}
		info = (TQString (" (%1x%2)").arg (width).arg (height));
		const TQStringList *tags = hit->getProperties (digikam_tag);
		if (tags == NULL)
		    break;
		TQString tags_string = tags->join (comma_string);
		info += (" " + tags_string);
	    }
	    break;
	case APPS:
	    {
		uri = hit->getUri ();
	    	title = (*hit) [dc_title];
		KDesktopFile desktopfile(uri.path(),true);
		if (genericTitle && !desktopfile.readGenericName().isEmpty()) {
		  title = desktopfile.readGenericName();
		  info = desktopfile.readName();
		}
		else {
		  title = desktopfile.readName();
		  info = desktopfile.readGenericName();
		}
		icon = desktopfile.readIcon();
                TQString input = current_query_str.lower();
		TQString command = desktopfile.readEntry("Exec");
		if (command==input)
                  score = 100;
                else if (command.find(input)==0)
                  score = 50;
                else if (command.find(input)!=-1)
                  score = 10;
		else if (title==input)
                  score = 100;
                else if (title.find(input)==0)
                  score = 50;
                else if (title.find(input)!=-1)
                  score = 10;
	    	break;
	    }
	    break;
	case NOTES:
            {
	        uri = hit->getUri ();
	        title = (*hit) [dc_title];
	        title = i18n("Title: %1").arg(title.isEmpty() ? i18n("Untitled") : title);

	        if (hit->getSource()=="KNotes")
                   icon="knotes";
                else
                   icon="contents2";
            }
	    break;
	case CHATS:
            {
	        uri = hit->getUri ();
	        title = (*hit) [fixme_speakingto];
	        title = i18n("Conversation With %1").arg(title.isEmpty() ? i18n("Unknown Person") : title);
	        TQDateTime datetime;
	        datetime = datetimeFromString((*hit) [fixme_starttime]);
                info=i18n("Date: %1").arg(KGlobal::locale()->formatDateTime(datetime,false));
	        if (hit->getMimeType()=="beagle/x-kopete-log")
                   icon="kopete";
                else
                   icon="gaim";
            }
	    break;
	case DOCS:
	    {
		uri = hit->getUri ();
		title = (*hit) [dc_title];
		if (title.isEmpty () || title.stripWhiteSpace ().isEmpty ())
		    title = uri.prettyURL ();
		else {
			TQString uristr = uri.path ();
			int last_slash = uristr.findRev ('/', -1);
                        info = i18n("Folder: %1").arg(last_slash == 0 ? "/" : uristr.section ('/',
                                    -2, -2));
		}
	    }
	    break;
	default:
	    return NULL;
    }
    if (mimetype.isEmpty ())
	mimetype = hit->getMimeType ();
    return new HitMenuItem (title, info, uri, mimetype, 0, category, icon, score);
}

void KickoffBeaglePlugin::showResults(BeagleSearchResult *result)
{
    if (result->total == 0 ) {
	// Dont report error from here ...
        kdDebug() << "No matches found" << endl;
	delete result;
	return;
    }

    const TQPtrList<Hit> *hits = result->getHits();
    if (hits == NULL) {
        kdDebug () << "Hmm... null" << endl;
	delete result;
        return;
    }
    kickoffSearchInterface()->initCategoryTitlesUpdate();

    TQPtrListIterator<Hit> it (*hits);
    Hit *hit;
    for (; (hit = it.current ()) != NULL; ++it) {
	CATEGORY category = getHitCategory (hit);

	// if category is not handled, continue
	if (category == OTHER)
          continue;

        if ( category == APPS ) {
            // we need to check if this is useful
            KService cs( hit->getUri().path() );
            if ( cs.noDisplay() )
                continue;
        }

        if (!kickoffSearchInterface()->anotherHitMenuItemAllowed(category))
          continue;

        HitMenuItem *hit_item = hitToHitMenuItem (category, hit);

        if (!hit_item)
	   continue;

        kickoffSearchInterface()->addHitMenuItem(hit_item);
    }

    kickoffSearchInterface()->updateCategoryTitles();

    delete result;
}

TQDateTime KickoffBeaglePlugin::datetimeFromString( const TQString& s)
{
      int year( s.mid( 0, 4 ).toInt() );
      int month( s.mid( 4, 2 ).toInt() );
      int day( s.mid( 6, 2 ).toInt() );
      int hour( s.mid( 8, 2 ).toInt() );
      int min( s.mid( 10, 2 ).toInt() );
      int sec( s.mid( 12, 2 ).toInt() );
      return TQDateTime(TQDate(year,month,day),TQTime(hour,min,sec));
}

#include "kickoff-beagle-plugin.moc"
