#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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

#define LOGMODULE log
#include "log.h"

#include <android-base/logging.h>

#if !defined(_MSC_VER) || defined(__INTEL_COMPILER)
#define likely(x)       __builtin_expect(!!(x), 1)
#define unlikely(x)     __builtin_expect(!!(x), 0)
#else
/* Microsoft Visual Studio gives internal error C1001 with _builtin_expect */
#define likely(x)       (x)
#define unlikely(x)     (x)
#endif

extern "C" {

/**
 * Compares two strings byte by byte and ignores the
 * character's case. Stops at the n-th byte of both
 * strings.
 *
 * This is basically a replacement of the POSIX-function
 * _strncasecmp_. Since tpm2-tss is supposed to be compatible
 * with ISO C99 and not with POSIX, _strncasecmp_ had to be
 * replaced. This function creates lowercase representations
 * of the strings and compares them bytewise.
 *
 * @param string1 The first of the two strings to compare
 * @param string2 The second of the two strings to compare
 * @param n The maximum number of bytes to compare
 * @return 0 if both strings are equal (case insensitive),
 *  an integer greater than zero if string1 is greater than
 *  string 2 and an integer smaller than zero if string1 is
 *  smaller than string2
 *
 */
static int
case_insensitive_strncmp(const char *string1,
        const char *string2,
        size_t n)
{
    if ((string1 == NULL) && (string2 == NULL)) {
        return 0;
    }
    if ((string1 == NULL) && (string2 != NULL)) {
        return -1;
    }
    if ((string1 != NULL) && (string2 == NULL)) {
        return 1;
    }
    if (n == 0) { // Zero bytes are always equal
        return 0;
    }
    if (string1 == string2) { // return equal if they point to same location
        return 0;
    }

    int result;
    do {
        result = tolower((unsigned char) *string1) - tolower((unsigned char) *string2);
        if (result != 0) {
                break;
        }
    } while (*string1++ != '\0' && *string2++ != '\0' && --n );
    return result;
}

static log_level
getLogLevel(const char *module, log_level logdefault);

void
doLogBlob(log_level loglevel, const char *module, log_level logdefault,
           log_level *status,
           const char *file, const char *func, int line,
           const uint8_t *blob, size_t size, const char *fmt, ...)
{
    if (unlikely(*status == LOGLEVEL_UNDEFINED))
        *status = getLogLevel(module, logdefault);
    if (loglevel > *status)
        return;

    va_list vaargs;
    va_start(vaargs, fmt);
    /* TODO: Unfortunately, vsnprintf(NULL, 0, ...) do not behave the same as
       snprintf(NULL, 0, ...). Until there is an alternative, messages on
       logblob are restricted to 255 characters
    int msg_len = vsnprintf(NULL, 0, fmt, vaargs); */
    int msg_len = 255;
    char msg[msg_len+1];
    vsnprintf(msg, sizeof(msg), fmt, vaargs);
    va_end(vaargs);

    doLog(loglevel, module, logdefault, status, file, func, line,
          "%s (size=%zi):", msg, size);

    unsigned int i, y, x, off, off2;
    unsigned int width = 16;
#define LINE_LEN 64
    char buffer[LINE_LEN];

    for (i = 1, off = 0, off2 = 0; i <= size; i++) {
        if (i == 1) {
            sprintf(&buffer[off], "%04x: ", i - 1);
            off += 6;
        }

        /* data output */
        sprintf(&buffer[off], "%02x", blob[i-1]);
        off += 2;

        /* ASCII output */
        if ((i % width == 0 && i > 1) || i == size) {
            sprintf(&buffer[off], "  ");
            off += 2;
            /* Align to the right */
            for (x = off; x < width * 2 + 8; x++) {
                sprintf(&buffer[off], " ");
                off++;
            }

            /* Account for a line that is not 'full' */
            unsigned int less = width - (i % width);
            if (less == width)
                less = 0;

            for (y = 0; y < width - less; y++) {
                if (isgraph(blob[off2 + y])) {
                    sprintf(&buffer[y + off], "%c", blob[off2 + y]);
                } else {
                    sprintf(&buffer[y + off], "%c", '.');
                }
            }
            /* print the line and restart */
            fprintf (stderr, "%s\n", buffer);
            off2 = i;
            off = 0;
            memset(buffer, '\0', LINE_LEN);
            sprintf(&buffer[off], "%04x: ", i);
            off += 6;
        }
    }
}

void
doLog(log_level loglevel, const char *module, log_level logdefault,
           log_level *status,
           const char *file, const char *func, int line,
           const char *msg, ...)
{
    if (unlikely(*status == LOGLEVEL_UNDEFINED))
        *status = getLogLevel(module, logdefault);

    if (loglevel > *status)
        return;

    int size = snprintf(NULL, 0, "%s:%s:%s:%d:%s() %s ",
                log_strings[loglevel], module, file, line, func, msg);
    char fmt[size+1];
    snprintf(fmt, sizeof(fmt), "%s:%s:%s:%d:%s() %s ",
                log_strings[loglevel], module, file, line, func, msg);

    va_list vaargs;
    va_start(vaargs, msg);
    int complete_size = vsnprintf(NULL, 0, fmt, vaargs);
    va_end(vaargs);

    va_start(vaargs, msg);
    char complete[complete_size+1];
    vsnprintf(complete, sizeof(complete), fmt, vaargs);
    va_end(vaargs);

    fprintf(stderr, "%s\n", complete);

    switch (loglevel) {
      case LOGLEVEL_NONE:
        LOG(ERROR) << complete;
        break;
      case LOGLEVEL_ERROR:
        LOG(ERROR) << complete;
        break;
      case LOGLEVEL_WARNING:
        LOG(WARNING) << complete;
        break;
      case LOGLEVEL_INFO:
        LOG(INFO) << complete;
        break;
      case LOGLEVEL_DEBUG:
        LOG(DEBUG) << complete;
        break;
      case LOGLEVEL_TRACE:
        LOG(VERBOSE) << complete;
        break;
      case LOGLEVEL_UNDEFINED:
      default:
        LOG(WARNING) << complete;
        break;
    }
}

static log_level
log_stringlevel(const char *n)
{
    log_level i;
    for(i = (log_level) 0; i < sizeof(log_strings)/sizeof(log_strings[0]); i = (log_level) ((int) i + 1)) {
        if (case_insensitive_strncmp(log_strings[i], n, strlen(log_strings[i])) == 0) {
            return i;
        }
    }
    return LOGLEVEL_UNDEFINED;
}

static log_level
getLogLevel(const char *module, log_level logdefault)
{
    log_level loglevel = logdefault;
    char *envlevel = getenv("TSS2_LOG");
    char *i = envlevel;
    if (envlevel == NULL)
        return loglevel;
    while ((i = strchr(i, '+')) != NULL) {
        if ((envlevel <= i - strlen("all") &&
	     case_insensitive_strncmp(i - 3, "all", 3) == 0) ||
            (envlevel <= i - strlen(module) &&
             case_insensitive_strncmp(i - strlen(module), module, strlen(module)) == 0)) {
            log_level tmp = log_stringlevel(i+1);
            if (tmp != LOGLEVEL_UNDEFINED)
                loglevel = tmp;
        }
        i = i + 1;
    }
    return loglevel;
}

} // extern "C"
