#include <stddef.h> // for size_t
#include <stdio.h>  // for fprintf, stderr

#include "vrpn_Auxiliary_Logger.h"

vrpn_Auxiliary_Logger::vrpn_Auxiliary_Logger(const char *name,
                                             vrpn_Connection *c)
    : vrpn_BaseClass(name, c)
{
    vrpn_BaseClass::init();
}

int vrpn_Auxiliary_Logger::register_types(void)
{
    request_logging_m_id = d_connection->register_message_type(
        "vrpn_Auxiliary_Logger Logging_request");
    report_logging_m_id = d_connection->register_message_type(
        "vrpn_Auxiliary_Logger Logging_response");
    request_logging_status_m_id = d_connection->register_message_type(
        "vrpn_Auxiliary_Logger Logging_status_request");

    if ((request_logging_m_id == -1) || (report_logging_m_id == -1) ||
        (request_logging_status_m_id == -1)) {
        d_connection = NULL;
        return -1;
    }
    else {
        return 0;
    }
}

// Send a message of the specified type.
// Note that the pointers may be NULL, so we need to check for this.
bool vrpn_Auxiliary_Logger::pack_log_message_of_type(
    vrpn_int32 type, const char *local_in_logfile_name,
    const char *local_out_logfile_name, const char *remote_in_logfile_name,
    const char *remote_out_logfile_name)
{
    if (!d_connection) {
        return false;
    }

    // Figure out the lengths, handling NULL pointers.
    vrpn_int32 lil = 0;
    if (local_in_logfile_name) {
        lil = static_cast<vrpn_int32>(strlen(local_in_logfile_name));
    }
    vrpn_int32 lol = 0;
    if (local_out_logfile_name) {
        lol = static_cast<vrpn_int32>(strlen(local_out_logfile_name));
    }
    vrpn_int32 ril = 0;
    if (remote_in_logfile_name) {
        ril = static_cast<vrpn_int32>(strlen(remote_in_logfile_name));
    }
    vrpn_int32 rol = 0;
    if (remote_out_logfile_name) {
        rol = static_cast<vrpn_int32>(strlen(remote_out_logfile_name));
    }

    struct timeval now;
    vrpn_int32 bufsize =
        static_cast<vrpn_int32>(4 * sizeof(lil) + lil + lol + ril + rol);
    char *buf = new char[bufsize];
    if (buf == NULL) {
        fprintf(stderr, "vrpn_Auxiliary_Logger::pack_log_message_of_type(): "
                        "Out of memory.\n");
        return false;
    }

    // Pack a message with the requested type from our sender ID that has
    // first the four lengths of the strings and then the four strings.
    // Do not include the NULL termination of the strings in the buffer.

    vrpn_gettimeofday(&now, NULL);
    char *bpp = buf;
    char **bp = &bpp;
    vrpn_int32 bufleft = bufsize;
    vrpn_buffer(bp, &bufleft, lil);
    vrpn_buffer(bp, &bufleft, lol);
    vrpn_buffer(bp, &bufleft, ril);
    vrpn_buffer(bp, &bufleft, rol);
    if (lil) {
        vrpn_buffer(bp, &bufleft, local_in_logfile_name, lil);
    }
    if (lol) {
        vrpn_buffer(bp, &bufleft, local_out_logfile_name, lol);
    }
    if (ril) {
        vrpn_buffer(bp, &bufleft, remote_in_logfile_name, ril);
    }
    if (rol) {
        vrpn_buffer(bp, &bufleft, remote_out_logfile_name, rol);
    }
    int ret =
        d_connection->pack_message(bufsize - bufleft, now, type, d_sender_id,
                                   buf, vrpn_CONNECTION_RELIABLE);
    delete[] buf;
    return (ret == 0);
}

