// Cyphesis Online RPG Server and AI Engine
// Copyright (C) 2001 Alistair Riddoch
//
// 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

// $Id: cycmd.cpp,v 1.114 2007-11-16 02:41:39 alriddoch Exp $

/// \page cycmd_index
///
/// \section Introduction
///
/// cycmd is a commandline tool to administrate the running server. For
/// information on the commands available, please see the unix manual page.
/// The manual page is generated from docbook sources, so can
/// also be converted into other formats.
///
/// The majority of the functionality is encapsulated by the Interactive
/// class template.

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "common/log.h"
#include "common/types.h"
#include "common/globals.h"

#include "common/Tick.h"

#include <Atlas/Objects/Encoder.h>
#include <Atlas/Net/Stream.h>
#include <Atlas/Objects/Decoder.h>
#include <Atlas/Codec.h>
#include <Atlas/Objects/Entity.h>
#include <Atlas/Objects/Anonymous.h>
#include <Atlas/Objects/Operation.h>

#include <varconf/config.h>

#include "common/Monitor.h"
#include "common/Connect.h"
#include "common/compose.hpp"

#include <skstream/skstream_unix.h>

#include <sigc++/connection.h>

#ifndef READLINE_CXX_SANE   // defined in config.h
extern "C" {
#endif
#define USE_VARARGS
#define PREFER_STDARG
#include <readline/readline.h>
#include <readline/history.h>
#ifndef READLINE_CXX_SANE
}
#endif

#include <iostream>
#include <algorithm>

#include <cstdio>

using Atlas::Message::Element;
using Atlas::Message::MapType;
using Atlas::Message::ListType;

using Atlas::Objects::Root;

using Atlas::Objects::Operation::Appearance;
using Atlas::Objects::Operation::Create;
using Atlas::Objects::Operation::Delete;
using Atlas::Objects::Operation::Disappearance;
using Atlas::Objects::Operation::Get;
using Atlas::Objects::Operation::Set;
using Atlas::Objects::Operation::Look;
using Atlas::Objects::Operation::Login;
using Atlas::Objects::Operation::Logout;
using Atlas::Objects::Operation::Talk;
using Atlas::Objects::Operation::Tick;
using Atlas::Objects::Entity::RootEntity;
using Atlas::Objects::Entity::Anonymous;

using Atlas::Objects::smart_dynamic_cast;

using Atlas::Objects::Operation::Monitor;
using Atlas::Objects::Operation::Connect;

/// \brief Entry in the global command table for cycmd
struct command {
    const char * cmd_string;
    const char * cmd_description;
};

struct command commands[] = {
    { "add_agent",      "Create an in-game agent", },
    { "connect",        "Connect server to a peer", },
    { "cancel",         "Cancel the current admin task", },
    { "delete",         "Delete an entity from the server", },
    { "get",            "Examine a class on the server", },
    { "find_by_name",   "Find an entity with the given name", },
    { "find_by_type",   "Find an entity with the given type", },
    { "flush",          "Flush entities from the server", },
    { "help",           "Display this help", },
    { "install",        "Install a new type", },
    { "look",           "Return the current server lobby", },
    { "logout",         "Log user out of server", },
    { "monitor",        "Enable in-game op monitoring", },
    { "query",          "Examine an object on the server", },
    { "reload",         "Reload the script for a type", },
    { "stat",           "Return current server status", },
    { "unmonitor",      "Disable in-game op monitoring", },
    { NULL,             "Guard", }
};


static void help()
{
    size_t max_length = 0;

    for (struct command * I = &commands[0]; I->cmd_string != NULL; ++I) {
       max_length = std::max(max_length, strlen(I->cmd_string));
    }
    max_length += 2;

    std::cout << "Cyphesis commands:" << std::endl << std::endl;

    for (struct command * I = &commands[0]; I->cmd_string != NULL; ++I) {
        std::cout << "    " << I->cmd_string
                  << std::string(max_length - strlen(I->cmd_string), ' ')
                  << I->cmd_description << std::endl;
    }
    std::cout << std::endl << std::flush;
}

/// \brief Base class for admin tasks which run for some time.
///
/// Typical tasks that inherit from this class are ones which last for
/// non trivial time and will typically require the user to be able to
/// continue issuing commands.
class AdminTask {
  protected:
    /// \brief Flag that indicates when the task is complete
    bool m_complete;

    ///\brief AdminTask constructor
    AdminTask() : m_complete(false) { }
  public:
    virtual ~AdminTask() { }

    /// \brief Set up the task processing user arguments
    virtual void setup(const std::string & arg, OpVector &) = 0;
    /// \brief Handle an operation from the server
    virtual void operation(const Operation &, OpVector &) = 0;

