
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>

#include <kio/forwardingslavebase.h>

#include <kdebug.h>
#include <kcomponentdata.h>
#include <klocale.h>
#include <kuser.h>
#include <KStandardDirs>
#include <KDesktopFile>
#include <KConfigGroup>
#include <QFile>
#include <QIODevice>
#include <QInputDialog>
#include <QMessageBox>

#include "kio_beagle.h"

using namespace KIO;

const UDSEntry resultToUDSEntry(beagle_result_struct *result)
{
    UDSEntry entry;
    
    // add name - use the path - no smartness please
    if (result->title.startsWith('/'))
       entry.insert( KIO::UDSEntry::UDS_NAME, KUrl(result->uri).fileName());
    else
       entry.insert( KIO::UDSEntry::UDS_NAME, result->title);
    
    // add mime type but skip the artificial mimetype from locate backend
    if (! result->mime_type.isEmpty() && result->mime_type != "beagle/x-locate-result")
	entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, result->mime_type);
    
    // add file or directory information
    // also add URL information
    if (result->type == Link)
    	entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFLNK);
    else if (result->type == Directory)
    	entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
    else
    	entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
    
    if (result->type == File || result->type == Directory) {
    	KUrl url (result->uri);
    	entry.insert( KIO::UDSEntry::UDS_LOCAL_PATH, url.path ());

	// For local files, supply the usual file stat information
    	struct stat info;
    	lstat(url.path().toAscii(), &info);
    
    	entry.insert( KIO::UDSEntry::UDS_SIZE, info.st_size);
    	entry.insert( KIO::UDSEntry::UDS_ACCESS, info.st_mode);
    	entry.insert( KIO::UDSEntry::UDS_MODIFICATION_TIME, info.st_mtime);

	// We could use the date stored in the index, but that date
	// is technically the last modified date known by beagle
    	entry.insert( KIO::UDSEntry::UDS_ACCESS_TIME, info.st_atime);
    	entry.insert( KIO::UDSEntry::UDS_CREATION_TIME, info.st_ctime);
    }
    
    entry.insert( KIO::UDSEntry::UDS_TARGET_URL, result->uri);
    
    return entry;
}

#define COMMAND "command"
#define STATUS "status"
#define INFORMATION_FILE "Information.html"
#define QUERY_SYNTAX_FILE "QuerySyntax.html"
#define BOOKMARK_DIR "Saved queries"
#define ADD_BOOKMARK "SaveQuery"
#define SERVICE_OPTIONS "Service Options"

void kio_beagleProtocol::createRootEntry(KIO::UDSEntry& entry)
{
    // home directory
    entry.clear();
    entry.insert( KIO::UDSEntry::UDS_NAME, ".");
    entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
    entry.insert( KIO::UDSEntry::UDS_ACCESS, 0700);
    entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory");
}

void kio_beagleProtocol::createInformationEntry(KIO::UDSEntry& entry)
{
    // status file
    entry.clear();
    entry.insert( KIO::UDSEntry::UDS_NAME, INFORMATION_FILE);
    entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);
    entry.insert( KIO::UDSEntry::UDS_TARGET_URL, "beagle://command/"INFORMATION_FILE);
    entry.insert( KIO::UDSEntry::UDS_ACCESS, 0500);
    entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, "text/html");
    entry.insert( KIO::UDSEntry::UDS_ICON_NAME, "help-about");
}

void kio_beagleProtocol::createHelpEntry(KIO::UDSEntry& entry)
{
    // query syntax file
    entry.clear();
    entry.insert( KIO::UDSEntry::UDS_NAME, QUERY_SYNTAX_FILE);
    entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);

    QString location = KStandardDirs::locate("data", "kio_beagle/help/query_syntax.html");
    entry.insert( KIO::UDSEntry::UDS_TARGET_URL, KUrl::fromLocalFile(location).toString());

    entry.insert( KIO::UDSEntry::UDS_ACCESS, 0500);
    entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, "text/html");
    entry.insert( KIO::UDSEntry::UDS_ICON_NAME, "system-help");
}

