/*
 * Copyright 2010-2017 JetBrains s.r.o.
 *
 * 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 <cstdint>
#ifdef KONAN_ANDROID
#include <android/log.h>
#endif
// region Tencent Code
#ifdef KONAN_OHOS
#include <hilog/log.h>
#include <hitrace/trace.h>
#endif
// endregion
#include <cstdio>
#include <cstdlib>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#if KONAN_WINDOWS
#include <windows.h>
#endif

#include <chrono>
#include <vector>

#include "CallsChecker.hpp"
#include "Common.h"
#include "CompilerConstants.hpp"
#include "Porting.h"
#include "KAssert.h"

using namespace kotlin;

namespace konan {

// Console operations.
void consoleInit() {
#if KONAN_WINDOWS
  // Note that this code enforces UTF-8 console output, so we may want to rethink
  // how we perform console IO, if it turns out, that UTF-16 is better output format.
  ::SetConsoleCP(CP_UTF8);
  ::SetConsoleOutputCP(CP_UTF8);
  // FIXME: should set original CP back during the deinit of the program.
  //  Otherwise, this codepage remains in the console.
#endif
}

void consoleWriteUtf8(const char* utf8, uint32_t sizeBytes) {
#ifdef KONAN_ANDROID
  if (kotlin::compiler::printToAndroidLogcat()) {
    // TODO: use sizeBytes!
    __android_log_print(ANDROID_LOG_INFO, "Konan_main", "%s", utf8);
  } else {
    ::write(STDOUT_FILENO, utf8, sizeBytes);
  }
// region Tencent Code
#elif KONAN_OHOS
  OH_LOG_Print(LOG_APP, LOG_INFO, LOG_DOMAIN, "Konan_main", "%{public}s", utf8);
// endregion
#else
  ::write(STDOUT_FILENO, utf8, sizeBytes);
#endif
}

void consoleErrorUtf8(const char* utf8, uint32_t sizeBytes) {
#ifdef KONAN_ANDROID
  if (kotlin::compiler::printToAndroidLogcat()) {
    // TODO: use sizeBytes!
    __android_log_print(ANDROID_LOG_ERROR, "Konan_main", "%s", utf8);
  } else {
    ::write(STDERR_FILENO, utf8, sizeBytes);
  }
// region Tencent Code
#elif KONAN_OHOS
  OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_DOMAIN, "Konan_main", "%{public}s", utf8);
// endregion
#else
  ::write(STDERR_FILENO, utf8, sizeBytes);
#endif
}

#if KONAN_WINDOWS
int getLastErrorMessage(char* message, uint32_t size) {
  auto errCode = ::GetLastError();
  if (errCode) {
    auto flags = FORMAT_MESSAGE_FROM_SYSTEM;
    auto errMsgBufSize = size / 4;
    std::vector<wchar_t> errMsgBuffer(errMsgBufSize);
    ::FormatMessageW(flags, NULL, errCode, 0, errMsgBuffer.data(), errMsgBufSize, NULL);
    ::WideCharToMultiByte(CP_UTF8, 0, errMsgBuffer.data(), -1, message, size, NULL, NULL);
  }
  return errCode;
}
#endif

int32_t consoleReadUtf8(void* utf8, uint32_t maxSizeBytes) {
#if KONAN_WINDOWS
  auto length = 0;
  void *stdInHandle = ::GetStdHandle(STD_INPUT_HANDLE);
  if (::GetFileType(stdInHandle) == FILE_TYPE_CHAR) {
    unsigned long bufferRead;
    // In UTF-16 there are surrogate pairs that a 2 * 16-bit long (4 bytes).
    auto bufferLength = maxSizeBytes / 4 - 1;
    std::vector<wchar_t> buffer(bufferLength);
    if (::ReadConsoleW(stdInHandle, buffer.data(), bufferLength, &bufferRead, NULL)) {
      length = ::WideCharToMultiByte(CP_UTF8, 0, buffer.data(), bufferRead, (char*) utf8,
                                     maxSizeBytes - 1, NULL, NULL);
      if (!length && kotlin::compiler::shouldContainDebugInfo()) {
        char msg[512];
        auto errCode = getLastErrorMessage(msg, sizeof(msg));
        consoleErrorf("UTF-16 to UTF-8 conversion error %d: %s", errCode, msg);
      }
      ((char*) utf8)[length] = 0;
    } else if (kotlin::compiler::shouldContainDebugInfo()) {
      char msg[512];
      auto errCode = getLastErrorMessage(msg, sizeof(msg));
      consoleErrorf("Console read failure: %d %s", errCode, msg);
    }
  } else {
    length = ::read(STDIN_FILENO, utf8, maxSizeBytes - 1);
  }
#else
  auto length = ::read(STDIN_FILENO, utf8, maxSizeBytes - 1);
#endif
  if (length <= 0) return -1;
  char* start = reinterpret_cast<char*>(utf8);
  char* current = start + length - 1;
  bool isTrimming = true;
  while (current >= start && isTrimming) {
    switch (*current) {
      case '\n':
      case '\r':
        *current = 0;
        length--;
        break;
      default:
        isTrimming = false;
    }
    current--;
  }
  return length;
}

NO_EXTERNAL_CALLS_CHECK void consolePrintf(const char* format, ...) {
  char buffer[1024];
  va_list args;
  va_start(args, format);
  int rv = std::vsnprintf(buffer, sizeof(buffer), format, args);
  if (rv < 0) return; // TODO: this may be too much exotic, but should i try to print itoa(error) and terminate?
  if (static_cast<size_t>(rv) >= sizeof(buffer)) rv = sizeof(buffer) - 1;  // TODO: Consider realloc or report truncating.
  va_end(args);
  consoleWriteUtf8(buffer, rv);
}

// TODO: Avoid code duplication.
NO_EXTERNAL_CALLS_CHECK void consoleErrorf(const char* format, ...) {
  char buffer[1024];
  va_list args;
  va_start(args, format);
  int rv = std::vsnprintf(buffer, sizeof(buffer), format, args);
  if (rv < 0) return; // TODO: this may be too much exotic, but should i try to print itoa(error) and terminate?
  if (static_cast<size_t>(rv) >= sizeof(buffer)) rv = sizeof(buffer) - 1;  // TODO: Consider realloc or report truncating.
  va_end(args);
  consoleErrorUtf8(buffer, rv);
}

void consoleFlush() {
  ::fflush(stdout);
  ::fflush(stderr);
}

pthread_key_t terminationKey;
pthread_once_t terminationKeyOnceControl = PTHREAD_ONCE_INIT;

typedef void (*destructor_t)(void*);

struct DestructorRecord {
  struct DestructorRecord* next;
  destructor_t destructor;
  void* destructorParameter;
};

static void onThreadExitCallback(void* value) {
  DestructorRecord* record = reinterpret_cast<DestructorRecord*>(value);
  pthread_setspecific(terminationKey, nullptr);
  while (record != nullptr) {
    record->destructor(record->destructorParameter);
    auto next = record->next;
    std::free(record);
    record = next;
  }
}

NO_EXTERNAL_CALLS_CHECK bool isOnThreadExitNotSetOrAlreadyStarted() {
    return terminationKey != 0 && pthread_getspecific(terminationKey) == nullptr;
}

// region Tencent Code
#if KONAN_LINUX || KONAN_OHOS
// endregion
static pthread_key_t dummyKey;
#endif
static void onThreadExitInit() {
// region Tencent Code
#if KONAN_LINUX || KONAN_OHOS
// endregion
  // Due to glibc bug we have to create first key as dummy, to avoid
  // conflicts with potentially uninitialized dlfcn error key.
  // https://code.woboq.org/userspace/glibc/dlfcn/dlerror.c.html#237
  // As one may see, glibc checks value of the key even if it was not inited (and == 0),
  // and so data associated with our legit key (== 0 as being the first one) is used.
  // Other libc are not affected, as usually == 0 pthread key is impossible.
  pthread_key_create(&dummyKey, nullptr);
#endif
  pthread_key_create(&terminationKey, onThreadExitCallback);
}

void onThreadExit(void (*destructor)(void*), void* destructorParameter) {
  // We cannot use pthread_cleanup_push() as it is lexical scope bound.
  pthread_once(&terminationKeyOnceControl, onThreadExitInit);
  DestructorRecord* destructorRecord = (DestructorRecord*)std::calloc(1, sizeof(DestructorRecord));
  destructorRecord->destructor = destructor;
  destructorRecord->destructorParameter = destructorParameter;
  destructorRecord->next =
      reinterpret_cast<DestructorRecord*>(pthread_getspecific(terminationKey));
  pthread_setspecific(terminationKey, destructorRecord);
}

// region Tencent Code
#if KONAN_LINUX || KONAN_OHOS
// endregion
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>

/**
 * We can't use gettid from glibc 2.30 because it is too new for us.
 */