    /// \brief Check whether the task is complete
    ///
    /// @return true if the task is complete, false otherwise
    bool isComplete() const { return m_complete; }
};

/// \brief Task class for flushing the server of character entities
class Flusher : public AdminTask {
  protected:
    const std::string agentId;
    std::string type;
  public:
    explicit Flusher(const std::string & agent_id) : agentId(agent_id) { }

    void setup(const std::string & arg, OpVector & ret) {
        type = arg;

        // Send a look to search by type.
        Look l;

        Anonymous lmap;
        lmap->setParents(std::list<std::string>(1, type));
        l->setArgs1(lmap);
        l->setFrom(agentId);

        ret.push_back(l);
    }

    void operation(const Operation & op, OpVector & res) {
        if (op->getClassNo() == Atlas::Objects::Operation::SIGHT_NO) {
            // We have a sight op, check if its the sight of an entity we
            // want to delete.
            const std::vector<Root> & args = op->getArgs();
            if (args.empty()) {
                std::cerr << "Got empty sight" << std::endl << std::flush;
                return;
            }
            const Root & arg = args.front();
            assert(arg.isValid());
            RootEntity sight_ent = smart_dynamic_cast<RootEntity>(arg);
            if (!sight_ent.isValid()) {
                return;
            }
            if (!sight_ent->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
                std::cerr << "Got sight no ID" << std::endl << std::flush;
                return;
            }
            if (!sight_ent->hasAttrFlag(Atlas::Objects::PARENTS_FLAG)) {
                std::cerr << "Got sight no PARENTS" << std::endl << std::flush;
                return;
            }
            if (sight_ent->getParents().empty() ||
                sight_ent->getParents().front() != type) {
                return;
            }
            const std::string & id = sight_ent->getId();

            std::cout << "Deleting: " << type << "(" << id << ")"
                      << std::endl << std::flush;

            // Send a delete to the entity we have seen.
            Delete d;

            Anonymous dmap;
            dmap->setId(id);
            d->setArgs1(dmap);
            d->setFrom(agentId);
            d->setTo(id);

            res.push_back(d);

            // Send a tick for a short time in the future so that
            // we can look again once this entity is definitly gone.
            Tick t;

            Anonymous tick_arg;
            tick_arg->setName("flusher");

            t->setFrom(agentId);
            t->setTo(agentId);
            t->setFutureSeconds(0.1);
            t->setArgs1(tick_arg);

            res.push_back(t);
        } else if (op->getParents().front() == "tick") {
            // We have a tick op, check if its the one we sent ourselves
            // to schedule the next look.
            if (op->getArgs().empty() ||
                op->getArgs().front()->getName() != "flusher") {
                std::cout << "Not for us" << std::endl << std::flush;
                return;
            }

            // Send another look by type.
            Look l;

            Anonymous lmap;
            lmap->setParents(std::list<std::string>(1, type));
            l->setArgs1(lmap);
            l->setFrom(agentId);

            res.push_back(l);
        } else if (op->getParents().front() == "unseen") {
            // We have an unseen op, which signals our last look returned
            // no results.
            m_complete = true;
        }
    }
};

/// \brief Task class for monitoring all in-game operations occuring.
class OperationMonitor : public AdminTask {
  protected:
    int op_count;
    int start_time;
  public:
    int count() {
        return op_count;
    }

    int startTime() {
        return start_time;
    }

    virtual void setup(const std::string & arg, OpVector &) {
        struct timeval tv;

        gettimeofday(&tv, NULL);
        start_time = tv.tv_sec;
        op_count = 0;
    }

    virtual void operation(const Operation & op, OpVector &) {
        ++op_count;
        std::cout << op->getParents().front() << "(from=\"" << op->getFrom()
                  << "\",to=\"" << op->getTo() << "\")"
                  << std::endl << std::flush;
    }
};

