// This file is part of BOINC.
// http://boinc.berkeley.edu
// Copyright (C) 2008 University of California
//
// BOINC is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation,
// either version 3 of the License, or (at your option) any later version.
//
// BOINC 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with BOINC.  If not, see <http://www.gnu.org/licenses/>.

// boinc_log: show stream of event-log messages from a client
//
// usage: boinc_log [--host hostname] [--passwd passwd] 

#if defined(_WIN32) && !defined(__CYGWIN32__)
#define snprintf    _snprintf
#define strdate     _strdate
#define strtime     _strtime
#define chdir       _chdir
#endif

#ifdef _WIN32
#include "boinc_win.h"
#include "win_util.h"
#else
#include "config.h"
#include <cstdio>
#include <cstring>
#include <unistd.h>
#endif

#include <vector>
#include <string>
using std::vector;
using std::string;

#include "gui_rpc_client.h"
#include "error_numbers.h"
#include "util.h"
#include "str_util.h"
#include "str_replace.h"
#include "url.h"
#include "version.h"
#include "common_defs.h"


#define ARGX2(s1,s2) (!strcmp(argv[i], s1)||!strcmp(argv[i], s2))
#define ARG(S) ARGX2("-"#S, "--"#S)


// Global variables
char  g_log_filename[256];
int   g_message_sequence;

void version(){
    printf("boinclog,  built from %s \n", PACKAGE_STRING );
    exit(0);
}

void usage() {
    fprintf(stderr, "\n\
usage: boinclog [--host hostname] [--passwd passwd] [commands]\n\n\
Commands:\n\
 --version, -V                      show version of the logging tool\n\
 --datadir <directory>              where the data directory is located\n\
"
);
    exit(1);
}

void show_error(int retval) {
    fprintf(stderr, "Error %d: %s\n", retval, boincerror(retval));
}

const char* prio_name(int prio) {
    switch (prio) {
    case MSG_INFO: return "low";
    case MSG_USER_ALERT: return "user notification";
    case MSG_INTERNAL_ERROR: return "internal error";
    }
    return "unknown";
}


void update_display() {
    system("cls");
    printf("BOINC Log Conversion Client %s\n", PACKAGE_VERSION);
    printf("Log file: %s\n", g_log_filename);
    printf("%d message(s) processed.\n\n", g_message_sequence);
    printf("Press CTRL-C to exit application.\n");
}


int main(int argc, char** argv) {
    unsigned int i;
    int retval, port=0;
    RPC_CLIENT rpc;
    MESSAGES msgs;
    char buf[256];
    char datadir[256];
    char hostname_buf[256], passwd_buf[256];
    char *hostname = 0, *passwd = passwd_buf, *p;
    struct tm* ptm;
    time_t timestamp;
    FILE* f = NULL;
    std::string msg_datetime;
    std::string msg_project;
    std::string msg_priority;
    std::string msg_type;
    std::string msg_body;
    std::string msg_tmp;

    strlcpy(buf, "", sizeof(buf));
    strlcpy(datadir, "", sizeof(datadir));
    strlcpy(hostname_buf, "", sizeof(hostname_buf));
    strlcpy(passwd_buf, "", sizeof(passwd_buf));
    strlcpy(g_log_filename, "", sizeof(g_log_filename));
    g_message_sequence = 0;

#if defined(_WIN32) && defined(USE_WINSOCK)
    WSADATA wsdata;
    retval = WSAStartup( MAKEWORD( 1, 1 ), &wsdata);
    if (retval) {
        fprintf(stderr, "WinsockInitialize: %d\n", retval);
        exit(1);
    }
#endif

    for (i=1; i<(unsigned int)argc; i++) {
        if (0) {
        } else if (ARG(h)) {
            usage();
        } else if (ARG(help)) {
            usage();
        } else if (ARG(V)) {
            version();
        } else if (ARG(version)) {
            version();
        } else if (ARG(host)) {
            if ((i+1) == (unsigned int)argc) usage();
            hostname = hostname_buf;
            safe_strcpy(hostname_buf, argv[++i]);
            p = strchr(hostname, ':');
            if (p) {
                port = atoi(p+1);
                *p=0;
            }
        } else if (ARG(passwd)) {
            if ((i+1) == (unsigned int)argc) usage();
            safe_strcpy(passwd_buf, argv[++i]);
        } else if (ARG(datadir)) {
            if ((i+1) == (unsigned int)argc) usage();
            safe_strcpy(datadir, argv[++i]);
        } else {
            printf("Unknown option: %s\n", argv[i]);
            usage();
        }
    }

    if (strlen(datadir)) {
        chdir(datadir);
    } else {
#ifdef _WIN32
        chdir_to_data_dir();
#endif
    }

    std::string msg;
    read_gui_rpc_password(passwd_buf, msg);

    retval = rpc.init(hostname, port);
    if (retval) {
        fprintf(stderr, "can't connect to %s\n", hostname?hostname:"local host");
        show_error(retval);
        exit(1);
    }

    if (passwd) {
        retval = rpc.authorize(passwd);
        if (retval) {
            fprintf(stderr, "authorization failure: %d\n", retval);
            show_error(retval);
            exit(1);
        }
    }


    // Construct a unique filename for the output.
    time(&timestamp);
    ptm = localtime(&timestamp);
    strftime(g_log_filename, sizeof(g_log_filename), "%Y%m%d%H%M.log", ptm);

    // Open the new log file for output
    f = fopen(g_log_filename, "w");
    setbuf(f, NULL);

    while(true) {
        update_display();

        msgs.clear();

        rpc.get_messages(g_message_sequence, msgs);

        for (i=0; i<msgs.messages.size(); i++) {
            MESSAGE* pMsg = msgs.messages[i];

            msg_datetime.clear();
            msg_project.clear();
            msg_priority.clear();
            msg_type.clear();
            msg_body.clear();

            msg_datetime = time_to_string(double(pMsg->timestamp));
            msg_project = pMsg->project;
            msg_priority = prio_name(pMsg->priority);
            msg_body = pMsg->body;
            if (pMsg->body[0] == '[') {
                msg_type = pMsg->body.substr(1, pMsg->body.find(']') - 1);
            }

            // If a message type is found in the message body, remove it from
            // the message body
            if (!msg_type.empty()) {
                msg_tmp = std::string("[") + msg_type + std::string("] ");
                msg_body.replace(0, msg_tmp.size(), "");
            }

            // If the last character if the message body is a newline character,
            // remove it before continueing on.  Standard BOINC messages contain
            // a newline character at the end.
            if (msg_body[msg_body.size() - 1] == '\n') {
                msg_body[msg_body.size() - 1] = ' ';
            }

            // If line feeds are detected in the message body, replace them with
            // the pipe symbol.
            for (unsigned int j = 0; j < msg_body.size(); j++) {
                if (msg_body[j] == '\n') {
                    msg_body[j] = '^';
                }
            }

            // Dump to tab delimited file
            fprintf(f,
                "%s\t%s\t%s\t%s\t%s\n",
                msg_datetime.c_str(),
                msg_priority.c_str(),
                msg_project.c_str(),
                msg_type.c_str(),
                msg_body.c_str()
            );

            g_message_sequence = pMsg->seqno;
        }

        boinc_sleep(1.0);
    }

#if defined(_WIN32) && defined(USE_WINSOCK)
    WSACleanup();
#endif
    exit(retval);
}