// Unpack the strings from the buffer.  Return non-NULL pointers to
// strings (an empty string when a file name is empty).
bool vrpn_Auxiliary_Logger::unpack_log_message_from_buffer(
    const char *buf, vrpn_int32 buflen, char **local_in_logfile_name,
    char **local_out_logfile_name, char **remote_in_logfile_name,
    char **remote_out_logfile_name)
{
    const char *bufptr = buf;

    // Make sure that the buffer contains at least enough space for the four
    // length
    // values, then pull the lengths of the strings out of the buffer
    vrpn_int32 localInNameLen, localOutNameLen, remoteInNameLen,
        remoteOutNameLen;
    if (static_cast<size_t>(buflen) < 4 * sizeof(localInNameLen)) {
        fprintf(stderr, "vrpn_Auxiliary_Logger::unpack_log_message_from_buffer:"
                        " Buffer too small for lengths.\n");
        return false;
    }
    vrpn_unbuffer(&bufptr, &localInNameLen);
    vrpn_unbuffer(&bufptr, &localOutNameLen);
    vrpn_unbuffer(&bufptr, &remoteInNameLen);
    vrpn_unbuffer(&bufptr, &remoteOutNameLen);

    // Make sure we have enough room in the buffer for the four sizes and also
    // the four
    // strings of the appropriate size.  If so, allocate the space for the
    // strings and then
    // copy them out and NULL-terminate them.  They are not NULL-terminated in
    // the message
    // buffer.
    int size = 4 * sizeof(localInNameLen) + localInNameLen + localOutNameLen +
               remoteInNameLen + remoteOutNameLen;
    if (buflen != size) {
        fprintf(stderr, "vrpn_Auxiliary_Logger::unpack_log_message_from_buffer:"
                        " Buffer size incorrect\n");
        return false;
    }
    (*local_in_logfile_name) = NULL;
    (*local_out_logfile_name) = NULL;
    (*remote_in_logfile_name) = NULL;
    (*remote_out_logfile_name) = NULL;
    if (localInNameLen > 0) {
        if (((*local_in_logfile_name) = new char[localInNameLen + 1]) == NULL) {
            fprintf(stderr, "vrpn_Auxiliary_Logger::unpack_log_message_from_"
                            "buffer: Out of memory\n");
            return false;
        }
        memcpy(*local_in_logfile_name, bufptr, localInNameLen);
        (*local_in_logfile_name)[localInNameLen] = '\0';
        bufptr += localInNameLen;
    }
    else {
        if (((*local_in_logfile_name) = new char[2]) == NULL) {
            fprintf(stderr, "vrpn_Auxiliary_Logger::unpack_log_message_from_"
                            "buffer: Out of memory\n");
            return false;
        }
        (*local_in_logfile_name)[0] = '\0';
    }
    if (localOutNameLen > 0) {
        if (((*local_out_logfile_name) = new char[localOutNameLen + 1]) ==
            NULL) {
            fprintf(stderr, "vrpn_Auxiliary_Logger::unpack_log_message_from_"
                            "buffer: Out of memory\n");
            return false;
        }
        memcpy(*local_out_logfile_name, bufptr, localOutNameLen);
        (*local_out_logfile_name)[localOutNameLen] = '\0';
        bufptr += localOutNameLen;
    }
    else {
        if (((*local_out_logfile_name) = new char[2]) == NULL) {
            fprintf(stderr, "vrpn_Auxiliary_Logger::unpack_log_message_from_"
                            "buffer: Out of memory\n");
            return false;
        }
        (*local_out_logfile_name)[0] = '\0';
    }
    if (remoteInNameLen > 0) {
        if (((*remote_in_logfile_name) = new char[remoteInNameLen + 1]) ==
            NULL) {
            fprintf(stderr, "vrpn_Auxiliary_Logger::unpack_log_message_from_"
                            "buffer: Out of memory\n");
            return false;
        }
        memcpy(*remote_in_logfile_name, bufptr, remoteInNameLen);
        (*remote_in_logfile_name)[remoteInNameLen] = '\0';
        bufptr += remoteInNameLen;
    }
    else {
        if (((*remote_in_logfile_name) = new char[2]) == NULL) {
            fprintf(stderr, "vrpn_Auxiliary_Logger::unpack_log_message_from_"
                            "buffer: Out of memory\n");
            return false;
        }
        (*remote_in_logfile_name)[0] = '\0';
    }
    if (remoteOutNameLen > 0) {
        if (((*remote_out_logfile_name) = new char[remoteOutNameLen + 1]) ==
            NULL) {
            fprintf(stderr, "vrpn_Auxiliary_Logger::unpack_log_message_from_"
                            "buffer: Out of memory\n");
            return false;
        }
        memcpy(*remote_out_logfile_name, bufptr, remoteOutNameLen);
        (*remote_out_logfile_name)[remoteOutNameLen] = '\0';
        bufptr += remoteOutNameLen;
    }
    else {
        if (((*remote_out_logfile_name) = new char[2]) == NULL) {
            fprintf(stderr, "vrpn_Auxiliary_Logger::unpack_log_message_from_"
                            "buffer: Out of memory\n");
            return false;
        }
        (*remote_out_logfile_name)[0] = '\0';
    }

    return true;
}