/// \brief Class template for clients used to connect to and administrate
/// a cyphesis server.
template <class Stream>
class Interactive : public Atlas::Objects::ObjectsDecoder,
                    virtual public sigc::trackable
{
  private:
    bool error_flag, reply_flag, login_flag, avatar_flag, server_flag;
    int cli_fd;
    Atlas::Objects::ObjectsEncoder * encoder;
    Atlas::Codec * codec;
    Stream ios;
    std::string password;
    std::string username;
    std::string accountType;
    std::string accountId;
    std::string agentId;
    std::string agentName;
    std::string serverName;
    std::string systemType;
    std::string prompt;
    bool exit;
    AdminTask * currentTask;

    void output(const Element & item, int depth = 0);
  protected:

    void objectArrived(const Atlas::Objects::Root &);

    void appearanceArrived(const Operation &);
    void disappearanceArrived(const Operation &);
    void infoArrived(const Operation &);
    void errorArrived(const Operation &);
    void sightArrived(const Operation &);
    void soundArrived(const Operation &);

    int negotiate();
    void updatePrompt();
  public:
    Interactive() : error_flag(false), reply_flag(false), login_flag(false),
                    avatar_flag(false), server_flag(false), encoder(0),
                    codec(0), serverName("cyphesis"), prompt("cyphesis> "),
                    exit(false), currentTask(0)
                    { }
    ~Interactive() {
        if (encoder != 0) {
            delete encoder;
        }
        if (codec != 0) {
            delete codec;
        }
    }

    void send(const Operation &);
    int connect(const std::string & host);
    int login();
    int setup();
    void exec(const std::string & cmd, const std::string & arg);
    void loop();
    void poll(bool rewrite_prompt = true);
    void getLogin();
    void runCommand(char *);
    int runTask(AdminTask * task, const std::string & arg);
    int endTask();

    void setPassword(const std::string & passwd) {
        password = passwd;
    }

    void setUsername(const std::string & uname) {
        username = uname;
    }

    static void gotCommand(char *);
};

template <class Stream>
void Interactive<Stream>::output(const Element & item, int depth)
{
    switch (item.getType()) {
        case Element::TYPE_INT:
            std::cout << item.Int();
            break;
        case Element::TYPE_FLOAT:
            std::cout << item.Float();
            break;
        case Element::TYPE_STRING:
            std::cout << "\"" << item.String() << "\"";
            break;
        case Element::TYPE_LIST:
            {
                std::cout << "[ ";
                ListType::const_iterator I = item.List().begin();
                ListType::const_iterator Iend = item.List().end();
                for(; I != Iend; ++I) {
                    output(*I, depth);
                    std::cout << " ";
                }
                std::cout << "]";
            }
            break;
        case Element::TYPE_MAP:
            {
                std::cout << "{" << std::endl << std::flush;
                MapType::const_iterator I = item.Map().begin();
                MapType::const_iterator Iend = item.Map().end();
                for(; I != Iend; ++I) {
                    std::cout << std::string((depth + 1) * 4, ' ') << I->first << ": ";
                    output(I->second, depth + 1);
                    std::cout << std::endl;
                }
                std::cout << std::string(depth * 4, ' ') << "}";
            }
            break;
        default:
            std::cout << "(\?\?\?)";
            break;
    }
}

template <class Stream>
void Interactive<Stream>::objectArrived(const Atlas::Objects::Root & obj)
{
    Operation op = Atlas::Objects::smart_dynamic_cast<Operation>(obj);
    if (!op.isValid()) {
        std::cerr << "Non op object received from client" << std::endl << std::flush;
        if (!obj->isDefaultParents() && !obj->getParents().empty()) {
            std::cerr << "NOTICE: Unexpected object has parent "
                      << obj->getParents().front()
                      << std::endl << std::flush;
        }
        if (!obj->isDefaultObjtype()) {
            std::cerr << "NOTICE: Unexpected object has objtype "
                      << obj->getObjtype()
                      << std::endl << std::flush;
        }

        return;
    }

    if (currentTask != 0) {
        OpVector res;
        currentTask->operation(op, res);
        OpVector::const_iterator Iend = res.end();
        for (OpVector::const_iterator I = res.begin(); I != Iend; ++I) {
            encoder->streamObjectsMessage(*I);
        }

        ios << std::flush;

        if (currentTask->isComplete()) {
            delete currentTask;
            currentTask = 0;
        }
    }

    switch (op->getClassNo()) {
        case Atlas::Objects::Operation::APPEARANCE_NO:
            appearanceArrived(op);
            break;
        case Atlas::Objects::Operation::DISAPPEARANCE_NO:
            disappearanceArrived(op);
            break;
        case Atlas::Objects::Operation::INFO_NO:
            infoArrived(op);
            break;
        case Atlas::Objects::Operation::ERROR_NO:
            errorArrived(op);
            break;
        case Atlas::Objects::Operation::SIGHT_NO:
            sightArrived(op);
            break;
        case Atlas::Objects::Operation::SOUND_NO:
            soundArrived(op);
            break;
        default:
            break;
    }
}

