/***************************************************************************
 *   tdeio-locate: KDE I/O Slave for the locate command                      *
 *                                                                         *
 *   Copyright (C) 2005 by Tobi Vollebregt                                 *
 *   tobivollebregt@gmail.com                                              *
 *                                                                         *
 *   Thanks to Google's Summer Of Code Program!                            *
 *                                                                         *
 *   Copyright (C) 2004 by Armin Straub                                    *
 *   linux@arminstraub.de                                                  *
 *                                                                         *
 *   This program was initially written by Michael Schuerig.               *
 *   Although I have completely rewritten it, most ideas are adopted       *
 *   from his original work.                                               *
 *                                                                         *
 *   Copyright (C) 2002 by Michael Schuerig                                *
 *   michael@schuerig.de                                                   *
 *                                                                         *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

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

#include <tdeapplication.h>
#include <tdeconfigdialog.h>
#include <kdebug.h>
#include <kde_file.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <kurl.h>
#include <kuser.h>
#include <tqfile.h>

#include "tdeio_locate.h"
#include "klocateconfig.h"
#include "klocateconfigwidget.h"
#include "klocateconfigfilterwidget.h"
#include "klocateconfiglocatewidget.h"

using namespace TDEIO;


static const TQString queryQuery = "q";
static const TQString queryDirectory = "directory";
static const TQString queryCase = "case";
static const TQString queryRegExp = "regexp";

static const TQString iconToStringTable[] = {
    "folder", "folder_green", "folder_grey", "folder_orange",
    "folder_red", "folder_violet", "folder_yellow"
};


/////////////////////////////////////////////////////////////////////
// HELPERS

/**
 * Determines if a string contains unescaped wildcard characters.
 * Currently, wildcard characters are: * + ? [ ]
 * For older versions of Konqueror: + behaves identical to *
 * @param s the string to inspect
 */
static bool hasWildcards(const TQString& s)
{
    for (unsigned int i = 0; i < s.length(); ++i) {
        if ((s[i] == '*' || s[i] == '+' || s[i] == '?' || s[i] == '[' || s[i] == ']') && (i < 1 || s[i-1] != '\\'))
            return true;
    }
    return false;
}


/**
 * Converts a string containing shell wildcard characters (globbing characters)
 * to a regular expression. This function takes care of escaped wildcards and
 * any regexp special characters which happen to be in the string.
 * @param s the string to convert
 * @return the converted string
 */
static TQString convertWildcardsToRegExp(TQString s)
{
    bool in_set = false;

    // No support for regexp searches.
    // (Konqueror makes passing chars like "/" almost impossible anyway.)
    // Note that this converts actual wildcards to escaped wildcards (\wildcard),
    // and escaped wildcards to 'triple'-escaped wildcards (\\\wildcard).
    s = TQRegExp::escape(s);

    // Walk through the string, converting \wildcard to regexp and
    // \\\wildcard back to \wildcard.
    for (unsigned i = 1; i < s.length(); ++i) {
        //kdDebug(7134) << s.left(i+1) << endl;
        if (i < 3 || s[i-3] != '\\' || s[i-2] != '\\') {
            // If it was an unescaped character (now possibly escaped once)
            if (s[i-1] == '\\') {
                // If it actually was escaped once
                if (!in_set) {
                    // If it's NOT inside a character set, we need to convert *?[
                    if (s[i] == '*' || s[i] == '+') {
                        s = s.left(i-1) + "[^/]*" + s.mid(i+1);
                        i += 3; // 3 == strlen("[^/]*")-strlen("\\*")
                    } else if (s[i] == '?') {
                        s = s.left(i-1) + "[^/]" + s.mid(i+1);
                        i += 2; // 2 == strlen("[^/]")-strlen("\\*")
                    } else if (s[i] == '[') {
                        s = s.left(i-1) + s.mid(i);
                        --i;
                        in_set = true;
                    }
                } else {
                    // If it's inside a character set, we need to convert ]^
                    // and to unescape everything else.
                    if (s[i] == ']') {
                        s = s.left(i-1) + s.mid(i);
                        --i;
                        in_set = false;
                    } else if (s[i] == '^' && i >= 2 && s[i-2] == '[') {
                        s = s.left(i-1) + s.mid(i);
                        --i;
                    } else {
                        s = s.left(i-1) + s.mid(i);
                    }
                }
            }
        } else {
            // If it's an escaped character, remove the extra escape characters.
            s = s.left(i-3) + s.mid(i-1);
            i -= 2; // 2 == strlen("\\\\")-strlen("")
        }
    }
    return s;
}