vrpn_Auxiliary_Logger_Server::vrpn_Auxiliary_Logger_Server(const char *name,
                                                           vrpn_Connection *c)
    : vrpn_Auxiliary_Logger(name, c)
{
    // Register a handler for the dropped last connection message.
    dropped_last_connection_m_id =
        d_connection->register_message_type(vrpn_dropped_last_connection);
    if (dropped_last_connection_m_id == -1) {
        fprintf(stderr, "vrpn_Auxiliary_Logger_Server::vrpn_Auxiliary_Logger_"
                        "Server: can't register dropped last connection "
                        "type\n");
        d_connection = NULL;
        return;
    }
    if (register_autodeleted_handler(dropped_last_connection_m_id,
                                     static_handle_dropped_last_connection,
                                     this, vrpn_ANY_SENDER)) {
        fprintf(stderr, "vrpn_Auxiliary_Logger_Server::vrpn_Auxiliary_Logger_"
                        "Server: can't register dropped last connection "
                        "handler\n");
        d_connection = NULL;
    }

    // Register a handler for the request logging message.
    if (register_autodeleted_handler(request_logging_m_id,
                                     static_handle_request_logging, this,
                                     d_sender_id)) {
        fprintf(stderr, "vrpn_Auxiliary_Logger_Server::vrpn_Auxiliary_Logger_"
                        "Server: can't register logging request handler\n");
        d_connection = NULL;
    }

    // Register a handler for the request logging-status message
    if (register_autodeleted_handler(request_logging_status_m_id,
                                     static_handle_request_logging_status, this,
                                     d_sender_id)) {
        fprintf(stderr, "vrpn_Auxiliary_Logger_Server::vrpn_Auxiliary_Logger_"
                        "Server: can't register logging-status request "
                        "handler\n");
        d_connection = NULL;
    }
}

// This handles the last dropped connection message by turning off all
// logging.
void vrpn_Auxiliary_Logger_Server::handle_dropped_last_connection(void)
{
    handle_request_logging("", "", "", "");
}

/* static */
// This method just passes the call on to the virtual function.
int vrpn_Auxiliary_Logger_Server::static_handle_dropped_last_connection(
    void *userdata, vrpn_HANDLERPARAM /*p*/)
{
    vrpn_Auxiliary_Logger_Server *me =
        static_cast<vrpn_Auxiliary_Logger_Server *>(userdata);
    me->handle_dropped_last_connection();
    return 0;
}

/* static */
int vrpn_Auxiliary_Logger_Server::static_handle_request_logging_status(
    void *userdata, vrpn_HANDLERPARAM /*p*/)
{
    vrpn_Auxiliary_Logger_Server *me =
        static_cast<vrpn_Auxiliary_Logger_Server *>(userdata);
    me->handle_request_logging_status();
    return 0;
}

