/*************************************************************************\
* Copyright (c) 2010 Brookhaven Science Associates, as Operator of
*     Brookhaven National Laboratory.
* Copyright (c) 2010 UChicago Argonne LLC, as Operator of Argonne
*     National Laboratory.
* SPDX-License-Identifier: EPICS
* EPICS BASE is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
\*************************************************************************/
/*
 *      Author: Michael Davidsaver
 *      Date:   2010-09-24
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "epicsAssert.h"
#include "epicsThread.h"
#include "epicsEvent.h"
#include "dbDefs.h"
#include "errlog.h"
#include "epicsUnitTest.h"
#include "testMain.h"
#include "iocLog.h"
#include "logClient.h"
#include "envDefs.h"
#include "osiSock.h"
#include "fdmgr.h"
#include "epicsString.h"
#include "errSymTbl.h"

/* private between errlog.c and this test */
LIBCOM_API
void errlogStripANSI(char *msg);

#define LOGBUFSIZE 2048

static
const char longmsg[]="A0123456789abcdef"
                     "B0123456789abcdef"
                     "C0123456789abcdef"
                     "D0123456789abcdef"
                     "E0123456789abcdef"
                     "F0123456789abcdef"
                     "G0123456789abcdef"
                     "H0123456789abcdef"
                     "I0123456789abcdef"
                     "J0123456789abcdef"
                     "K0123456789abcdef"
                     "L0123456789abcdef"
                     "M0123456789abcdef"
                     "N0123456789abcdef"
                     "O0123456789abcdef"
                     "P0123456789abcdef"
                     "Q0123456789abcdef"
                     "R0123456789abcdef"
                     "S0123456789abcdef"
                     ;
STATIC_ASSERT(NELEMENTS(longmsg)==324);

static
const char truncmsg[]="A0123456789abcdef"
                      "B0123456789abcdef"
                      "C0123456789abcdef"
                      "D0123456789abcdef"
                      "E0123456789abcdef"
                      "F0123456789abcdef"
                      "G0123456789abcdef"
                      "H0123456789abcdef"
                      "I0123456789abcdef"
                      "J0123456789abcdef"
                      "K0123456789abcdef"
                      "L0123456789abcdef"
                      "M0123456789abcdef"
                      "N0123456789abcdef"
                      "O01<<TRUNCATED>>\n"
                      ;
STATIC_ASSERT(NELEMENTS(truncmsg)==256);

typedef struct {
    unsigned int count;
    const char* expect;
    size_t checkLen;
    epicsEventId jammer;
    int jam;
    epicsEventId done;
} clientPvt;

static void testLogPrefix(void);
static void acceptNewClient( void *pParam );
static void readFromClient( void *pParam );
static void testPrefixLogandCompare( const char* logmessage);

static void *pfdctx;
static SOCKET sock;
static SOCKET insock;

static const char* prefixactualmsg[]= {
                     "A message without prefix",
                     "A message with prefix",
                     "DONE"
                     };
static const char *prefixstring = "fac=LI21 ";
static const char prefixexpectedmsg[] = "A message without prefix"
                     "fac=LI21 A message with prefix"
                     "fac=LI21 DONE"
                     ;
static char prefixmsgbuffer[1024];


static void
testEqInt_(int line, int lhs, int rhs, const char *LHS, const char *RHS)
{
    testOk(lhs==rhs, "%d: %s (%d) == %s (%d)", line, LHS, lhs, RHS, rhs);
}
#define testEqInt(L, R) testEqInt_(__LINE__, L, R, #L, #R);

