// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "logging.h"
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>

namespace kj {

Log::Severity Log::minSeverity = Log::Severity::INFO;

ArrayPtr<const char> KJ_STRINGIFY(Log::Severity severity) {
  static const char* SEVERITY_STRINGS[] = {
    "info",
    "warning",
    "error",
    "fatal"
  };

  const char* s = SEVERITY_STRINGS[static_cast<uint>(severity)];
  return arrayPtr(s, strlen(s));
}

namespace {

enum DescriptionStyle {
  LOG,
  ASSERTION,
  SYSCALL
};

static String makeDescription(DescriptionStyle style, const char* code, int errorNumber,
                              const char* macroArgs, ArrayPtr<String> argValues) {
  KJ_STACK_ARRAY(ArrayPtr<const char>, argNames, argValues.size(), 8, 64);

  if (argValues.size() > 0) {
    size_t index = 0;
    const char* start = macroArgs;
    while (isspace(*start)) ++start;
    const char* pos = start;
    uint depth = 0;
    bool quoted = false;
    while (char c = *pos++) {
      if (quoted) {
        if (c == '\\' && *pos != '\0') {
          ++pos;
        } else if (c == '\"') {
          quoted = false;
        }
      } else {
        if (c == '(') {
          ++depth;
        } else if (c == ')') {
          --depth;
        } else if (c == '\"') {
          quoted = true;
        } else if (c == ',' && depth == 0) {
          if (index < argValues.size()) {
            argNames[index] = arrayPtr(start, pos - 1);
          }
          ++index;
          while (isspace(*pos)) ++pos;
          start = pos;
        }
      }
    }
    if (index < argValues.size()) {
      argNames[index] = arrayPtr(start, pos - 1);
    }
    ++index;

    if (index != argValues.size()) {
      getExceptionCallback().logMessage(
          str(__FILE__, ":", __LINE__, ": Failed to parse logging macro args into ",
              argValues.size(), " names: ", macroArgs, '\n'));
    }
  }

  if (style == SYSCALL) {
    // Strip off leading "foo = " from code, since callers will sometimes write things like:
    //   ssize_t n;
    //   RECOVERABLE_SYSCALL(n = read(fd, buffer, sizeof(buffer))) { return ""; }
    //   return std::string(buffer, n);
    const char* equalsPos = strchr(code, '=');
    if (equalsPos != nullptr && equalsPos[1] != '=') {
      code = equalsPos + 1;
      while (isspace(*code)) ++code;
    }
  }

  {
    StringPtr expected = "expected ";
    StringPtr codeArray = style == LOG ? nullptr : StringPtr(code);
    StringPtr sep = " = ";
    StringPtr delim = "; ";
    StringPtr colon = ": ";

    if (style == ASSERTION && strcmp(code, "false") == 0) {
      // Don't print "expected false", that's silly.
      style = LOG;
    }

    StringPtr sysErrorArray;
#if __USE_GNU
    char buffer[256];
    if (style == SYSCALL) {
      sysErrorArray = strerror_r(errorNumber, buffer, sizeof(buffer));
    }
#else
    // TODO(port):  Other unixes should have strerror_r but it may have a different signature.
    //   Port for thread-safety.
    sysErrorArray = arrayPtr(strerror(errorNumber));
#endif

    size_t totalSize = 0;
    switch (style) {
      case LOG:
        break;
      case ASSERTION:
        totalSize += expected.size() + codeArray.size();
        break;
      case SYSCALL:
        totalSize += codeArray.size() + colon.size() + sysErrorArray.size();
        break;
    }

    for (size_t i = 0; i < argValues.size(); i++) {
      if (i > 0 || style != LOG) {
        totalSize += delim.size();
      }
      if (argNames[i].size() > 0 && argNames[i][0] != '\"') {
        totalSize += argNames[i].size() + sep.size();
      }
      totalSize += argValues[i].size();
    }

    String result = heapString(totalSize);
    char* pos = result.begin();

    switch (style) {
      case LOG:
        break;
      case ASSERTION:
        pos = internal::fill(pos, expected, codeArray);
        break;
      case SYSCALL:
        pos = internal::fill(pos, codeArray, colon, sysErrorArray);
        break;
    }
    for (size_t i = 0; i < argValues.size(); i++) {
      if (i > 0 || style != LOG) {
        pos = internal::fill(pos, delim);
      }
      if (argNames[i].size() > 0 && argNames[i][0] != '\"') {
        pos = internal::fill(pos, argNames[i], sep);
      }
      pos = internal::fill(pos, argValues[i]);
    }

    return result;
  }
}

}  // namespace

void Log::logInternal(const char* file, int line, Severity severity, const char* macroArgs,
                      ArrayPtr<String> argValues) {
  getExceptionCallback().logMessage(
      str(severity, ": ", file, ":", line, ": ",
          makeDescription(LOG, nullptr, 0, macroArgs, argValues), '\n'));
}

void Log::recoverableFaultInternal(
    const char* file, int line, Exception::Nature nature,
    const char* condition, const char* macroArgs, ArrayPtr<String> argValues) {
  getExceptionCallback().onRecoverableException(
      Exception(nature, Exception::Durability::PERMANENT, file, line,
                makeDescription(ASSERTION, condition, 0, macroArgs, argValues)));
}

void Log::fatalFaultInternal(
    const char* file, int line, Exception::Nature nature,
    const char* condition, const char* macroArgs, ArrayPtr<String> argValues) {
  getExceptionCallback().onFatalException(
      Exception(nature, Exception::Durability::PERMANENT, file, line,
                makeDescription(ASSERTION, condition, 0, macroArgs, argValues)));
  abort();
}

void Log::recoverableFailedSyscallInternal(
    const char* file, int line, const char* call,
    int errorNumber, const char* macroArgs, ArrayPtr<String> argValues) {
  getExceptionCallback().onRecoverableException(
      Exception(Exception::Nature::OS_ERROR, Exception::Durability::PERMANENT, file, line,
                makeDescription(SYSCALL, call, errorNumber, macroArgs, argValues)));
}

void Log::fatalFailedSyscallInternal(
    const char* file, int line, const char* call,
    int errorNumber, const char* macroArgs, ArrayPtr<String> argValues) {
  getExceptionCallback().onFatalException(
      Exception(Exception::Nature::OS_ERROR, Exception::Durability::PERMANENT, file, line,
                makeDescription(SYSCALL, call, errorNumber, macroArgs, argValues)));
  abort();
}

void Log::addContextToInternal(Exception& exception, const char* file, int line,
                               const char* macroArgs, ArrayPtr<String> argValues) {
  exception.wrapContext(file, line, makeDescription(LOG, nullptr, 0, macroArgs, argValues));
}

int Log::getOsErrorNumber() {
  int result = errno;
  return result == EINTR ? -1 : result;
}

Log::Context::Context(): next(getExceptionCallback()), registration(*this) {}
Log::Context::~Context() {}

void Log::Context::onRecoverableException(Exception&& exception) {
  addTo(exception);
  next.onRecoverableException(kj::mv(exception));
}
void Log::Context::onFatalException(Exception&& exception) {
  addTo(exception);
  next.onFatalException(kj::mv(exception));
}
void Log::Context::logMessage(StringPtr text) {
  // TODO(someday):  We could do something like log the context and then indent all log messages
  //   written until the end of the context.
  next.logMessage(text);
}

}  // namespace kj