/**
 * Determines if path includes a trailing slash.
 * @param path the path to inspect
 */
static bool hasTrailingSlash(const TQString& path)
{
    int n = path.length();
    return ((n > 0) && (path[n-1] == '/'));
}


/**
 * Strips a trailing slash / from a path.
 * @param path the path to strip the slash off
 */
static TQString stripTrailingSlash(const TQString& path)
{
    int n = path.length();
    if ((n > 0) && (path[n-1] == '/')) {
        return path.left(n-1);
    }
    return path;
}


/**
 * Add a trailing slash / to a path if there is none yet.
 * @param path the path to append the slash to
 */
static TQString addTrailingSlash(const TQString& path)
{
    int n = path.length();
    if ((n > 0) && (path[n-1] == '/')) {
        return path;
    }
    return path + '/';
}


static void addAtom(UDSEntry& entry, unsigned int uds, const TQString& s)
{
    UDSAtom a;
    a.m_uds = uds;
    a.m_str = s;
    entry.append(a);
}


static void addAtom(UDSEntry& entry, unsigned int uds, long long l)
{
    UDSAtom a;
    a.m_uds = uds;
    a.m_long = l;
    entry.append(a);
}


static const UDSEntry pathToUDSEntry(const TQString& path, const TQString& display,
    const TQString& url = TQString(), const TQString& icon = TQString())
{
    UDSEntry entry;
    addAtom(entry, TDEIO::UDS_NAME, display);

    if (!path.isEmpty()) {
        KDE_struct_stat info;
        KDE_lstat(path.local8Bit(), &info);

        addAtom(entry, TDEIO::UDS_SIZE, info.st_size);
        addAtom(entry, TDEIO::UDS_ACCESS, info.st_mode);
        addAtom(entry, TDEIO::UDS_MODIFICATION_TIME, info.st_mtime);
        addAtom(entry, TDEIO::UDS_ACCESS_TIME, info.st_atime);
        addAtom(entry, TDEIO::UDS_CREATION_TIME, info.st_ctime);

        struct passwd * user = getpwuid(info.st_uid);
        struct group * group = getgrgid(info.st_gid);
        addAtom(entry, TDEIO::UDS_USER, ((user != NULL) ? user->pw_name : "???" ));
        addAtom(entry, TDEIO::UDS_GROUP, ((group != NULL) ? group->gr_name : "???" ));

        if (url.isEmpty()) {
            // List an existing file.
            addAtom(entry, TDEIO::UDS_URL, "file:" + path);

            mode_t type = info.st_mode;
            if (S_ISLNK(type)) {
                TQString slink = TQString();
                char buff[1000];
                int n = readlink(path.ascii(), buff, 1000);
                if (n != -1) {
                    buff[n] = 0;
                    slink = buff;
                }
                addAtom(entry, TDEIO::UDS_LINK_DEST, slink);
            } else {
                type &= S_IFMT;
            }
            addAtom(entry, TDEIO::UDS_FILE_TYPE, type);

#ifdef HAVE_UDS_HIDDEN
            if (path.contains("/.")) {
                addAtom(entry, TDEIO::UDS_HIDDEN, 1);
            }
#endif
        } else {
            // List a locate link.
            addAtom(entry, TDEIO::UDS_URL, url);
            addAtom(entry, TDEIO::UDS_FILE_TYPE, S_IFDIR);
        }
    } else {
        addAtom(entry, TDEIO::UDS_URL, url);
    }

    if (!icon.isEmpty()) {
        addAtom(entry, TDEIO::UDS_ICON_NAME, icon);
    }

    return entry;
}


