/*
 * 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 <cinttypes>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <type_traits>

#include "Alignment.hpp"
#include "KAssert.h"
#include "KString.h"
#include "StackTrace.hpp"
#include "Memory.h"
#include "Porting.h"
#include "Natives.h"
#include "Types.h"
#include "std_support/CStdlib.hpp"

using namespace kotlin;

extern "C" {

KInt Kotlin_Any_hashCode(KConstRef thiz) {
  // NOTE: `Any?.identityHashCode()` is used in Blackhole implementations of both kotlinx-benchmark and
  //        K/N's own benchmarks. These usages rely on this being an intrinsic property of the object.
  //        So, calling `obj.identityHashCode()` should be seen by the optimizer as reading the entire
  //        `obj` memory, and any changes to `obj` beforehand couldn't be optimized away. Additionally,
  //        it should be very cheap to call in order not to pollute the time measurements.
  // Here we will use different mechanism for stable hashcode, using meta-objects
  // if moving collector will be used.
  return reinterpret_cast<uintptr_t>(thiz);
}

NO_INLINE OBJ_GETTER0(Kotlin_getCurrentStackTrace) {
    kotlin::StackTrace stackTrace;
    {
        // Don't use `kotlin::CallWithThreadState` to avoid messing up callstack.
        kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
        // Skip this function and primary `Throwable` constructor.
        stackTrace = kotlin::StackTrace<>::current(2);
    }

    ObjHolder resultHolder;
    ObjHeader* result = AllocArrayInstance(theNativePtrArrayTypeInfo, stackTrace.size(), resultHolder.slot());
    for (size_t index = 0; index < stackTrace.size(); ++index) {
        Kotlin_NativePtrArray_set(result, index, stackTrace[index]);
    }
    RETURN_OBJ(result);
}

OBJ_GETTER(Kotlin_getStackTraceStrings, KConstRef stackTrace) {
    const KNativePtr* array = PrimitiveArrayAddressOfElementAt<KNativePtr>(stackTrace->array(), 0);
    size_t size = stackTrace->array()->count_;
    auto stackTraceStrings = kotlin::CallWithThreadState<kotlin::ThreadState::kNative>(kotlin::GetStackTraceStrings, kotlin::std_support::span<void* const>(array, size));
    ObjHolder resultHolder;
    ObjHeader* strings = AllocArrayInstance(theArrayTypeInfo, stackTraceStrings.size(), resultHolder.slot());

    for (size_t index = 0; index < stackTraceStrings.size(); ++index) {
        ObjHolder holder;
        CreateStringFromCString(stackTraceStrings[index].c_str(), holder.slot());
        UpdateHeapRef(ArrayAddressOfElementAt(strings->array(), index), holder.obj());
    }

    RETURN_OBJ(strings);
}

// TODO: consider handling it with compiler magic instead.
OBJ_GETTER0(Kotlin_native_internal_undefined) {
  RETURN_OBJ(nullptr);
}

void* Kotlin_interop_malloc(KLong size, KInt align) {
  if (size < 0 || static_cast<std::make_unsigned_t<decltype(size)>>(size) > std::numeric_limits<size_t>::max()) {
    return nullptr;
  }
  RuntimeAssert(align > 0, "Invalid alignment %d", align);
  size_t actualAlign = static_cast<size_t>(align);
  size_t actualSize = AlignUp(static_cast<size_t>(size), actualAlign);

  void* result = std::memset(std_support::aligned_malloc(actualAlign, actualSize), 0, actualSize);
  RuntimeAssert(IsAligned(result, actualAlign), "aligned_malloc result %p is not aligned to %zu", result, actualAlign);

  return result;
}

void Kotlin_interop_free(void* ptr) {
    std_support::aligned_free(ptr);
}

void Kotlin_system_exitProcess(KInt status) {
  SwitchThreadState(mm::GetMemoryState(), ThreadState::kNative);
  std::exit(status);
}

const void* Kotlin_Any_getTypeInfo(KConstRef obj) {
  return obj->type_info();
}

void Kotlin_CPointer_CopyMemory(KNativePtr to, KNativePtr from, KInt count) {
  memcpy(to, from, count);
}

RUNTIME_NOTHROW RUNTIME_PURE KRef* Kotlin_arrayGetElementAddress(KRef array, KInt index) {
    ArrayHeader* arr = array->array();
    RuntimeAssert(index >= 0 && static_cast<uint32_t>(index) < arr->count_, "Index %" PRId32 " must be in [0, %" PRIu32 ")", index, arr->count_); 
    return ArrayAddressOfElementAt(arr, index);
}

RUNTIME_NOTHROW RUNTIME_PURE KInt* Kotlin_intArrayGetElementAddress(KRef array, KInt index) {
    ArrayHeader* arr = array->array();
    RuntimeAssert(index >= 0 && static_cast<uint32_t>(index) < arr->count_, "Index %" PRId32 " must be in [0, %" PRIu32 ")", index, arr->count_);
    return IntArrayAddressOfElementAt(arr, index);
}

RUNTIME_NOTHROW RUNTIME_PURE KLong* Kotlin_longArrayGetElementAddress(KRef array, KInt index) {
    ArrayHeader* arr = array->array();
    RuntimeAssert(index >= 0 && static_cast<uint32_t>(index) < arr->count_, "Index %" PRId32 " must be in [0, %" PRIu32 ")", index, arr->count_);
    return LongArrayAddressOfElementAt(arr, index);
}

}  // extern "C"