/* static */
// This method just parses the raw data in the Handlerparam to produce strings
// and then
// passes the call on to the virtual function.
int vrpn_Auxiliary_Logger_Server::static_handle_request_logging(
    void *userdata, vrpn_HANDLERPARAM p)
{
    vrpn_Auxiliary_Logger_Server *me =
        static_cast<vrpn_Auxiliary_Logger_Server *>(userdata);
    char *localInName = NULL, *localOutName = NULL, *remoteInName = NULL,
         *remoteOutName = NULL;

    // Attempt to unpack the names from the buffer
    if (!me->unpack_log_message_from_buffer(p.buffer, p.payload_len,
                                            &localInName, &localOutName,
                                            &remoteInName, &remoteOutName)) {
        fprintf(stderr, "vrpn_Auxiliary_Logger_Server::static_handle_request_"
                        "logging: Could not unpack buffer\n");
        return -1;
    }

    // Call the virtual function with the strings, then clean up memory and
    // return.
    me->handle_request_logging(localInName, localOutName, remoteInName,
                               remoteOutName);
    if (localInName) {
        delete[] localInName;
    };
    if (localOutName) {
        delete[] localOutName;
    };
    if (remoteInName) {
        delete[] remoteInName;
    };
    if (remoteOutName) {
        delete[] remoteOutName;
    };
    return 0;
}

vrpn_Auxiliary_Logger_Server_Generic::vrpn_Auxiliary_Logger_Server_Generic(
    const char *logger_name, const char *connection_to_log, vrpn_Connection *c)
    : vrpn_Auxiliary_Logger_Server(logger_name, c)
    , d_connection_name(NULL)
    , d_logging_connection(NULL)
{
    // Copy the name of the connection to log and its NULL terminator.
    if ((connection_to_log == NULL) || (strlen(connection_to_log) == 0)) {
        fprintf(stderr, "vrpn_Auxiliary_Logger_Server_Generic::vrpn_Auxiliary_"
                        "Logger_Server_Generic: Empty logging name passed "
                        "in\n");
        d_connection = NULL;
        return;
    }
    d_connection_name = new char[strlen(connection_to_log) + 1];
    if (d_connection_name == NULL) {
        fprintf(stderr, "vrpn_Auxiliary_Logger_Server_Generic::vrpn_Auxiliary_"
                        "Logger_Server_Generic: Out of memory\n");
        d_connection = NULL;
        return;
    }
    memcpy(d_connection_name, connection_to_log, strlen(connection_to_log) + 1);
}

vrpn_Auxiliary_Logger_Server_Generic::~vrpn_Auxiliary_Logger_Server_Generic()
{
    if (d_logging_connection) {
        delete d_logging_connection;
        d_logging_connection = NULL;
    }

    if (d_connection_name) {
        delete[] d_connection_name;
        d_connection_name = NULL;
    }
}

// Close an existing logging connection, then (if any of the file
// names are non-empty) open a new logging connection to the
// connection we are to log (even if this process already has a
// connection to it) and then send back the report that we've started
// logging if we are able.  If we cannot open it, then fill in all
// blank names for the return report.
void vrpn_Auxiliary_Logger_Server_Generic::handle_request_logging(
    const char *local_in_logfile_name, const char *local_out_logfile_name,
    const char *remote_in_logfile_name, const char *remote_out_logfile_name)
{
    // If we have a logging connection open, reduce its reference
    // count (which may delete it but will leave it going if some
    // other object has a pointer to it).
    if (d_logging_connection) {
        d_logging_connection->removeReference();
        d_logging_connection = NULL;
    }

    // If at least one of the names passed in is not empty, create
    // a new logging connection.  If this fails, report no logging.

    // Find the relevant part of the name (skip past last '@'
    // if there is one); also find the port number.
    const char *cname = d_connection_name;
    const char *where_at; // Part of name past last '@'
    if ((where_at = strrchr(cname, '@')) != NULL) {
        cname = where_at + 1; // Chop off the front of the name
    }

    // Pass "true" to force_connection so that it will open a new
    // connection even if we already have one with that name.
    d_logging_connection = vrpn_get_connection_by_name(
        where_at, local_in_logfile_name, local_out_logfile_name,
        remote_in_logfile_name, remote_out_logfile_name, NULL, true);
    if (!d_logging_connection || !d_logging_connection->doing_okay()) {
        struct timeval now;
        vrpn_gettimeofday(&now, NULL);
        send_text_message("handle_request_logging: Could not create connection "
                          "(files already exist?)",
                          now, vrpn_TEXT_ERROR);
        send_report_logging(NULL, NULL, NULL, NULL);
        if (d_logging_connection) {
            delete d_logging_connection;
            d_logging_connection = NULL;
        }
        return;
    }

    // Report the logging that we're doing.
    send_report_logging(local_in_logfile_name, local_out_logfile_name,
                        remote_in_logfile_name, remote_out_logfile_name);
}