/////////////////////////////////////////////////////////////////////
// INITIALIZATION

LocateProtocol::LocateProtocol(const TQCString &pool_socket, const TQCString &app_socket)
    : SlaveBase("tdeio_locate", pool_socket, app_socket)
{
    kdDebug(7134) << "LocateProtocol::LocateProtocol()" << endl;

    connect(&m_locater, TQ_SIGNAL(found(const TQStringList&)),
             this, TQ_SLOT(processLocateOutput(const TQStringList&)));
    connect(&m_locater, TQ_SIGNAL(finished()),
             this, TQ_SLOT(locateFinished()));

    m_baseDir = NULL;
    m_curDir = NULL;
}


LocateProtocol::~LocateProtocol()
{
    kdDebug(7134) << "LocateProtocol::~LocateProtocol()" << endl;

    delete m_baseDir;
}


const LocateRegExp& LocateProtocol::getRegExp() const
{
    return m_locateRegExp;
}


int LocateProtocol::getCollapseDirectoryThreshold() const
{
    return m_config.m_collapseDirectoryThreshold;
}


/////////////////////////////////////////////////////////////////////
// TDEIO STUFF

void LocateProtocol::setUrl(const KURL& url)
{
    if (url.protocol() != "locater") {
        TQString pattern = KURL::decode_string(url.url());
        pattern = pattern.mid(url.protocol().length() + 1);

        KURL newUrl;
        newUrl.setProtocol("locater");

        if (pattern.isEmpty() || pattern == "/") {
            // Give help.
            newUrl.setPath("help");
        } else if (hasTrailingSlash(pattern)) {
            // Detect auto-completion from within konqueror and "stop"
            // this search.
            newUrl.setPath("autosearch");
            newUrl.addQueryItem(queryQuery, pattern);
        } else if (url.protocol() == "rlocate") {
            // Standard regexp search.
            newUrl.setPath("search");
            newUrl.addQueryItem(queryQuery, pattern);
            newUrl.addQueryItem(queryRegExp, "1");
        } else {
            // Standard wildcard search.
            newUrl.setPath("search");
            newUrl.addQueryItem(queryQuery, pattern);
        }
        m_url = newUrl;

        kdDebug(7134) << "Redirect: " << m_url << endl;
    } else {
        m_url = url;
    }
    // Perhaps this will be unneccessary most times, but who knows...
    updateConfig();
}

void LocateProtocol::get(const KURL& url)
{
    kdDebug(7134) << "LocateProtocol::get(" << url << ")" << endl;

    setUrl(url);

    if (isSearchRequest()) {
        if (m_locater.binaryExists()) {
            error(TDEIO::ERR_IS_DIRECTORY, TQString());
        } else {
            TQString html = i18n("<h1>\"%1\" could not be started.</h1><p>Please note that tdeio-locate can't be used on its own. You need an additional program for doing searches. Typically this is the command line tool <i>locate</i> that can be found in many distributions by default. You can check if the correct tool is used by looking at the <a href=\"locater:config\">setting</a> \"Locate Binary\".<p>Besides the mentioned tool <i>locate</i>, tdeio-locate can use any tool that uses the same syntax. In particular, it was reported to work with <i>slocate</i> and <i>rlocate</i>.").arg(m_locater.binary());
            outputHtml(html);
        }
    } else if (isConfigRequest()) {
        configRequest();
    } else if (isHelpRequest()) {
        helpRequest();
    } else {
        // What's this?
        error(TDEIO::ERR_DOES_NOT_EXIST, TQString());
    }
}


void LocateProtocol::stat(const KURL& url)
{
    kdDebug(7134) << "LocateProtocol::stat(" << url << ")" << endl ;

    setUrl(url);

    if (isSearchRequest() || isConfigRequest() || isHelpRequest()) {
        bool isDir = isSearchRequest() && m_locater.binaryExists();
        /// \todo Is UDS_NAME used for anything in stat? If so we should
        /// at least strip of the protocol part.
        UDSEntry entry;
        addAtom(entry, TDEIO::UDS_NAME, url.decode_string(url.url()));
        addAtom(entry, TDEIO::UDS_FILE_TYPE, isDir ? S_IFDIR : S_IFREG);
        statEntry(entry);
        finished();
        /// \todo Somehow locate: and locate:/ is thought to be a directory
        /// by konqueror anyway. How to change this?
    } else {
        // What's this?
        error(TDEIO::ERR_DOES_NOT_EXIST, TQString());
    }
}