void kio_beagleProtocol::createBookmarkDirEntry(KIO::UDSEntry& entry)
{
    // query syntax file
    entry.clear();
    entry.insert( KIO::UDSEntry::UDS_NAME, BOOKMARK_DIR);
    entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
    entry.insert( KIO::UDSEntry::UDS_TARGET_URL, KUrl::fromLocalFile(bookmarkDir).toString());
    entry.insert( KIO::UDSEntry::UDS_ACCESS, 0700);
    entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory");
    entry.insert( KIO::UDSEntry::UDS_ICON_NAME, "folder-bookmarks");
}

void kio_beagleProtocol::createAddBookmarkEntry(KIO::UDSEntry& entry)
{
    // status file
    entry.clear();
    entry.insert( KIO::UDSEntry::UDS_NAME, ADD_BOOKMARK);
    entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFREG);

    QString location = KStandardDirs::locate("data", "kio_beagle/add_bookmark.html");
    entry.insert( KIO::UDSEntry::UDS_TARGET_URL, KUrl::fromLocalFile(location).toString());

    entry.insert( KIO::UDSEntry::UDS_ACCESS, 0500);
    entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, "text/html");
    entry.insert( KIO::UDSEntry::UDS_ICON_NAME, "bookmark-new");
}

void kio_beagleProtocol::createServiceOptionsEntry(KIO::UDSEntry& entry)
{
    // query syntax file
    entry.clear();
    entry.insert( KIO::UDSEntry::UDS_NAME, SERVICE_OPTIONS);
    entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);

    QString location = KStandardDirs::locate("data", "kio_beagle/beagle/");
    entry.insert( KIO::UDSEntry::UDS_TARGET_URL, KUrl::fromLocalFile(location).toString());

    entry.insert( KIO::UDSEntry::UDS_ACCESS, 0700);
    entry.insert( KIO::UDSEntry::UDS_MIME_TYPE, "inode/directory");
    entry.insert( KIO::UDSEntry::UDS_ICON_NAME, "folder-development");
}

kio_beagleProtocol::kio_beagleProtocol(const QByteArray &pool_socket, const QByteArray &app_socket)
    : SlaveBase("kio_beagle", pool_socket, app_socket)
{
    kDebug() << "kio_beagleProtocol::kio_beagleProtocol()" << endl;

    connect(& beagle_search, SIGNAL(found(BeagleResultList&)),
            this, SLOT(searchHasOutput(BeagleResultList&)));
    connect(& beagle_search, SIGNAL(finished()),
            this, SLOT(searchFinished()));
    connect(& beagle_search, SIGNAL(oops_error(const QString&)),
            this, SLOT(searchError(const QString&)));

    hostMap["file"]	= "type:File";
    hostMap["email"]	= "type:MailMessage";
    hostMap["link"]	= "type:WebHistory OR type:Bookmark";
    hostMap["all"]	= "";

    // stored queries or bookmarks
    bookmarkDir = KStandardDirs::locateLocal("data", "kio_beagle/"BOOKMARK_DIR"/", true);
}


kio_beagleProtocol::~kio_beagleProtocol()
{
    kDebug() << "kio_beagleProtocol::~kio_beagleProtocol()" << endl;
}

