/* bzflag
 * Copyright (c) 1993-2025 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "common.h"

// implementation header
#include "AutoCompleter.h"

// system headers
#include <ctype.h>
#include <string.h>
#include <algorithm>


AutoCompleter::WordRecord::WordRecord(const std::string& w, bool q)
{
    word = w;
    quoteString = q;
}

bool AutoCompleter::WordRecord::operator<(const WordRecord& w) const
{
    return (word < w.word);
}

bool AutoCompleter::WordRecord::operator==(const WordRecord& w) const
{
    return (word == w.word);
}

bool AutoCompleter::WordRecord::operator!=(const WordRecord& w) const
{
    return (word != w.word);
}


void AutoCompleter::registerWord(const std::string& str, bool quoteString)
{
    // only use 'quoteString' if it applies
    if (quoteString)
    {
        quoteString = false;
        for (int i = 0; i < (int)str.size(); i++)
        {
            if (isspace(str[i]))
            {
                quoteString = true;
                break;
            }
        }
    }
    WordRecord rec(str, quoteString);
    words.insert(std::lower_bound(words.begin(), words.end(), rec), rec);
}


void AutoCompleter::unregisterWord(const std::string& str)
{
    WordRecord rec(str, false);
    while (true)
    {
        std::vector<WordRecord>::iterator iter =
            std::lower_bound(words.begin(), words.end(), rec);
        if (iter != words.end() && *iter == rec)
            words.erase(iter);
        else
            return;
    }
}


std::string AutoCompleter::complete(const std::string& str, std::string* matches)
{
    if (str.size() == 0)
        return str;

    // from the last space
    const int lastSpace = str.find_last_of(" \t");
    const std::string tail = str.substr(lastSpace + 1);
    if (tail.size() == 0)
        return str;
    const std::string head = str.substr(0, lastSpace + 1);

    // find the first and last word with the prefix str
    std::vector<WordRecord>::iterator first, last;
    WordRecord rec(tail, false);
    first = std::lower_bound(words.begin(), words.end(), rec);
    if ((first == words.end()) ||
            (first->word.substr(0, tail.size()) != tail))
    {
        return str; // no match
    }
    std::string tmp = tail;
    tmp[tmp.size() - 1]++;
    last = std::lower_bound(first, words.end(), WordRecord(tmp, false)) - 1;

    // get a list of partial matches
    if (matches != NULL)
    {
        *matches = "";
        if (first != last)
        {
            std::vector<WordRecord>::iterator it = first;
            for (it = first; it != (last + 1); ++it)
            {
                std::string tmp2 = it->word;
                // strip the trailing whitespace
                while ((tmp2.size() > 0) && isspace(tmp2[tmp2.size() - 1]))
                    tmp2.resize(tmp2.size() - 1);
                if (tmp2.size() > 0)
                {
                    if (it->quoteString)
                        *matches += "\"" + tmp2 + "\" ";
                    else
                        *matches += tmp2 + " ";
                }
            }
        }
    }

    // FIXME: hack to allow the auto-completion to work with old /clientquery
    const char* hackCmd = "/clientquery";
    const unsigned int hackLen = strlen(hackCmd);
    const bool hack = (strncasecmp(head.c_str(), hackCmd, hackLen) == 0);

    const bool noQuotes = (lastSpace == -1) || hack;

    // return the largest common prefix without any spaces
    const int minLen = first->word.size() < last->word.size() ?
                       first->word.size() : last->word.size();
    int i;
    for (i = 0; i < minLen; ++i)
    {
        if ((!noQuotes && isspace(first->word[i])) ||
                (first->word[i] != last->word[i]))
            break;
    }

    if (!noQuotes && first->quoteString && (first == last))
    {
        const std::string quoted = "\"" + first->word + "\"";
        return (head + quoted);
    }
    else
        return (head + first->word.substr(0, i));
}



DefaultCompleter::DefaultCompleter()
{
    setDefaults();
}

void DefaultCompleter::setDefaults()
{
    words.clear();
    registerWord("/ban ");
    registerWord("/banlist");
    registerWord("/checkip ");
    registerWord("/countdown");
    registerWord("/clientquery");
    registerWord("/date");
    registerWord("/dumpvars");
    registerWord("/flag ");
    registerWord("reset");
    registerWord("up");
    registerWord("show");
    registerWord("/flaghistory");
    registerWord("/gameover");
    registerWord("/grouplist");
    registerWord("/groupperms");
    registerWord("/handicap");
    registerWord("/help");
    registerWord("/highlight ");
    registerWord("/hostban ");
    registerWord("/hostunban ");
    registerWord("/hostbanlist");
    registerWord("/idban ");
    registerWord("/idunban ");
    registerWord("/idbanlist");
    registerWord("/idlist");
    registerWord("/idlestats");
    registerWord("/idletime");
    registerWord("/jitterdrop");
    registerWord("/jitterwarn");
    registerWord("/kick ");
    registerWord("/kill ");
    registerWord("/lagdrop");
    registerWord("/lagstats");
    registerWord("/lagwarn ");
    registerWord("/localset ");
    registerWord("/modcount ");
    registerWord("/mute ");
    registerWord("/owner");
    registerWord("/packetlossdrop");
    registerWord("/packetlosswarn");
    registerWord("/password ");
    registerWord("/playerlist");
    registerWord("/poll ");
    registerWord("ban");
    registerWord("kick");
    registerWord("kill");
    registerWord("/quit");
    registerWord("/record");
    registerWord("start");
    registerWord("stop");
    registerWord("size");
    registerWord("rate");
    registerWord("stats");
    registerWord("file");
    registerWord("save");
    registerWord("/reload");
    registerWord("/masterban"); // also uses list
    registerWord("reload");
    registerWord("flush");
    registerWord("/removegroup ");
    registerWord("/replay ");
    registerWord("list");
    registerWord("load");
    registerWord("play");
    registerWord("skip");
    registerWord("/report ");
    registerWord("/reset");
    registerWord("/retexture");
    registerWord("/roampos ");
    registerWord("/saveworld ");
    registerWord("/say ");
    registerWord("/sendhelp ");
    registerWord("/serverdebug");
    registerWord("/serverquery");
    registerWord("/set");
    registerWord("/setgroup ");
    registerWord("/showgroup ");
    registerWord("/showperms ");
    registerWord("/shutdownserver");
    registerWord("/silence ");
    registerWord("/unsilence ");
    registerWord("/superkill");
    registerWord("/time");
    registerWord("/unban ");
    registerWord("/unmute ");
    registerWord("/uptime");
    registerWord("/veto");
    registerWord("/viewreports");
    registerWord("/vote");
    registerWord("/loadplugin");
    registerWord("/listplugins");
    registerWord("/unloadplugin");
}

// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