void LocateProtocol::listDir(const KURL& url)
{
    kdDebug(7134) << "LocateProtocol::listDir(" << url << ")" << endl ;

    setUrl(url);

    if (isSearchRequest()) {
        searchRequest();
    } else if (isConfigRequest() || isHelpRequest()) {
        error(TDEIO::ERR_IS_FILE, TQString());
    } else {
        // What's this?
        error(TDEIO::ERR_DOES_NOT_EXIST, TQString());
    }
}


void LocateProtocol::mimetype(const KURL& url)
{
    kdDebug(7134) << "LocateProtocol::mimetype(" << url << ")" << endl ;

    setUrl(url);

    if (isSearchRequest()) {
        if (m_locater.binaryExists()) {
            mimeType("inode/directory");
        } else {
            mimeType("text/html");
        }
    } else if (isConfigRequest() || isHelpRequest()) {
        mimeType("text/html");
    }
    finished();
}


void LocateProtocol::outputHtml(const TQString& body)
{
    mimeType("text/html");

    TQString theData = "<html><body>" + body + "</body></html>";
    data(theData.local8Bit());
    finished();
}


/////////////////////////////////////////////////////////////////////
// SEARCHING

bool LocateProtocol::isSearchRequest()
{
    return m_url.path() == "search";
}


void LocateProtocol::searchRequest()
{
    // Reset old values.
    m_caseSensitivity = caseAuto;
    m_useRegExp = false;
    m_locatePattern = TQString();
    m_locateDirectory = TQString();
    m_regExps.clear();
    m_pendingPath = TQString();

    delete m_baseDir;
    m_baseDir = NULL;
    m_curDir = NULL;

    updateConfig();

    TQString query = m_url.queryItem(queryQuery);
    m_locateDirectory = addTrailingSlash(m_url.queryItem(queryDirectory));

    TQString caseSensitivity = m_url.queryItem(queryCase);
    if (caseSensitivity == "sensitive") {
        m_caseSensitivity = caseSensitive;
    } else if (caseSensitivity == "insensitive") {
        m_caseSensitivity = caseInsensitive;
    }

    TQString useRegExp = m_url.queryItem(queryRegExp);
    if (!useRegExp.isEmpty() && useRegExp != "0") {
        m_useRegExp = true;
    }

    // Split query into components. The first component is the query
    // for locate. The others are filtering regular expressions. They are
    // delimited by (not escaped) whitespace.
    // If the last component starts with two backslahes \\, then the search
    // is only to be done within the directory following them.
    query = query.simplifyWhiteSpace();
    int s = 0;
    int n = query.length();
    bool regexp;
    TQString display;
    for (int i = 0; i <= n; i++) {
        if ((i == n) || ((query[i] == ' ') && (i > 0)
                   && (query[i-1] != '\\') && (i-s > 0))) {
            TQString temp = query.mid(s, i-s);
            TQString part = partToPattern(temp, s==0);
            if (s == 0) {
                // We don't want to show the escaped regexpified string to
                // the user, so we store the string we get in for later display.
                display = temp;
                // This is the same check as in partToPattern.
                // ie. regexp is used if locate pattern contains wildcards,
                // or regexp searching was enabled.
                regexp = hasWildcards(temp);
                m_locatePattern = part;
            } else {
                // For each regular expression determine if it should be
                // case sensitive.
                m_regExps += LocateRegExp(part, !isCaseSensitive(part));
            }
            s = i+1;
        }
    }

    kdDebug(7134) << "Pattern: " << m_locatePattern << endl;
    kdDebug(7134) << "Directory: " << m_locateDirectory << endl;

    // We set up the regexp used to see whether the match was in the
    // directory part or the filename part of a path.
    m_locateRegExp = LocateRegExp(convertWildcardsToRegExp(m_locatePattern), !isCaseSensitive(m_locatePattern));

    // Now perform the search...
    infoMessage(i18n("Locating %1 ...").arg(display));

    bool started = m_locater.locate(m_locatePattern, !isCaseSensitive(m_locatePattern), regexp);
    
    if (!started) {
        kdDebug(7134) << "Locate could not be found." << endl;
        finished();
    }
}


