/**
  *  \file server/talk/serverapplication.cpp
  *  \brief Class server::talk::ServerApplication
  */

#include "server/talk/serverapplication.hpp"
#include "afl/async/controller.hpp"
#include "afl/net/resp/protocolhandler.hpp"
#include "afl/net/server.hpp"
#include "afl/string/format.hpp"
#include "afl/sys/thread.hpp"
#include "server/common/sessionprotocolhandlerfactory.hpp"
#include "server/ports.hpp"
#include "server/talk/commandhandler.hpp"
#include "server/talk/root.hpp"
#include "server/talk/session.hpp"
#include "util/string.hpp"
#include "version.hpp"

using afl::async::InterruptOperation;

namespace {
    const char LOG_NAME[] = "talk";
}

server::talk::ServerApplication::ServerApplication(afl::sys::Environment& env, afl::io::FileSystem& fs, afl::net::NetworkStack& net, afl::async::Interrupt& intr)
    : Application(LOG_NAME, env, fs, net),
      m_listenAddress(DEFAULT_ADDRESS, TALK_PORT),
      m_dbAddress(DEFAULT_ADDRESS, DB_PORT),
      m_mailAddress(DEFAULT_ADDRESS, MAILOUT_PORT),
      m_keywordTableName(),
      m_config(),
      m_interrupt(intr)
{ }

server::talk::ServerApplication::~ServerApplication()
{ }

bool
server::talk::ServerApplication::handleCommandLineOption(const String_t& /*option*/, afl::sys::CommandLineParser& /*parser*/)
{
    return false;
}

void
server::talk::ServerApplication::serverMain()
{
    // Connect to servers.
    // Set both to auto-reconnect. Database is stateless, so it's ok.
    // Mail is stateful, but the worst that happens for a mid-way reconnect is a message gotten lost.
    afl::base::Deleter del;
    afl::net::CommandHandler& db(createClient(m_dbAddress, del, true));
    afl::net::CommandHandler& mail(createClient(m_mailAddress, del, true));

    // Set up root (global data)
    Root root(db, mail, m_config);
    root.log().addListener(log());
    if (!m_keywordTableName.empty()) {
        root.keywordTable().load(*fileSystem().openFile(m_keywordTableName, afl::io::FileSystem::OpenRead), log());
    }

    // Protocol Handler
    server::common::SessionProtocolHandlerFactory<Root, Session, afl::net::resp::ProtocolHandler, CommandHandler> factory(root);

    // Server
    afl::net::Server server(networkStack().listen(m_listenAddress, 10), factory);
    log().write(afl::sys::LogListener::Info, LOG_NAME, afl::string::Format("Listening on %s", m_listenAddress.toString()));

    // Server thread
    afl::sys::Thread serverThread("talk.server", server);
    serverThread.start();

    // Wait for termination request
    afl::async::Controller ctl;
    m_interrupt.wait(ctl, InterruptOperation::Kinds_t() + InterruptOperation::Break + InterruptOperation::Terminate);

    // Stop
    log().write(afl::sys::LogListener::Info, LOG_NAME, "Received stop signal, shutting down.");
    server.stop();
    serverThread.join();
}

bool
server::talk::ServerApplication::handleConfiguration(const String_t& key, const String_t& value)
{
    // ex planetscentral/talk/talk.cc:processConfig
    if (key == "TALK.HOST") {
        /* @q Talk.Host:Str (Config)
           Listen address */
        m_listenAddress.setName(value);
        return true;
    } else if (key == "TALK.PORT") {
        /* @q Talk.Port:Int (Config)
           Port number. */
        m_listenAddress.setService(value);
        return true;
    } else if (key == "TALK.THREADS") {
        /* @q Talk.Threads:Int (Config)
           Ignored in c2ng/c2talk-server for compatibility reasons.
           Number of threads (=maximum number of parallel connections) */
        return true;
    } else if (key == "TALK.MSGID") {
        /* @q Talk.MsgID:Str (Config)
           Suffix for creating NNTP Message-IDs.
           The value should start with a punctuator and must include a "@",
           for example, ".talk@msgid.example.com".
           The Id will be generated by prepending numbers (sequence number and {@type MID|posting Id}). */
        m_config.messageIdSuffix = value;
        return true;
    } else if (key == "TALK.PATH") {
        /* @q Talk.Path:Str (Config)
           Name of NNTP server, used for generating "Path" and "Xref" headers. */
        m_config.pathHost = value;
        return true;
    } else if (key == "TALK.WWWROOT") {
        /* @q Talk.WWWRoot:Str (Config)
           Root of web application, used for generating links. */
        m_config.baseUrl = value;
        return true;
    } else if (key == "TALK.SYNTAXDB") {
        /* @q Talk.SyntaxDB:Str (Config)
           Name of file with syntax database.
           If not specified, the syntax database will be empty ({SYNTAXGET} will always fail). */
        m_keywordTableName = value;
        return true;
    } else if (key == "REDIS.HOST") {
        m_dbAddress.setName(value);
        return true;
    } else if (key == "REDIS.PORT") {
        m_dbAddress.setService(value);
        return true;
    } else if (key == "MAILOUT.HOST") {
        m_mailAddress.setName(value);
        return true;
    } else if (key == "MAILOUT.PORT") {
        m_mailAddress.setService(value);
        return true;
    } else {
        return false;
    }
}

String_t
server::talk::ServerApplication::getApplicationName() const
{
    return afl::string::Format("PCC2 Talk Server v%s - (c) 2017-2023 Stefan Reuther", PCC2_VERSION);
}

String_t
server::talk::ServerApplication::getCommandLineOptionHelp() const
{
    return String_t();
}