template <class Stream>
void Interactive<Stream>::appearanceArrived(const Operation & op)
{
    if (accountId.empty()) {
        return;
    }
    if (accountId != op->getTo()) {
        // This is an IG op we are monitoring
        return;
    }
    if (op->getArgs().empty()) {
        return;
    }
    RootEntity ent = smart_dynamic_cast<RootEntity>(op->getArgs().front());
    if (!ent.isValid()) {
        std::cerr << "Got Appearance of non-entity" << std::endl << std::flush;
        return;
    }
    if (!ent->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
        std::cerr << "Got Appearance of non-string ID" << std::endl << std::flush;
        return;
    }
    const std::string & id = ent->getId();
    std::cout << "Appearance(id: " << id << ")";
    if (!ent->hasAttrFlag(Atlas::Objects::Entity::LOC_FLAG)) {
        std::cout << std::endl << std::flush;
        return;
    }
    const std::string & loc = ent->getLoc();
    std::cout << " in " << loc << std::endl;
    if (loc == "lobby") {
        std::cout << id << " has logged in." << std::endl;
    }
    std::cout << std::flush;
}

template <class Stream>
void Interactive<Stream>::disappearanceArrived(const Operation & op)
{
    if (accountId.empty()) {
        return;
    }
    if (accountId != op->getTo()) {
        // This is an IG op we are monitoring
        return;
    }
    if (op->getArgs().empty()) {
        return;
    }
    RootEntity ent = smart_dynamic_cast<RootEntity>(op->getArgs().front());
    if (!ent.isValid()) {
        std::cerr << "Got Disappearance of non-entity" << std::endl << std::flush;
        return;
    }
    if (!ent->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
        std::cerr << "Got Disappearance of non-string ID" << std::endl << std::flush;
        return;
    }
    const std::string & id = ent->getId();
    std::cout << "Disappearance(id: " << id << ")";
    if (!ent->hasAttrFlag(Atlas::Objects::Entity::LOC_FLAG)) {
        std::cout << std::endl << std::flush;
        return;
    }
    const std::string & loc = ent->getLoc();
    std::cout << " in " << loc << std::endl;
    if (loc == "lobby") {
        std::cout << id << " has logged out." << std::endl;
    }
    std::cout << std::flush;
}

template <class Stream>
void Interactive<Stream>::infoArrived(const Operation & op)
{
    reply_flag = true;
    if (op->getArgs().empty()) {
        return;
    }
    const Root & ent = op->getArgs().front();
    if (login_flag) {
        std::cout << "login success" << std::endl << std::flush;
        if (ent->isDefaultId()) {
            std::cerr << "ERROR: Response to login does not contain account id"
                      << std::endl << std::flush;
            
        } else {
            accountId = ent->getId();
        }
        if (!ent->isDefaultParents()) {
            const std::list<std::string> & parents = ent->getParents();
            if (!parents.empty()) {
                accountType = parents.front();
            }
        }
    } else if (avatar_flag) {
        std::cout << "Create agent success" << std::endl << std::flush;
        if (!ent->hasAttrFlag(Atlas::Objects::ID_FLAG)) {
            std::cerr << "ERROR: Response to agent create does not contain agent id"
                      << std::endl << std::flush;
            
        } else {
            agentId = ent->getId();
            avatar_flag = false;
        }
    } else if (server_flag) {
        std::cout << "Server query success" << std::endl << std::flush;
        if (!ent->isDefaultName()) {
            serverName = ent->getName();
            std::string::size_type p = serverName.find(".");
            if (p != std::string::npos) {
                serverName = serverName.substr(0, p);
            }
            updatePrompt();
        }
        Element raw_attr;
        if (ent->copyAttr("server", raw_attr) == 0) {
            if (raw_attr.isString()) {
                systemType = raw_attr.String();
                updatePrompt();
            }
        }
        server_flag = false;
    } else {
        std::cout << "Info(" << std::endl;
        MapType entmap = ent->asMessage();
        MapType::const_iterator Iend = entmap.end();
        for (MapType::const_iterator I = entmap.begin(); I != Iend; ++I) {
            const Element & item = I->second;
            std::cout << "    " << I->first << ": ";
            output(item, 1);
            std::cout << std::endl;
        }
        std::cout << ")" << std::endl << std::flush;
        // Display results of command
    }
}

template <class Stream>
void Interactive<Stream>::errorArrived(const Operation & op)
{
    reply_flag = true;
    error_flag = true;
    std::cout << "Error(";
    const std::vector<Root> & args = op->getArgs();
    const Root & arg = args.front();
    Element message_attr;
    if (arg->copyAttr("message", message_attr) == 0 && message_attr.isString()) {
        std::cout << message_attr.asString();
    }
    std::cout << ")" << std::endl << std::flush;
}