bool LocateProtocol::isCaseSensitive(const TQString& text)
{
    if (m_caseSensitivity == caseSensitive) {
        return true;
    } else if (m_caseSensitivity == caseInsensitive) {
        return false;
    } else if (m_config.m_caseSensitivity == caseSensitive) {
        return true;
    } else if (m_config.m_caseSensitivity == caseInsensitive) {
        return false;
    } else {
        return text != text.lower();
    }
}


void LocateProtocol::addHit(const TQString& path, int subItems)
{
    // kdDebug(7134) << "LocateProtocol::addHit( " << path << ", " << subItems << " )" << endl;
    if (TQFile::exists(path)) {
        if (subItems > 0) {
            m_entries += pathToUDSEntry(path, pathToDisplay(path, subItems), makeLocaterUrl(path), iconToStringTable[m_config.m_collapsedIcon]);
        } else {
            m_entries += pathToUDSEntry(path, pathToDisplay(path));
        }
    }
}


void LocateProtocol::addPreviousLocateOutput()
{
    if (m_baseDir == NULL) {
        return;
    }
    // m_baseDir->debugTrace();
    if (m_locateDirectory == "/") {
        // Allow toplevel directories to collapse (e.g. when locating "/usr/").
        m_baseDir->prepareListing(this, 0);
    } else {
        m_baseDir->prepareListing(this, m_locateDirectory.length());
    }
    m_baseDir->listItems(this);
    delete m_baseDir;
    m_baseDir = NULL;
    m_curDir = NULL;

    listEntries(m_entries);
    m_entries.clear();
}


void LocateProtocol::processPath(const TQString &path, const TQString &nextPath)
{
    if (!nextPath) {
        // We need to know the next path, so we remember this path for later processing.
        m_pendingPath = path;
    } else if (!nextPath.startsWith(path + '/')) {
        if (isMatching(path)) {
            if ((m_baseDir != NULL) && !path.startsWith(m_baseDir->m_path)) {
                addPreviousLocateOutput();
            }
            // Add path to current directory.
            if (m_baseDir == NULL) {
                int p = path.find('/', 1);
                TQString base = path;
                if (p >= 0) {
                    base = path.left(p+1);
                }
                m_baseDir = new LocateDirectory(NULL, base);
                m_curDir = m_baseDir;
            }
            m_curDir = m_curDir->addPath(path);
        }
    }
}


void LocateProtocol::processLocateOutput(const TQStringList& items)
{
    // I don't know if this really necessary, but if we were signaled, we'll
    // better stop.
    if (wasKilled()) {
        m_locater.stop();
        return;
    }
    // Go through what we have found.
    TQStringList::ConstIterator it = items.begin();
    if (!m_pendingPath.isNull()) {
        processPath(m_pendingPath, *it);
        m_pendingPath = TQString();
    }
    for (; it != items.end();) {
        TQString path = *it;
        ++it;
        processPath(path, it != items.end() ? *it : TQString());
    }
}


void LocateProtocol::locateFinished()
{
    // Add any pending items.
    if (!m_pendingPath.isNull()) {
        processPath(m_pendingPath, "");
        m_pendingPath = TQString();
    }
    addPreviousLocateOutput();

    kdDebug(7134) << "LocateProtocol::locateFinished" << endl;
    infoMessage(i18n("Finished."));
    finished();
}