static
void logClient(void* raw, const char* msg)
{
    clientPvt *pvt = raw;
    size_t len;
    char show[46];

    /* Simulate thread priority on non-realtime
     * OSs like Linux.  This will cause the logging
     * thread to sleep with the buffer lock held.
     */
    if (pvt->jam > 0) {
        pvt->jam = 0;
        epicsEventMustWait(pvt->jammer);
    } else if (pvt->jam < 0) {
        pvt->jam++;
        if (pvt->jam == 0)
            epicsEventMustWait(pvt->jammer);
    }

    len = strlen(msg);
    if (len > 45) {
        /* Only show start and end of long messages */
        strncpy(show, msg, 20);
        show[20] = 0;
        strcat(show + 20, " ... ");
        strcat(show + 25, msg + len - 20);
    }
    else {
        strcpy(show, msg);
    }

    if (pvt->checkLen)
        if (!testOk(pvt->checkLen == len, "Received %d chars", (int) len)) {
            testDiag("Expected %d", (int) pvt->checkLen);
            if (!pvt->expect)
                testDiag("Message is \"%s\"", show);
        }

    if (pvt->expect) {
        if (!testOk(strcmp(pvt->expect, msg) == 0, "Message is \"%s\"", show)) {
            len = strlen(pvt->expect);
            if (len > 45) {
                testDiag("Expected \"%.20s ... %s\"",
                    pvt->expect, pvt->expect + len - 20);
            }
            else {
                testDiag("Expected \"%s\"", pvt->expect);
            }
        }
    }

    pvt->count++;
    epicsEventSignal(pvt->done);
}

static
void testANSIStrip(void)
{
    char scratch[64];
    char actual[128];
    char input[128];
#define testEscape(INP, EXPECT) \
    strcpy(scratch, INP); \
    epicsStrnEscapedFromRaw(input, sizeof(input), INP, sizeof(INP)); \
    errlogStripANSI(scratch); \
    epicsStrnEscapedFromRaw(actual, sizeof(actual), scratch, epicsStrnLen(scratch, sizeof(scratch))); \
    testOk(strcmp(scratch, EXPECT)==0, "input \"%s\" expect \"%s\" actual \"%s\"", input, EXPECT, actual)

    testEscape("", "");
    testEscape("hello", "hello");
    testEscape("he\033[31;1mllo", "hello");
    testEscape("\033[31;1mhello", "hello");
    testEscape("hello\033[31;1m", "hello");
    testEscape("hello\033[31;1", "hello");
    testEscape("hello\033[", "hello");
    testEscape("hello\033", "hello");

#undef testEscape
}

static void testErrorMessageMatches(long status, const char *expected)
{
    const char *msg = errSymMsg(status);
    testOk(strcmp(msg, expected) == 0,
           "Error code %ld returns \"%s\", expected message \"%s\"", status,
           msg, expected
          );
}

static void testGettingExistingErrorSymbol()
{
    testErrorMessageMatches(S_err_invCode, "Invalid error symbol code");
}

static void testGettingNonExistingErrorSymbol()
{
    long invented_code = (0x7999 << 16) | 0x9998;
    testErrorMessageMatches(invented_code, "<Unknown code>");
}

static void testAddingErrorSymbol()
{
    long invented_code = (0x7999 << 16) | 0x9999;
    errSymbolAdd(invented_code, "Invented Error Message");
    testErrorMessageMatches(invented_code, "Invented Error Message");
}

static void testAddingInvalidErrorSymbol()
{
    long invented_code = (500 << 16) | 0x1;
    testOk(errSymbolAdd(invented_code, "No matter"),
           "Adding error symbol with module code < 501 should fail");
}

static void testAddingExistingErrorSymbol()
{
    testOk(errSymbolAdd(S_err_invCode, "Duplicate"),
           "Adding an error symbol with an existing error code should fail");
}

static void testAddingExistingErrorSymbolWithSameMessage()
{
    long invented_code = (0x7999 << 16) | 0x9997;
    errSymbolAdd(invented_code, "Invented Error Message");
    testOk(!errSymbolAdd(invented_code, "Invented Error Message"),
           "Adding identical error symbol shouldn't fail");
}