template <class Stream>
void Interactive<Stream>::sightArrived(const Operation & op)
{
    if (accountId.empty()) {
        return;
    }
    if (accountId != op->getTo() && agentId != op->getTo()) {
        // This is an IG op we are monitoring
        return;
    }
    reply_flag = true;
    std::cout << "Sight(" << std::endl;
    const MapType & ent = op->getArgs().front()->asMessage();
    MapType::const_iterator Iend = ent.end();
    for (MapType::const_iterator I = ent.begin(); I != Iend; ++I) {
        const Element & item = I->second;
        std::cout << "      " << I->first << ":";
        output(item, 1);
        std::cout << std::endl;
    }
    std::cout << ")" << std::endl << std::flush;
}

template <class Stream>
void Interactive<Stream>::soundArrived(const Operation & op)
{
    if (accountId.empty()) {
        return;
    }
    if (accountId != op->getTo()) {
        // This is an IG op we are monitoring
        return;
    }
    reply_flag = true;
    const MapType & arg = op->getArgs().front()->asMessage();
    MapType::const_iterator I = arg.find("from");
    if (I == arg.end() || !I->second.isString()) {
        std::cout << "Sound arg has no from" << std::endl << std::flush;
        return;
    }
    const std::string & from = I->second.asString();
    I = arg.find("args");
    if (I == arg.end() || !I->second.isList()
                       || I->second.asList().empty()
                       || !I->second.asList().front().isMap()) {
        std::cout << "Sound arg has no args" << std::endl << std::flush;
        return;
    }
    const MapType & ent = I->second.asList().front().asMap();
    I = ent.find("say");
    if (I == ent.end() || !I->second.isString()) {
        std::cout << "Sound arg arg has no say" << std::endl << std::flush;
        return;
    }
    const std::string & say = I->second.asString();
    std::cout << "[" << from << "] " << say
              << std::endl << std::flush;
}

sigc::signal<void, char *> CmdLine;

template <class Stream>
void Interactive<Stream>::gotCommand(char * cmd)
{
    CmdLine.emit(cmd);
}

template <class Stream>
void Interactive<Stream>::runCommand(char * cmd)
{
    if (cmd == NULL) {
        exit = true;
        std::cout << std::endl << std::flush;
        return;
    }

    if (*cmd == 0) {
        free(cmd);
        return;
    }

    add_history(cmd);

    char * arg = strchr(cmd, ' ');
    if (arg != NULL) {
        *arg++ = 0;
        int len = strlen(arg);
        while (len > 0 && arg[--len] == ' ') { arg[len] = 0; }
    } else {
        arg = "";
    }

    exec(cmd, arg);
}

template <class Stream>
int Interactive<Stream>::runTask(AdminTask * task, const std::string & arg)
{
    assert(task != 0);

    if (currentTask != 0) {
        std::cout << "Busy" << std::endl << std::flush;
        return -1;
    }

    currentTask = task;

    OpVector res;

    currentTask->setup(arg, res);

    OpVector::const_iterator Iend = res.end();
    for (OpVector::const_iterator I = res.begin(); I != Iend; ++I) {
        encoder->streamObjectsMessage(*I);
    }
    return 0;
}

template <class Stream>
int Interactive<Stream>::endTask()
{
    if (currentTask == 0) {
        return -1;
    }
    delete currentTask;
    currentTask = 0;
    return 0;
}

int completion_iterator = 0;

char * completion_generator(const char * text, int state)
{
    if (state == 0) {
        completion_iterator = 0;
    }
    for (int i = completion_iterator; commands[i].cmd_string != 0; ++i) {
        if (strncmp(text, commands[i].cmd_string, strlen(text)) == 0) {
            completion_iterator = i + 1;
            return strdup(commands[i].cmd_string);
        }
    }
    return 0;
}

template <class Stream>
void Interactive<Stream>::loop()
{
    rl_callback_handler_install(prompt.c_str(),
                                &Interactive<Stream>::gotCommand);
    rl_completion_entry_function = &completion_generator;
    CmdLine.connect(sigc::mem_fun(this, &Interactive<Stream>::runCommand));
    while (!exit) {
        poll();
    };
    std::cout << std::endl << std::flush;
    rl_callback_handler_remove();
}

template <class Stream>
void Interactive<Stream>::poll(bool rewrite_prompt)
// poll the codec if select says there is something there.
{
    fd_set infds;
    struct timeval tv;
    int retval;

    FD_ZERO(&infds);

    FD_SET(cli_fd, &infds);
    FD_SET(STDIN_FILENO, &infds);

    tv.tv_sec = 0;
    tv.tv_usec = 500000;

    if (rewrite_prompt) {
        retval = select(cli_fd+1, &infds, NULL, NULL, NULL);
    } else {
        retval = select(cli_fd+1, &infds, NULL, NULL, &tv);
    }

    if (retval > 0) {
        if (FD_ISSET(cli_fd, &infds)) {
            if (ios.peek() == -1) {
                std::cout << "Server disconnected" << std::endl << std::flush;
                exit = true;
            } else {
                if (rewrite_prompt) {
                    std::cout << std::endl;
                }
                codec->poll();
                if (rewrite_prompt) {
                    rl_forced_update_display();
                }
            }
        }
        if (FD_ISSET(STDIN_FILENO, &infds)) {
            rl_callback_read_char();
        }
    }
}