void vrpn_Auxiliary_Logger_Server_Generic::handle_request_logging_status()
{
    char *local_in;
    char *local_out;
    char *remote_in;
    char *remote_out;
    d_logging_connection->get_log_names(&local_in, &local_out, &remote_in,
                                        &remote_out);
    send_report_logging(local_in, local_out, remote_in, remote_out);
    if (local_in) delete[] local_in;
    if (local_out) delete[] local_out;
    if (remote_in) delete[] remote_in;
    if (remote_out) delete[] remote_out;
}

void vrpn_Auxiliary_Logger_Remote::mainloop(void)
{
    if (d_connection) {
        d_connection->mainloop();
        client_mainloop();
    }
}

vrpn_Auxiliary_Logger_Remote::vrpn_Auxiliary_Logger_Remote(const char *name,
                                                           vrpn_Connection *c)
    : vrpn_Auxiliary_Logger(name, c)
{
    // Register a handler for the report callback from this device,
    // if we got a connection.
    if (d_connection != NULL) {
        if (register_autodeleted_handler(report_logging_m_id,
                                         handle_report_message, this,
                                         d_sender_id)) {
            fprintf(stderr,
                    "vrpn_Auxiliary_Logger_Remote: can't register handler\n");
            d_connection = NULL;
        }
    }
    else {
        fprintf(stderr,
                "vrpn_Auxiliary_Logger_Remote: Can't get connection!\n");
    }
}

/* Static */
int VRPN_CALLBACK
vrpn_Auxiliary_Logger_Remote::handle_report_message(void *userdata,
                                                    vrpn_HANDLERPARAM p)
{
    vrpn_Auxiliary_Logger_Remote *me =
        static_cast<vrpn_Auxiliary_Logger_Remote *>(userdata);
    char *localInName = NULL, *localOutName = NULL, *remoteInName = NULL,
         *remoteOutName = NULL;

    // Attempt to unpack the names from the buffer
    if (!me->unpack_log_message_from_buffer(p.buffer, p.payload_len,
                                            &localInName, &localOutName,
                                            &remoteInName, &remoteOutName)) {
        fprintf(stderr, "vrpn_Auxiliary_Logger_Remote::handle_report_message: "
                        "Could not unpack buffer\n");
        return -1;
    }

    // Fill in the data type for the callback handlers.
    vrpn_AUXLOGGERCB cs;
    cs.msg_time = p.msg_time;
    cs.local_in_logfile_name = localInName;
    cs.local_out_logfile_name = localOutName;
    cs.remote_in_logfile_name = remoteInName;
    cs.remote_out_logfile_name = remoteOutName;

    // Go down the list of callbacks that have been registered.
    // Fill in the parameter and call each.
    me->d_callback_list.call_handlers(cs);

    // Clean up memory and return.
    if (localInName) {
        delete[] localInName;
    };
    if (localOutName) {
        delete[] localOutName;
    };
    if (remoteInName) {
        delete[] remoteInName;
    };
    if (remoteOutName) {
        delete[] remoteOutName;
    };
    return 0;
}