MAIN(epicsErrlogTest)
{
    size_t mlen, i, N;
    char msg[256];
    clientPvt pvt, pvt2;

    testPlan(54);

    testANSIStrip();

    strcpy(msg, truncmsg);

    errlogInit2(LOGBUFSIZE, 256);

    pvt.count = 0;
    pvt2.count = 0;

    pvt.expect = NULL;
    pvt2.expect = NULL;

    pvt.checkLen = 0;
    pvt2.checkLen = 0;

    pvt.jam = 0;
    pvt2.jam = 0;

    pvt.jammer = epicsEventMustCreate(epicsEventEmpty);
    pvt.done = epicsEventMustCreate(epicsEventEmpty);
    pvt2.jammer = epicsEventMustCreate(epicsEventEmpty);
    pvt2.done = epicsEventMustCreate(epicsEventEmpty);

    testDiag("Check listener registration");

    errlogAddListener(&logClient, &pvt);

    pvt.expect = "Testing";
    pvt.checkLen = strlen(pvt.expect);

    errlogPrintfNoConsole("%s", pvt.expect);
    errlogFlush();

    epicsEventMustWait(pvt.done);
    testEqInt(pvt.count, 1);

    errlogAddListener(&logClient, &pvt2);

    /* logClient will not see ANSI escape sequences */
    pvt2.expect = pvt.expect = "Testing2";
    pvt2.checkLen = pvt.checkLen = strlen(pvt.expect);

    errlogPrintfNoConsole("%s", ANSI_RED("Testing2"));
    errlogFlush();

    epicsEventMustWait(pvt.done);
    testEqInt(pvt.count, 2);

    epicsEventMustWait(pvt2.done);
    testEqInt(pvt2.count, 1);

    /* Removes the first listener */
    testOk(1 == errlogRemoveListeners(&logClient, &pvt),
        "Removed 1 listener");

    pvt2.expect = "Testing3";
    pvt2.checkLen = strlen(pvt2.expect);

    errlogPrintfNoConsole("%s", pvt2.expect);
    errlogFlush();

    testOk(epicsEventWaitWithTimeout(pvt.done, 0.5) == epicsEventWaitTimeout,
        "%d: Listener 1 didn't run", __LINE__);
    testOk(epicsEventTryWait(pvt2.done) == epicsEventOK,
        "%d: Listener 2 ran", __LINE__);
    testEqInt(pvt.count, 2);
    testEqInt(pvt2.count, 2);

    /* Add the second listener again, then remove both instances */
    errlogAddListener(&logClient, &pvt2);
    testOk(2 == errlogRemoveListeners(&logClient, &pvt2),
        "Removed 2 listeners");

    errlogPrintfNoConsole("Something different");
    errlogFlush();

    testOk(epicsEventWaitWithTimeout(pvt.done, 0.5) == epicsEventWaitTimeout,
        "%d: Listener 1 didn't run", __LINE__);
    testOk(epicsEventTryWait(pvt2.done) == epicsEventWaitTimeout,
        "%d: Listener 2 didn't run", __LINE__);
    testEqInt(pvt.count, 2);
    testEqInt(pvt2.count, 2);

    /* Re-add one listener */
    errlogAddListener(&logClient, &pvt);

    testDiag("Check truncation");

    pvt.expect = truncmsg;
    pvt.checkLen = 255;

    errlogPrintfNoConsole("%s", longmsg);
    errlogFlush();

    epicsEventMustWait(pvt.done);
    testEqInt(pvt.count, 3);

    pvt.expect = NULL;

    testDiag("Check priority");
    /* For the following tests it is important that
     * the buffer should not flush until we request it
     */
    pvt.jam = 1;

    errlogPrintfNoConsole("%s", longmsg);

    testOk(epicsEventWaitWithTimeout(pvt.done, 0.5) == epicsEventWaitTimeout,
        "%d: Listener 1 didn't run", __LINE__);
    testEqInt(pvt.count, 3);

    epicsEventSignal(pvt.jammer);
    errlogFlush();

    epicsEventMustWait(pvt.done);
    testEqInt(pvt.count, 4);

    testDiag("Find buffer capacity (%u theoretical)",LOGBUFSIZE);

    pvt.checkLen = 0;

    for (mlen = 8; mlen <= 255; mlen *= 2) {
        double eff;
        char save = msg[mlen - 1];

        N = LOGBUFSIZE / mlen; /* # of of messages to send */
        msg[mlen - 1] = '\0';
        pvt.count = 0;
        /* pvt.checkLen = mlen - 1; */

        pvt.jam = 1;

        for (i = 0; i < N; i++) {
            errlogPrintfNoConsole("%s", msg);
        }

        epicsEventSignal(pvt.jammer);
        errlogFlush();

        eff = (double) (pvt.count * mlen) / LOGBUFSIZE * 100.0;
        testDiag(" For %d messages of length %d got %u (%.1f%% efficient)",
                 (int) N, (int) mlen, pvt.count, eff);

        msg[mlen - 1] = save;
        N = pvt.count;  /* Save final count for the test below */

        /* Clear "errlog: <n> messages were discarded" status */
        pvt.checkLen = 0;
        errlogPrintfNoConsole(".");
        errlogFlush();
    }

    testOk(epicsEventTryWait(pvt.done) == epicsEventOK,
        "%d: Listener 1 ran", __LINE__);

    testDiag("Checking buffer use after partial flush");

    /* Use the numbers from the largest block size above */
    mlen /= 2;
    msg[mlen - 1] = '\0';

    pvt.jam = 1;
    pvt.count = 0;

    testDiag("Filling with %d messages of size %d", (int) N, (int) mlen);

    for (i = 0; i < N; i++) {
        errlogPrintfNoConsole("%s", msg);
    }

    testOk(epicsEventWaitWithTimeout(pvt.done, 0.5) == epicsEventWaitTimeout,
        "%d: Listener 1 didn't run", __LINE__);
    testEqInt(pvt.count, 0);

    /* Extract the first 2 messages, 2*(sizeof(msgNode) + 128) bytes */
    pvt.jam = -2;
    epicsEventSignal(pvt.jammer);
    epicsThreadSleep(0.5);

    testDiag("Drained %u messages", pvt.count);
    epicsEventMustWait(pvt.done);
    testEqInt(pvt.count, 2);

    /* The buffer has space for 1 more message: sizeof(msgNode) + 256 bytes */
    errlogPrintfNoConsole("%s", msg); /* Use up that space */

    testDiag("Overflow the buffer");
    errlogPrintfNoConsole("%s", msg);

    testOk(epicsEventWaitWithTimeout(pvt.done, 0.5) == epicsEventWaitTimeout,
        "%d: Listener 1 didn't run", __LINE__);
    testEqInt(pvt.count, 2);

    epicsEventSignal(pvt.jammer); /* Empty */
    errlogFlush();

    testDiag("Logged %u messages", pvt.count);
    epicsEventMustWait(pvt.done);
    /* Expect N+1 messages +- 1 depending on impl */
    testOk(pvt.count >= N && pvt.count<=N+2, "Logged %u messages, expected %zu", pvt.count, N+1);

    /* Clean up */
    testOk(1 == errlogRemoveListeners(&logClient, &pvt),
        "Removed 1 listener");

    osiSockAttach();
    testLogPrefix();
    osiSockRelease();

    testGettingExistingErrorSymbol();
    testGettingNonExistingErrorSymbol();
    testAddingErrorSymbol();
    testAddingInvalidErrorSymbol();
    testAddingExistingErrorSymbol();
    testAddingExistingErrorSymbolWithSameMessage();

    return testDone();
}
/*
 * Tests the log prefix code
 * The prefix is only applied to log messages as they go out to the socket,
 * so we need to create a server listening on a port, accept connections etc.
 * This code is a reduced version of the code in iocLogServer.
 */