template <class Stream>
void Interactive<Stream>::getLogin()
{
    // This needs to be re-written to hide input, so the password can be
    // secret
    std::cout << "Username: " << std::flush;
    std::cin >> username;
    std::cout << "Password: " << std::flush;
    std::cin >> password;
}

template<>
int Interactive<tcp_socket_stream>::connect(const std::string & host)
{
    std::cout << "Connecting... " << std::flush;
    ios.open(host, client_port_num);
    if (!ios.is_open()) {
        std::cout << "failed." << std::endl << std::flush;
        return -1;
    }
    std::cout << "done." << std::endl << std::flush;
    cli_fd = ios.getSocket();

    return negotiate();
}

template<>
int Interactive<unix_socket_stream>::connect(const std::string & filename)
{
    std::cout << "Connecting... " << std::flush;
    ios.open(filename);
    if (!ios.is_open()) {
        std::cout << "failed." << std::endl << std::flush;
        return -1;
    }
    std::cout << "done." << std::endl << std::flush;
    cli_fd = ios.getSocket();

    return negotiate();
}

template <class Stream>
int Interactive<Stream>::negotiate()
{
    // Do client negotiation with the server
    Atlas::Net::StreamConnect conn("cycmd", ios);

    std::cout << "Negotiating... " << std::flush;
    while (conn.getState() == Atlas::Negotiate::IN_PROGRESS) {
        // conn.poll() does all the negotiation
        conn.poll();
    }
    std::cout << "done." << std::endl << std::flush;

    // Check whether negotiation was successful
    if (conn.getState() == Atlas::Negotiate::FAILED) {
        std::cerr << "Failed to negotiate." << std::endl;
        return -1;
    }
    // Negotiation was successful

    // Get the codec that negotiation established
    codec = conn.getCodec(*this);

    // Create the encoder
    encoder = new Atlas::Objects::ObjectsEncoder(*codec);

    // Send whatever codec specific data marks the beginning of a stream
    codec->streamBegin();

    return 0;

}

template <class Stream>
void Interactive<Stream>::updatePrompt()
{
    std::string designation(">");
    if (!username.empty()) {
        prompt = username + "@";
        if (accountType == "admin") {
            designation = "#";
        } else {
            designation = "$";
        }
    } else {
        prompt = "";
    }
    prompt += serverName;
    prompt += " ";
    prompt += systemType;
    prompt += designation;
    prompt += " ";
    rl_set_prompt(prompt.c_str());
}

template <class Stream>
int Interactive<Stream>::login()
{
    Atlas::Objects::Entity::Account account;
    Login l;
    error_flag = false;
    reply_flag = false;
    login_flag = true;
 
    account->setAttr("username", username);
    account->setAttr("password", password);
 
    l->setArgs1(account);
 
    encoder->streamObjectsMessage(l);

    ios << std::flush;
 
    while (!reply_flag) {
       codec->poll();
    }

    login_flag = false;

    if (!error_flag) {
       updatePrompt();
       return 0;
    }
    return -1;
}

template <class Stream>
int Interactive<Stream>::setup()
{
    Get get;

    encoder->streamObjectsMessage(get);
    ios << std::flush;
    server_flag = true;

    reply_flag = true;
    while (server_flag && !error_flag) {
       codec->poll();
    }

    server_flag = false;
    if (!error_flag) {
       return 0;
    }
    return -1;
}

