/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

/*!
 * \file cpu_device_api.cc
 */
#include <tvm/ffi/error.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/runtime/device_api.h>

#include <cstdlib>
#include <cstring>

#include "workspace_pool.h"

#ifdef __ANDROID__
#include <android/api-level.h>
#endif

#if defined(__linux__) || defined(__ANDROID__)
#include <sys/sysinfo.h>
#endif

#ifdef _WIN32
#include <windows.h>
#endif

#if defined(__APPLE__)
#include <TargetConditionals.h>
#include <sys/sysctl.h>
#endif

namespace tvm {
namespace runtime {
class CPUDeviceAPI final : public DeviceAPI {
 public:
  void SetDevice(Device dev) final {}
  void GetAttr(Device dev, DeviceAttrKind kind, ffi::Any* rv) final {
    if (kind == kExist) {
      *rv = 1;
    }

    switch (kind) {
      case kExist:
        break;
      case kTotalGlobalMemory: {
#if defined(__linux__) || defined(__ANDROID__)
        struct sysinfo info;
        if (sysinfo(&info) == 0) {
          *rv = static_cast<int64_t>(info.totalram) * info.mem_unit;  // Convert to bytes
        } else {
          *rv = -1;
        }
#elif defined(_WIN32)
        MEMORYSTATUSEX statex;
        statex.dwLength = sizeof(statex);
        if (GlobalMemoryStatusEx(&statex)) {
          *rv = static_cast<int64_t>(statex.ullTotalPhys);  // Total physical memory in bytes
        } else {
          *rv = -1;
        }
#elif defined(__APPLE__)
        int64_t mem;
        size_t size = sizeof(mem);
        if (sysctlbyname("hw.memsize", &mem, &size, nullptr, 0) == 0) {
          *rv = mem;
        } else {
          *rv = -1;
        }
#else
        *rv = -1;
#endif
      }
      default:
        break;
    }
  }
  void* AllocDataSpace(Device dev, size_t nbytes, size_t alignment, DLDataType type_hint) final {
    void* ptr;
#if _MSC_VER
    ptr = _aligned_malloc(nbytes, alignment);
    if (ptr == nullptr) throw std::bad_alloc();
#elif defined(__ANDROID__) && __ANDROID_API__ < 17
    ptr = memalign(alignment, nbytes);
    if (ptr == nullptr) throw std::bad_alloc();
#else
    // posix_memalign is available in android ndk since __ANDROID_API__ >= 17
    int ret = posix_memalign(&ptr, alignment, nbytes);
    if (ret != 0) throw std::bad_alloc();
#endif
    return ptr;
  }

  void FreeDataSpace(Device dev, void* ptr) final {
#if _MSC_VER
    _aligned_free(ptr);
#else
    free(ptr);
#endif
  }

  void StreamSync(Device dev, TVMStreamHandle stream) final {}

  void* AllocWorkspace(Device dev, size_t size, DLDataType type_hint) final;
  void FreeWorkspace(Device dev, void* data) final;

  bool SupportsDevicePointerArithmeticsOnHost() final { return true; }

  static CPUDeviceAPI* Global() {
    // NOTE: explicitly use new to avoid exit-time destruction of global state
    // Global state will be recycled by OS as the process exits.
    static auto* inst = new CPUDeviceAPI();
    return inst;
  }

 protected:
  void CopyDataFromTo(const void* from, size_t from_offset, void* to, size_t to_offset, size_t size,
                      Device dev_from, Device dev_to, DLDataType type_hint,
                      TVMStreamHandle stream) final {
    memcpy(static_cast<char*>(to) + to_offset, static_cast<const char*>(from) + from_offset, size);
  }
};

struct CPUWorkspacePool : public WorkspacePool {
  CPUWorkspacePool() : WorkspacePool(kDLCPU, CPUDeviceAPI::Global()) {}
};

static CPUWorkspacePool* CPUWorkspacePoolThreadLocal() {
  static thread_local CPUWorkspacePool inst;
  return &inst;
}

void* CPUDeviceAPI::AllocWorkspace(Device dev, size_t size, DLDataType type_hint) {
  return CPUWorkspacePoolThreadLocal()->AllocWorkspace(dev, size);
}

void CPUDeviceAPI::FreeWorkspace(Device dev, void* data) {
  CPUWorkspacePoolThreadLocal()->FreeWorkspace(dev, data);
}

TVM_FFI_STATIC_INIT_BLOCK() {
  namespace refl = tvm::ffi::reflection;
  refl::GlobalDef().def_packed("device_api.cpu", [](ffi::PackedArgs args, ffi::Any* rv) {
    DeviceAPI* ptr = CPUDeviceAPI::Global();
    *rv = static_cast<void*>(ptr);
  });
}
}  // namespace runtime
}  // namespace tvm