static void testLogPrefix(void) {
    struct sockaddr_in serverAddr;
    int status;
    struct timeval timeout;
    struct sockaddr_in actualServerAddr;
    osiSocklen_t actualServerAddrSize;
    char portstring[16];


    testDiag("Testing iocLogPrefix");

    timeout.tv_sec = 5; /* in seconds */
    timeout.tv_usec = 0;

    memset(prefixmsgbuffer, 0, sizeof prefixmsgbuffer);

    /* Clear "errlog: <n> messages were discarded" status */
    errlogPrintfNoConsole(".");
    errlogFlush();

    sock = epicsSocketCreate(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) {
        testAbort("epicsSocketCreate failed.");
    }

    /* We listen on a an available port. */
    memset(&serverAddr, 0, sizeof serverAddr);
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(0);

    status = bind (sock, (struct sockaddr *)&serverAddr,
                   sizeof (serverAddr) );
    if (status < 0) {
        testAbort("bind failed; all ports in use?");
    }

    status = listen(sock, 10);
    if (status < 0) {
        testAbort("listen failed!");
    }

    /* Determine the port that the OS chose */
    actualServerAddrSize = sizeof actualServerAddr;
    memset(&actualServerAddr, 0, sizeof serverAddr);
    status = getsockname(sock, (struct sockaddr *) &actualServerAddr,
         &actualServerAddrSize);
    if (status < 0) {
        testAbort("Can't find port number!");
    }

    sprintf(portstring, "%d", ntohs(actualServerAddr.sin_port));
    testDiag("Listening on port %s", portstring);

    /* Set the EPICS environment variables for logging. */
    epicsEnvSet ( "EPICS_IOC_LOG_INET", "localhost" );
    epicsEnvSet ( "EPICS_IOC_LOG_PORT", portstring );

    pfdctx = fdmgr_init();
    if (status < 0) {
        testAbort("fdmgr_init failed!");
    }

    status = fdmgr_add_callback(pfdctx, sock, fdi_read,
        acceptNewClient, &serverAddr);

    if (status < 0) {
        testAbort("fdmgr_add_callback failed!");
    }

    testOk1(iocLogInit() == 0);
    fdmgr_pend_event(pfdctx, &timeout);

    testPrefixLogandCompare(prefixactualmsg[0]);

    iocLogPrefix(prefixstring);
    testPrefixLogandCompare(prefixactualmsg[1]);
    testPrefixLogandCompare(prefixactualmsg[2]);
    epicsSocketDestroy(sock);
}

