/*
 * Dynomite - A thin, distributed replication layer for multi non-distributed
 * storages. Copyright (C) 2014 Netflix, Inc.
 */

/*
 * twemproxy - A fast and lightweight proxy for memcached protocol.
 * Copyright (C) 2011 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <ctype.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>

#include "dyn_core.h"

static struct logger logger;

/**
 * Initialize logging including log level and output target. Logging output may
 * be sent to standard error or to a log file.
 * @param[in] level Log level.
 * @param[in] name Full path to the log file.
 * @return
 */
int log_init(int level, char *name) {
  struct logger *l = &logger;

  l->level = MAX(LOG_EMERG, MIN(level, LOG_PVERB));
  l->name = name;
  if (name == NULL || !strlen(name)) {
    l->fd = STDERR_FILENO;
  } else {
    l->fd = open(name, O_WRONLY | O_APPEND | O_CREAT, 0666);
    if (l->fd < 0) {
      log_stderr("opening log file '%s' failed: %s", name, strerror(errno));
      return -1;
    }
  }

  return 0;
}

/**
 * Close the logging file descriptor.
 */
void log_deinit(void) {
  struct logger *l = &logger;

  if (l->fd < 0 || l->fd == STDERR_FILENO) {
    return;
  }

  close(l->fd);
}

void log_reopen(void) {
  struct logger *l = &logger;

  if (l->fd != STDERR_FILENO) {
    close(l->fd);
    l->fd = open(l->name, O_WRONLY | O_APPEND | O_CREAT, 0644);
    if (l->fd < 0) {
      log_stderr("reopening log file '%s' failed, ignored: %s", l->name,
                 strerror(errno));
    }
  }
}

void log_level_up(void) {
  struct logger *l = &logger;

  if (l->level < LOG_PVERB) {
    l->level++;
    loga("up log level to %d", l->level);
  }
}

void log_level_down(void) {
  struct logger *l = &logger;

  if (l->level > LOG_EMERG) {
    l->level--;
    loga("down log level to %d", l->level);
  }
}

void log_level_set(int level) {
  struct logger *l = &logger;

  l->level = MAX(LOG_EMERG, MIN(level, LOG_PVERB));
  loga("set log level to %d", l->level);
}

int log_loggable(int level) {
  struct logger *l = &logger;

  if (level > l->level) {
    return 0;
  }

  return 1;
}

void _log(const char *file, int line, int panic, const char *fmt, ...) {
  struct logger *l = &logger;
  int len, size, errno_save;
  char buf[LOG_MAX_LEN];
  va_list args;
  ssize_t n;

  if (l->fd < 0) {
    return;
  }

  errno_save = errno;
  len = 0;            /* length of output buffer */
  size = LOG_MAX_LEN; /* size of output buffer */

  struct timeval curTime;
  gettimeofday(&curTime, NULL);

  char buffer[80];
  strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", localtime(&curTime.tv_sec));

  // May be not the perfect place to fix this
  len +=
      dn_scnprintf(buf + len, size - len, "[%.*s.%03d] %s:%d ", strlen(buffer),
                   buffer, (int64_t)curTime.tv_usec / 1000, file, line);

  va_start(args, fmt);

  len += dn_vscnprintf(buf + len, size - len, fmt, args);

  va_end(args);

  buf[len++] = '\n';

  n = dn_write(l->fd, buf, len);
  if (n < 0) {
    l->nerror++;
  }

  errno = errno_save;

  if (panic) {
    fsync(l->fd);
    close(l->fd);
    abort();
  }
}

void _log_stderr(const char *fmt, ...) {
  struct logger *l = &logger;
  int len, size, errno_save;
  char buf[4 * LOG_MAX_LEN];
  va_list args;
  ssize_t n;

  errno_save = errno;
  len = 0;                /* length of output buffer */
  size = 4 * LOG_MAX_LEN; /* size of output buffer */

  va_start(args, fmt);
  len += dn_vscnprintf(buf, size, fmt, args);
  va_end(args);

  buf[len++] = '\n';

  n = dn_write(STDERR_FILENO, buf, len);
  if (n < 0) {
    l->nerror++;
  }

  errno = errno_save;
}

/*
 * Hexadecimal dump in the canonical hex + ascii display
 * See -C option in man hexdump
 */
void _log_hexdump(const char *file, int line, char *data, int datalen,
                  const char *fmt, ...) {
  struct logger *l = &logger;
  char buf[8 * LOG_MAX_LEN];
  int i, off, len, size, errno_save;
  ssize_t n;

  if (l->fd < 0) {
    return;
  }

  /* log hexdump */
  errno_save = errno;
  off = 0;                /* data offset */
  len = 0;                /* length of output buffer */
  size = 8 * LOG_MAX_LEN; /* size of output buffer */

  while (datalen != 0 && (len < size - 1)) {
    char *save, *str;
    unsigned char c;
    int savelen;

    len += dn_scnprintf(buf + len, size - len, "%08x  ", off);

    save = data;
    savelen = datalen;

    for (i = 0; datalen != 0 && i < 16; data++, datalen--, i++) {
      c = (unsigned char)(*data);
      str = (i == 7) ? "  " : " ";
      len += dn_scnprintf(buf + len, size - len, "%02x%s", c, str);
    }
    for (; i < 16; i++) {
      str = (i == 7) ? "  " : " ";
      len += dn_scnprintf(buf + len, size - len, "  %s", str);
    }

    data = save;
    datalen = savelen;

    len += dn_scnprintf(buf + len, size - len, "  |");

    for (i = 0; datalen != 0 && i < 16; data++, datalen--, i++) {
      c = (unsigned char)(isprint(*data) ? *data : '.');
      len += dn_scnprintf(buf + len, size - len, "%c", c);
    }
    len += dn_scnprintf(buf + len, size - len, "|\n");

    off += 16;
  }

  n = dn_write(l->fd, buf, len);
  if (n < 0) {
    l->nerror++;
  }

  errno = errno_save;
}