void kio_beagleProtocol::showStatusPage()
{
    const char *str;
    GSList *list, *index_stats;
    BeagleSchedulerInformation  *sched_info;
    BeagleDaemonInformationRequest *request = NULL;
    BeagleResponse *response = NULL;

    mimeType("text/html");

    QString index_info = QString::null, tasks = QString::null;

    g_type_init ();
    BeagleClient *client = beagle_client_new(NULL);
    kDebug() << "not running\n";
    if (client == NULL)
	goto footer;

    request = beagle_daemon_information_request_new_specific (FALSE, TRUE, TRUE, FALSE);
    response = beagle_client_send_request (client, BEAGLE_REQUEST (request), NULL);
    if (response == NULL)
	goto footer;

    index_stats = beagle_daemon_information_response_get_index_status (BEAGLE_DAEMON_INFORMATION_RESPONSE(response));
    index_info = "<ul>";
    BeagleQueryableStatus *stat;
    while (index_stats != NULL) {
	stat = (BeagleQueryableStatus *) index_stats->data;
	if (beagle_queryable_status_get_item_count(stat) != -1)
	   index_info += QString("<li>%1 <i>(%2 items)</i></li>").
			arg(beagle_queryable_status_get_name(stat)).
			arg(beagle_queryable_status_get_item_count(stat));
	else
	   index_info += QString("<li>%1 </li>").
			arg(beagle_queryable_status_get_name(stat));

	index_stats = index_stats->next;
    }
    index_info += "</ul>";

    sched_info = beagle_daemon_information_response_get_scheduler_information( BEAGLE_DAEMON_INFORMATION_RESPONSE(response) );
    tasks = "No tasks";
    if (sched_info == NULL)
	goto footer;

    tasks = "";
    str = beagle_scheduler_information_get_status_string(sched_info);
    if (str != NULL)
	tasks += QString("<p>Current status: %1</p>").arg(str);

    list = beagle_scheduler_information_get_pending_tasks(sched_info);
    tasks += "<p>Pending tasks:";
    if (g_slist_length(list) == 0)
	tasks += "<i>None</i></p>";
    else {
	tasks += "</p><ol>";
	while (list != NULL) {
	    tasks += QString("<li>%1</li>").arg((char*)list->data);
	    list = list->next;
	}
	tasks += "</ol>";
    }

    list = beagle_scheduler_information_get_pending_tasks(sched_info);
    tasks += "<p>Future tasks:";
    if (g_slist_length(list) == 0)
	tasks += "<i>None</i></p>";
    else {
	tasks += "</p><ol>";
	while (list != NULL) {
	    tasks += QString("<li>%1</li>").arg((char*)list->data);
	    list = list->next;
	}
	tasks += "</ol>";
    }

    list = beagle_scheduler_information_get_pending_tasks(sched_info);
    tasks += "<p>Blocked tasks (waiting for future events):";
    if (g_slist_length(list) == 0)
	tasks += "<i>None</i></p>";
    else {
	tasks += "</p><ol>";
	while (list != NULL) {
	    tasks += QString("<li>%1</li>").arg((char*)list->data);
	    list = list->next;
	}
	tasks += "</ol>";
    }

footer:
    QString location = KStandardDirs::locate("data", "kio_beagle/help/status.html");
    QFile statusfile(location);
    statusfile.open(QIODevice::ReadOnly | QIODevice::Text);
    QTextStream t(&statusfile);
    QString content = t.readAll();

    content = content.arg("file:" + KStandardDirs::locate("data","kio_beagle/help/default.css")); // %1
    if (index_info.isNull())
	// not running
	content = content.arg("block").arg("none"); // %2, %3
    else
	// running
	content = content.arg("none").arg("block"); // %2, %3

    content = content.arg("file:" + KStandardDirs::locate("data","kio_beagle/help/beagle-logo.png")); //%4
    content = content.arg(index_info).arg(tasks); // %5, %6
    data(content.toUtf8());
    data(QByteArray());
    finished();

    if (request != NULL)
	    g_object_unref (request);
    if (response != NULL)
	    g_object_unref (response);
}