TQString LocateProtocol::partToPattern(const TQString& part, bool forLocate)
{
    kdDebug(7134) << "BEG part: " << part << endl;
    TQString pattern = part;
    // Unescape whitespace.
    pattern.replace("\\ ", " ");
    // Unquote quoted pattern.
    int n = pattern.length(), index;
    if ((n > 1) && (pattern[0] == '"') && (pattern[n-1] == '"')) {
        pattern = pattern.mid(1, n-2);
    }

    // We can't do regular expression matching on the locate pattern,
    // the regular expression format used by locate is incompatible
    // with the format used by TQRegExp.
    if (!m_useRegExp || forLocate) {
        // Escape regexp characters for filtering pattern, and for locate,
        // but the latter only if it is actually necessary to pass a regexp to locate.
        // (ie. the pattern contains wildcards.)
        if (!forLocate || hasWildcards(pattern)) {
            pattern = convertWildcardsToRegExp(pattern);
        } else {
            // Special case for locate pattern without wildcards:
            // Unescape all escaped wildcards.
            pattern.replace("\\*", "*");
            pattern.replace("\\+", "+");
            pattern.replace("\\?", "?");
            pattern.replace("\\[", "[");
            pattern.replace("\\]", "]");
        }
    }

    // Special treatment for the pattern used for locate:
    if (forLocate) {
        // Replace ~/ and ~user/ at the beginning (as the shell does)
        if ((pattern.length() > 0) && (pattern[0] == '~')) {
            index = pattern.find('/');
            if (index >= 0) {
                TQString name = pattern.mid(1, index-1);
                TQString homeDir;
                if (name.isEmpty()) {
                    homeDir = KUser(KUser::UseRealUserID).homeDir();
                } else {
                    homeDir = KUser(name).homeDir();
                }
                if (!homeDir.isEmpty()) {
                    pattern.replace(0, index, homeDir);
                }
            }
        }
        pattern.replace("\\~", "~");
    }
    kdDebug(7134) << "END part: " << pattern << endl;
    return pattern;
}


bool LocateProtocol::isMatching(const TQString& path)
{
    // The file has to belong to our directory.
    if (!path.startsWith(m_locateDirectory)) {
        return false;
    }
    // And it has to match at least one regular expression in the whitelist.
    if (!m_config.m_whiteList.isMatchingOne(path)) {
        return false;
    }
    // And it may not match any regular expression in the blacklist.
    if (m_config.m_blackList.isMatchingOne(path)) {
        return false;
    }
    // And it has to match against all regular expressions specified in the URL.
    if (!m_regExps.isMatchingAll(path)) {
        return false;
    }
    // And it must not solely match m_locateDirectory.
    return m_locateRegExp.isMatching(path.mid(m_locateDirectory.length()));
}


TQString LocateProtocol::pathToDisplay(const TQString& path, int subItems)
{
    // Split off the directory part. If it is not just the minimal '/'.
    TQString display = path;
    if ((m_locateDirectory != "/") && display.startsWith(m_locateDirectory)) {
        display = display.mid(m_locateDirectory.length());
    }
    if (subItems > 0) {
        // Can't use m_collapsedDisplay.arg(subItems).arg(display); here
        // because user might forget to type %1 or %2, or type it twice.
        // In both cases the result of arg() is undefined.
        TQString output = m_config.m_collapsedDisplay, temp;
        temp.setNum(subItems);
        output.replace("%1", temp);
        output.replace("%2", display);
        display = output;
    }
    return display;
}


TQString LocateProtocol::makeLocaterUrl(const TQString& directory)
{
    KURL url(m_url);
    url.removeQueryItem(queryDirectory);
    url.addQueryItem(queryDirectory, directory);
    return url.url();
}


/////////////////////////////////////////////////////////////////////
// CONFIG

bool LocateProtocol::isConfigRequest()
{
    return m_url.path() == "config";
}