NO_EXTERNAL_CALLS_CHECK NO_INLINE int gettid() {
    return static_cast<int>(syscall(__NR_gettid));
}
#endif

NO_EXTERNAL_CALLS_CHECK uintptr_t currentThreadId() {
#if defined(KONAN_OSX) or defined(KONAN_IOS) or defined(KONAN_TVOS) or defined(KONAN_WATCHOS)
    uint64_t tid;
    pthread_t self = pthread_self();
    RuntimeCheck(!pthread_threadid_np(self, &tid), "Error getting thread id");
    if constexpr (sizeof(uintptr_t) < sizeof(uint64_t)) {
        RuntimeCheck(*(reinterpret_cast<int32_t*>(&tid) + 1) == 0, "Thread id does not fit in a pointer");
    }
    return tid;
#elif KONAN_ANDROID
    return gettid();
// region Tencent Code
#elif KONAN_OHOS
    return gettid();
// endregion
#elif KONAN_LINUX
    return gettid();
#elif KONAN_WINDOWS
  return GetCurrentThreadId();
#else
#error "How to find currentThreadId()?"
#endif
}

// Time operations.
using namespace std::chrono;

// Get steady clock as a source of time
using steady_time_clock = std::conditional<high_resolution_clock::is_steady, high_resolution_clock, steady_clock>::type;

uint64_t getTimeMillis() {
  return duration_cast<milliseconds>(steady_time_clock::now().time_since_epoch()).count();
}

uint64_t getTimeNanos() {
  return duration_cast<nanoseconds>(steady_time_clock::now().time_since_epoch()).count();
}

uint64_t getTimeMicros() {
  return duration_cast<microseconds>(steady_time_clock::now().time_since_epoch()).count();
}

uint64_t getSystemTimeNanos() {
  return duration_cast<nanoseconds>(system_clock::now().time_since_epoch()).count();
}

bool isLittleEndian() {
  return __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__;
}

// region Tencent Code
// 标记一个同步任务的开始, 需成对使用 finishTrace
void startTrace(const char* sectionName) {
  #ifdef KONAN_OHOS
      OH_HiTrace_StartTrace(sectionName);
  #endif
  }
  
  // 标记一个同步任务的结束, 需成对使用 startTrace
  void finishTrace() {
  #ifdef KONAN_OHOS
      OH_HiTrace_FinishTrace();
  #endif
  }
  // endregion

}  // namespace konan