bool kio_beagleProtocol::addBookmark(const KUrl &url)
{
    QMap<QString, QString> options = url.queryItems();
    kDebug() << "Saving query name=" << options["queryname"] << ", value=" << options["querystr"] << endl;

    KDesktopFile df(bookmarkDir + "/" + options["queryname"] + ".desktop");
    if (df.hasGroup("[Desktop Entry]")) {
	messageBox(SlaveBase::Information, QString("There is already a query with name %1").arg(options["queryname"]));
	return false;
    }

    QString beagleUrl = "beagle://all";

    if (options.contains("n"))
	beagleUrl += ",n=" + options ["n"];

    if (options.contains("dLocal"))
	beagleUrl += ",d=l";

    if (options.contains("dSystem"))
	beagleUrl += ",d=s";

    if (options.contains("dNet"))
	beagleUrl += ",d=n";

    if (options.contains("dGlobal"))
	beagleUrl += ",d=g";

    beagleUrl = QString("%1/%2").arg(beagleUrl).arg(options["querystr"]);

    KConfigGroup group = df.group("Desktop Entry");
    group.writeEntry("Icon" , "kio_beagle");
    group.writeEntry("Type" , "Link");
    group.writeEntry("URL"  , beagleUrl);
    group.writeEntry("Comment" ,  options["querystr"]);
    df.sync();
    return true;
}

void kio_beagleProtocol::get(const KUrl& url )
{
    kDebug() << "kio_beagle::get(const KUrl& url)" << url << endl ;

    if (url.host() == "command") {
	if (url.path() == "/"INFORMATION_FILE) {
	    showStatusPage();
	    return;
	}
    }

    error(KIO::ERR_IS_DIRECTORY, QString::null);
}