template <class Stream>
void Interactive<Stream>::exec(const std::string & cmd, const std::string & arg)
{
    bool reply_expected = true;
    reply_flag = false;
    error_flag = false;

    if (cmd == "stat") {
        Get g;
        encoder->streamObjectsMessage(g);
    } else if (cmd == "install") {
        size_t space = arg.find(' ');
        if (space == std::string::npos || space >= (arg.size() - 1)) {
            std::cout << "usage: install <type id> <parent id>"
                      << std::endl << std::flush;
        } else {
            Create c;
            c->setFrom(accountId);
            Anonymous ent;
            ent->setId(std::string(arg, 0, space));
            ent->setObjtype("class");
            ent->setParents(std::list<std::string>(1, std::string(arg, space + 1)));
            c->setArgs1(ent);
            encoder->streamObjectsMessage(c);
        }
        reply_expected = false;
    } else if (cmd == "look") {
        Look l;
        l->setFrom(accountId);
        encoder->streamObjectsMessage(l);
    } else if (cmd == "logout") {
        Logout l;
        l->setFrom(accountId);
        if (!arg.empty()) {
            Anonymous lmap;
            lmap->setId(arg);
            l->setArgs1(lmap);
            reply_expected = false;
        }
        encoder->streamObjectsMessage(l);
    } else if (cmd == "say") {
        Talk t;
        Anonymous ent;
        ent->setAttr("say", arg);
        t->setArgs1(ent);
        t->setFrom(accountId);
        encoder->streamObjectsMessage(t);
    } else if (cmd == "help" || cmd == "?") {
        reply_expected = false;
        help();
    } else if (cmd == "query") {
        Get g;

        Anonymous cmap;
        cmap->setObjtype("obj");
        if (!arg.empty()) {
            cmap->setId(arg);
        }
        g->setArgs1(cmap);
        g->setFrom(accountId);

        encoder->streamObjectsMessage(g);
    } else if (cmd == "reload") {
        if (arg.empty()) {
            reply_expected = false;
            std::cout << "reload: Argument required" << std::endl << std::flush;
        } else {
            Set s;

            Anonymous tmap;
            tmap->setObjtype("class");
            tmap->setId(arg);
            s->setArgs1(tmap);
            s->setFrom(accountId);

            encoder->streamObjectsMessage(s);
        }
    } else if (cmd == "get") {
        Get g;

        Anonymous cmap;
        cmap->setObjtype("class");
        if (!arg.empty()) {
            cmap->setId(arg);
        }
        g->setArgs1(cmap);
        g->setFrom(accountId);

        encoder->streamObjectsMessage(g);
    } else if (cmd == "monitor") {
        AdminTask * task = new OperationMonitor;
        if (runTask(task, arg) == 0) {
            Monitor m;

            m->setArgs1(Anonymous());
            m->setFrom(accountId);

            encoder->streamObjectsMessage(m);
        }

        reply_expected = false;
    } else if (cmd == "unmonitor") {
        OperationMonitor * om = dynamic_cast<OperationMonitor *>(currentTask);

        if (om != 0) {
            Monitor m;

            m->setFrom(accountId);

            encoder->streamObjectsMessage(m);

            reply_expected = false;

            struct timeval tv;
            gettimeofday(&tv, NULL);
            int monitor_time = tv.tv_sec - om->startTime();

            std::cout << om->count() << " operations monitored in "
                      << monitor_time << " seconds = "
                      << om->count() / monitor_time
                      << " operations per second"
                      << std::endl << std::flush;

            endTask();
        }
    } else if (cmd == "connect") {
        reply_expected = false;
        Connect m;

        Anonymous cmap;
        cmap->setAttr("hostname", arg);
        m->setArgs1(cmap);
        m->setFrom(accountId);

        encoder->streamObjectsMessage(m);
    } else if (cmd == "add_agent") {
        std::string agent_type("creator");

        if (!arg.empty()) {
            agent_type = arg;
        }
        
        Create c;

        Anonymous cmap;
        cmap->setParents(std::list<std::string>(1, agent_type));
        cmap->setName("cycmd agent");
        cmap->setObjtype("obj");
        c->setArgs1(cmap);
        c->setFrom(accountId);

        avatar_flag = true;

        encoder->streamObjectsMessage(c);
    } else if (cmd == "delete") {
        if (agentId.empty()) {
            std::cout << "Use add_agent to add an in-game agent first" << std::endl << std::flush;
            reply_expected = false;
        } else if (arg.empty()) {
            std::cout << "Please specify the entity to delete" << std::endl << std::flush;
            reply_expected = false;
        } else {
            Delete del;

            Anonymous del_arg;
            del_arg->setId(arg);
            del->setArgs1(del_arg);
            del->setFrom(agentId);
            del->setTo(arg);

            encoder->streamObjectsMessage(del);

            reply_expected = false;
        }
    } else if (cmd == "find_by_name") {
        if (agentId.empty()) {
            std::cout << "Use add_agent to add an in-game agent first" << std::endl << std::flush;
            reply_expected = false;
        } else if (arg.empty()) {
            std::cout << "Please specify the name to search for" << std::endl << std::flush;
            reply_expected = false;
        } else {
            Look l;

            Anonymous lmap;
            lmap->setName(arg);
            l->setArgs1(lmap);
            l->setFrom(agentId);

            encoder->streamObjectsMessage(l);

            reply_expected = false;
        }
    } else if (cmd == "find_by_type") {
        if (agentId.empty()) {
            std::cout << "Use add_agent to add an in-game agent first" << std::endl << std::flush;
            reply_expected = false;
        } else if (arg.empty()) {
            std::cout << "Please specify the type to search for" << std::endl << std::flush;
            reply_expected = false;
        } else {
            Look l;

            Anonymous lmap;
            lmap->setParents(std::list<std::string>(1, arg));
            l->setArgs1(lmap);
            l->setFrom(agentId);

            encoder->streamObjectsMessage(l);

            reply_expected = false;
        }
    } else if (cmd == "flush") {
        if (agentId.empty()) {
            std::cout << "Use add_agent to add an in-game agent first" << std::endl << std::flush;
            reply_expected = false;
        } else if (arg.empty()) {
            std::cout << "Please specify the type to flush" << std::endl << std::flush;
            reply_expected = false;
        } else {
            AdminTask * task = new Flusher(agentId);
            runTask(task, arg);
            reply_expected = false;
        }
    } else if (cmd == "cancel") {
        if (endTask() != 0) {
            std::cout << "No task currently running" << std::endl << std::flush;
        }
    } else {
        reply_expected = false;
        std::cout << cmd << ": Command not known" << std::endl << std::flush;
    }

    ios << std::flush;

    if (!reply_expected) { return; }
    // Wait for reply
    time_t wait_start_time = time(NULL);
    while (!reply_flag) {
       if (time(NULL) - wait_start_time > 5) {
           std::cout << cmd << ": No reply from server" << std::endl << std::flush;
           return;
       }
       poll(false);
    }
}