static void testPrefixLogandCompare( const char* logmessage ) {
    struct timeval timeout;
    timeout.tv_sec = 5; /* in seconds */
    timeout.tv_usec = 0;

    errlogPrintfNoConsole("%s", logmessage);
    errlogFlush();
    iocLogFlush();
    fdmgr_pend_event(pfdctx, &timeout);
}

static void acceptNewClient ( void *pParam )
{
    osiSocklen_t addrSize;
    struct sockaddr_in addr;
    int status;

    addrSize = sizeof ( addr );
    insock = epicsSocketAccept ( sock, (struct sockaddr *)&addr, &addrSize );
    testOk(insock != INVALID_SOCKET && addrSize >= sizeof (addr),
        "Accepted new client");

    status = fdmgr_add_callback(pfdctx, insock, fdi_read,
        readFromClient,  NULL);

    testOk(status >= 0, "Client read configured");
}

static void readFromClient(void *pParam)
{
    char recvbuf[1024];
    int recvLength;

    memset(recvbuf, 0, 1024);
    recvLength = recv(insock, recvbuf, 1024, 0);
    if (recvLength > 0) {
        strcat(prefixmsgbuffer, recvbuf);

        /* If we have received all of the messages. */
        if (strstr(prefixmsgbuffer, "DONE") != NULL) {
            size_t msglen = strlen(prefixexpectedmsg);
            int prefixcmp = strncmp(prefixexpectedmsg, prefixmsgbuffer, msglen);

            if (!testOk(prefixcmp == 0, "prefix matches")) {
                testDiag("Expected '%s'\n", prefixexpectedmsg);
                testDiag("Obtained '%s'\n", prefixmsgbuffer);
            }
        }
    }
}