void LocateProtocol::configRequest()
{
    // This flag is used to show either a "succesful" or an "unchanged" message
    // in configFinished().
    m_configUpdated = false;

    // Don't show it twice. During my tests I never got there however.
    if(TDEConfigDialog::showDialog("settings"))
        return;

    TDEConfigDialog *dialog = new TDEConfigDialog(0, "settings", KLocateConfig::self(),
                                              KDialogBase::IconList,
                                              KDialogBase::Default|KDialogBase::Ok|KDialogBase::Cancel|KDialogBase::Help,
                                              KDialogBase::Ok, true);
    dialog->setCaption(i18n("Configure - tdeio-locate"));
    dialog->setIcon(SmallIcon("edit-find"));

    dialog->addPage(new KLocateConfigWidget(), i18n("General"), "package_settings");
    dialog->addPage(new KLocateConfigFilterWidget(), i18n("Filters"), "filter");
    dialog->addPage(new KLocateConfigLocateWidget(), i18n("Locate"), "edit-find");

    // React on user's actions.
    connect(dialog, TQ_SIGNAL(settingsChanged()), this, TQ_SLOT(updateConfig()));
    connect(dialog, TQ_SIGNAL(finished()), this, TQ_SLOT(configFinished()));

    dialog->show();
    tqApp->enter_loop();
    delete dialog;
}


void LocateProtocol::configFinished()
{
    kdDebug(7134) << "LocateProtocol::configFinished" << endl;

    tqApp->exit_loop();

    TQString html;
    if (m_configUpdated) {
        html = i18n("Configuration succesfully updated.");
    } else {
        html = i18n("Configuration unchanged.");
    }
    outputHtml("<h1>" + html + "</h1>");
}


void LocateProtocol::updateConfig()
{
    // It's not needed to update the config if it's still up to date.
    kdDebug(7134) << "LocateProtocol::updateConfig" << endl;

    KLocateConfig::self()->readConfig();
    m_config.m_caseSensitivity = (LocateCaseSensitivity) KLocateConfig::caseSensitivity();
    m_config.m_collapseDirectoryThreshold = KLocateConfig::collapseDirectoryThreshold();
    m_config.m_collapsedDisplay = KLocateConfig::collapsedDisplay();
    m_config.m_collapsedIcon = (LocateCollapsedIcon) KLocateConfig::collapsedIcon();
    m_config.m_whiteList = KLocateConfig::whiteList();
    m_config.m_blackList = KLocateConfig::blackList();

    m_locater.setupLocate(KLocateConfig::locateBinary(),
                          KLocateConfig::locateAdditionalArguments());

    m_configUpdated = true;
}


/////////////////////////////////////////////////////////////////////
// HELP

bool LocateProtocol::isHelpRequest()
{
    return m_url.path() == "help";
}


void LocateProtocol::helpRequest()
{
    // Redirect the user to our help documents.
    redirection("help:/tdeioslave/locate/");
    finished();
}


/////////////////////////////////////////////////////////////////////
// SEARCH STRUCTURES

LocateDirectory::LocateDirectory(LocateDirectory *parent, const TQString& path)
{
    m_parent = parent;
    m_path = path;
    m_childs.setAutoDelete(true);
    m_itemsCount = 0;
}


LocateDirectory *LocateDirectory::addPath(const TQString& path)
{
    if (path.startsWith(m_path)) {
        TQString relPath = path.mid(m_path.length());
        int p = relPath.findRev('/');
        if (p >= 0) {
            LocateDirectory *child = getSubDirectory(relPath.left(p));
            child->addItem(relPath.mid(p+1));
            return child;
        }
        addItem(relPath);
        return this;
    }
    if (m_parent != NULL) {
        return m_parent->addPath(path);
    }
    // This should not happen
    return this;
}


LocateDirectory *LocateDirectory::getSubDirectory(const TQString& relPath)
{
    TQString base = relPath;
    int p = relPath.find('/');
    if (p >= 0) {
        base = relPath.left(p);
    }
    LocateDirectory *child = m_childs.find(base);
    if (child == NULL) {
        child = new LocateDirectory(this, addTrailingSlash(m_path + base));
        m_childs.insert(base, child);
    }
    if (p >= 0) {
        return child->getSubDirectory(relPath.mid(p+1));
    }
    return child;
}


void LocateDirectory::addItem(const TQString& path)
{
    m_items += LocateItem(m_path + path, 0);
    m_itemsCount++;
}