// from kio_locate
void kio_beagleProtocol::stat(const KUrl & url)
{
    kDebug() << "kio_beagle::stat(const KUrl& url)" << url << endl ;

    QString path = url.path();

    if (path.isEmpty() || path == "/")
    {
	kDebug() << "kio_beagle: stat /" << endl;
	KIO::UDSEntry entry;
	createRootEntry(entry);
	statEntry(entry);
	finished();
	return;
    }

    if (url.host() == "command" && url.path() == "/"INFORMATION_FILE)
    {
	kDebug() << "kio_beagle: status" << endl;
	KIO::UDSEntry entry;
	createInformationEntry(entry);
	statEntry(entry);
	finished();
	return;
    }

    if (url.host() == "command" && url.path() == QUERY_SYNTAX_FILE)
    {
	kDebug() << "kio_beagle: query syntax help" << endl;
	KIO::UDSEntry entry;
	createHelpEntry(entry);
	statEntry(entry);
	finished();
	return;
    }

    // Other virtual files and folders

    // Else its a query
    {
	KIO::UDSEntry entry;
    	/// \todo Is UDS_NAME used for anything? If so we should
    	/// at least strip of the protocol part.
    	entry.insert( KIO::UDSEntry::UDS_NAME, url.path());
    	entry.insert( KIO::UDSEntry::UDS_TARGET_URL, url.url());
    	entry.insert( KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
    	statEntry(entry);
    	finished();
    }
}

void kio_beagleProtocol::mimetype(const KUrl & url)
{
    kDebug() << "kio_beagle::mimetype(const KUrl& url)" << url << endl ;

    if (url.host() == "command" && url.path() == "/"INFORMATION_FILE)
	mimeType("text/html");
    else
	mimeType("inode/directory");

    finished();
}

void kio_beagleProtocol::listDir(const KUrl& url)
{
    kDebug() << "kio_beagleProtocol::listDir()" << url << endl;

    if (url.host() == "command") {
	if (url.path() == "/"INFORMATION_FILE) {
	    error(KIO::ERR_IS_FILE, QString::null);
	    finished();
	    return;
	} else if (url.path() == "/"ADD_BOOKMARK && addBookmark(url)) {
	    redirection(KUrl::fromLocalFile(bookmarkDir));
	    finished();
	    return;
	}

	redirection(KUrl("beagle:///"));
	finished();
	return;
    }

    if (url.path().isEmpty() || url.path() == "/") {
	kDebug() << "kio_beagle: list /" << endl;

	KIO::UDSEntry entry;

	// entry for '/'
	createRootEntry(entry);
	listEntry(entry, false);

	// entry for 'Information'
	createInformationEntry(entry);
	listEntry(entry, false);

	createHelpEntry(entry);
	listEntry(entry, false);

	createBookmarkDirEntry(entry);
	listEntry(entry, false);

	createAddBookmarkEntry(entry);
	listEntry(entry, false);

	createServiceOptionsEntry(entry);
	listEntry(entry, false);

	entry.clear();
	listEntry(entry, true);

	finished();
	return;
    }

    uint domain = 0;
    int maxNumResults = -1;
    QString str = URL_ToSearchString(url, domain, maxNumResults);
    kDebug() << "str=[" << str << "],n=" << maxNumResults << ",d=" << domain << endl;

    if (str.isEmpty())
    	finished();
    else {
    	infoMessage(i18n("Searching via libbeagle for %1 ...",str));
    	beagle_search.search(str, maxNumResults, domain);
    	//emit searchError("Failed!");
    	//finished();
    }
}

QString kio_beagleProtocol::hostToQuery(const QString query) const
{
    // FIXME get this information from KConfig if user has added
    // custom mappings
    return hostMap.value(query, QString::null);
}

QString kio_beagleProtocol::URL_ToSearchString(const KUrl &url, uint &domain, int &maxNumResults) const
{
    QString host = url.host();
    QString query = url.path();

    if (query[query.size() - 1] == '/') {
	kDebug() << "Disable autocompleted search.\n";
	return QString::null;
    }

    // Need at least some 'path' component
    if (query.size() <= 1)
	return QString::null;
    query = query.mid(1); // beginning '/'

    if (query.indexOf ('/') != -1)
	return QString::null;

    if (host.isEmpty())
	host = "file"; // default types to query

    if (host.indexOf(',') != -1) {
	// Parse the host to get various options
	QStringList options = host.split(',');
	for (int i=0; i < options.size(); ++i) {
	    QString option = options.at(i);
	    if (option.indexOf('=') == -1) {
		host = option;
	    } else {
		QStringList values = option.split('=');
		if (values.at(0) == "d") {
		    QString domainVal = values.at(1);
		    if (domainVal == "l") // local
			domain |= BEAGLE_QUERY_DOMAIN_LOCAL;
		    else if (domainVal == "s") // system
			domain |= BEAGLE_QUERY_DOMAIN_SYSTEM;
		    else if (domainVal == "n") // network
			domain |= BEAGLE_QUERY_DOMAIN_NEIGHBORHOOD;
		    else if (domainVal == "g") // global
			domain |= BEAGLE_QUERY_DOMAIN_GLOBAL;
		} else if (values.at(0) == "n") {
		    bool ok;
		    int n = values.at(1).toInt(&ok, 10);
		    if (ok == true)
			maxNumResults = n;
		}
	    }
	}
    }

    host = hostMap.value(host, QString::null);

    if (host.isNull())
	return QString::null;

    return host + " " + query;
}

void kio_beagleProtocol::searchHasOutput(BeagleResultList &items)
{
    UDSEntryList entries;
    for (BeagleResultList::Iterator it = items.begin(); it != items.end(); ++it) {
        beagle_result_struct *result = &(*it);
		kDebug() << "hit:(" << result->type << ")" 
			  << result->uri << " title=" << result->title
			  << " mime-type=" << result->mime_type << endl;
		entries.append(resultToUDSEntry (result));
	}
	
	infoMessage(i18np("Found 1 search result", "Found %1 search results", items.count() ) );
	listEntries(entries);
}

void kio_beagleProtocol::searchFinished()
{
    finished();
}

void kio_beagleProtocol::searchError(const QString& error)
{
    messageBox(SlaveBase::Information, error);
    redirection(KUrl("beagle:///"));
    finished();
}

extern "C"
{
    KDE_EXPORT int kdemain(int argc, char **argv)
    {
        KComponentData instance( "kio_beagle" );

        kDebug(7101) << "*** Starting kio_beagle " << endl;

        if (argc != 4) {
            kDebug(7101) << "Usage: kio_beagle  protocol domain-socket1 domain-socket2" << endl;
            exit(-1);
        }

        kio_beagleProtocol slave(argv[2], argv[3]);
        slave.dispatchLoop();

        kDebug(7101) << "*** kio_beagle Done" << endl;
        return 0;
    }
}

#include "kio_beagle.moc"