static void usage(char * prg)
{
    std::cerr << "usage: " << prg << " [ cmd [ server ] ]" << std::endl << std::flush;
}

int main(int argc, char ** argv)
{
    int config_status = loadConfig(argc, argv, USAGE_CYCMD); 
    if (config_status < 0) {
        if (config_status == CONFIG_VERSION) {
            reportVersion(argv[0]);
            return 0;
        } else if (config_status == CONFIG_HELP) {
            showUsage(argv[0], USAGE_CYCMD, "[ cmd [ server ] ]");
            return 0;
        } else if (config_status != CONFIG_ERROR) {
            log(ERROR, "Unknown error reading configuration.");
        }
        // Fatal error loading config file
        return 1;
    }

    int optind = config_status;

    std::string server;
    readConfigItem("client", "serverhost", server);

    int useslave = 0;
    readConfigItem("client", "useslave", useslave);

    bool interactive = true;
    std::string cmd;
    if (optind < argc) {
        if ((argc - optind) == 2) {
            server = argv[optind + 1];
        } else if ((argc - optind) > 2) {
            usage(argv[0]);
            return 1;
        }
        cmd = argv[optind];
        interactive = false;
    }

    if (server.empty()) {
        std::string localSocket = var_directory + "/tmp/";
        if (useslave != 0) {
            localSocket += slave_socket_name;
        } else {
            localSocket += client_socket_name;
        }

        std::cout << "Attempting local connection" << std::endl << std::flush;
        Interactive<unix_socket_stream> bridge;
        if (bridge.connect(localSocket) == 0) {
            bridge.setUsername("admin");

            bridge.setup();
            std::cout << "Logging in... " << std::flush;
            if (bridge.login() != 0) {
                std::cout << "failed." << std::endl << std::flush;
                bridge.getLogin();

                std::cout << "Logging in... " << std::flush;
                if (!bridge.login()) {
                    std::cout << "failed." << std::endl << std::flush;
                    return 1;
                }
            }
            std::cout << "done." << std::endl << std::flush;
            if (!interactive) {
                bridge.exec(cmd, "");
                return 0;
            } else {
                bridge.loop();
            }
            return 0;
        }
        server = "localhost";
    }
    
    std::cerr << "Attempting tcp connection" << std::endl << std::flush;

    Interactive<tcp_socket_stream> bridge;

    if (bridge.connect(server) != 0) {
        return 1;
    }
    bridge.setup();
    if (!interactive) {
        std::cerr << "WARNING: No login details available for remote host"
                  << std::endl
                  << "WARNING: Attempting command without logging in"
                  << std::endl << std::flush;
    } else {
        bridge.getLogin();
        std::cout << "Logging in... " << std::flush;
        if (bridge.login() != 0) {
            std::cout << "failed." << std::endl << std::flush;
            return 1;
        }
        std::cout << "done." << std::endl << std::flush;
    }
    if (!interactive) {
        bridge.exec(cmd, "");
    } else {
        bridge.loop();
    }
    delete global_conf;
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1