int LocateDirectory::countMatchingItems(const LocateProtocol* protocol, int skip)
{
    int count = 0;
    LocateItems::ConstIterator item = m_items.begin();
    for (; item != m_items.end(); ++item) {
        if ((*item).m_subItems) {
            count += (*item).m_subItems;
        } else if (protocol->getRegExp().isMatching((*item).m_path.mid(skip))) {
            ++count;
        }
    }
    return count;
}


void LocateDirectory::prepareListing(const LocateProtocol* protocol, int skip)
{
    int n = m_path.length(), newSkip = n;
    if (skip > newSkip) newSkip = skip;

    // Recursively walk through all childs.
    LocateDirectoriesIterator child(m_childs);
    for (; child.current(); ++child) {
        child.current()->prepareListing(protocol, newSkip);
    }

    // Set m_fullCount to the total number of childs matching the pattern.
    m_fullCount = countMatchingItems(protocol, newSkip);

    // Collapse if directory part matches.
    LocateDirectory* parent = m_parent;
    if (parent == NULL) {
        parent = this;
    }
    if (n > skip && protocol->getRegExp().isMatching(m_path.mid(skip))) {
        // Directory part matches.
        m_childs.clear();
        m_items.clear();
        m_itemsCount = 0;
        parent->m_items += LocateItem(m_path, m_fullCount);
        ++parent->m_itemsCount;
        if (m_fullCount != 0) {
            parent->m_items += LocateItem(m_path, 0);
            ++parent->m_itemsCount;
        }
    }

    // Collapse if we got too many hits.
    int maxHits = protocol->getCollapseDirectoryThreshold();
    if (n > skip && maxHits != 0 && m_itemsCount > maxHits) {
        if (m_parent != NULL) {
            m_parent->m_items += LocateItem(m_path, m_fullCount);
            ++m_parent->m_itemsCount;
        } else {
            m_items.clear();
            m_items += LocateItem(m_path, m_fullCount);
            ++m_itemsCount;
        }
    } else {
        // Propagate items to parent.
        // (only root LocateDirectory runs listItems)
        if (m_parent != NULL) {
            m_parent->m_items += m_items;
            m_parent->m_itemsCount += m_itemsCount;
        }
    }
}


void LocateDirectory::listItems(LocateProtocol *protocol)
{
    LocateItems::ConstIterator item = m_items.begin();
    for (; item != m_items.end(); ++item) {
        protocol->addHit(stripTrailingSlash((*item).m_path), (*item).m_subItems);
    }
}


void LocateDirectory::debugTrace(int level)
{
    TQString ws;
    ws.fill(' ', level);
    kdDebug(7134) << ws << m_path << endl;
    LocateItems::ConstIterator item = m_items.begin();
    for (; item != m_items.end(); ++item) {
        kdDebug(7134) << ws << "+ " << (*item).m_path << endl;
    }
    LocateDirectoriesIterator child(m_childs);
    for (; child.current(); ++child) {
        child.current()->debugTrace(level+2);
    }
}


LocateItem::LocateItem()
{
}


LocateItem::LocateItem(const TQString& path, int subItems)
{
    m_path = path;
    m_subItems = subItems;
}


/////////////////////////////////////////////////////////////////////
// INVOKATION

extern "C" { TDE_EXPORT int kdemain(int argc, char **argv); }

extern "C"
{
    int kdemain(int argc, char **argv)
    {
        // We use TDEApplication instead of TDEInstance here, because we use a
        // config dialog and such gui stuff.
        TDEApplication::disableAutoDcopRegistration();
        TDEApplication app(argc, argv, "tdeio_locate", false, true, false);

        kdDebug(7134) << "*** Starting tdeio_locate " << endl;

        if (argc != 4) {
            kdDebug(7134) << "Usage: tdeio_locate  protocol domain-socket1 domain-socket2" << endl;
            exit(-1);
        }

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

        kdDebug(7134) << "*** tdeio_locate Done" << endl;
        return 0;
    }
}


#include "tdeio_locate.moc"
