#include "modules/memory.hpp"

#include <sys/types.h>
#include <sys/sysctl.h>
#include <unistd.h>           // getpagesize

#if defined(__DragonFly__)
#  include <sys/vmmeter.h>    // struct vmstats
#elif defined(__NetBSD__)
#  include <uvm/uvm_extern.h> // struct uvmexp_sysctl
#elif defined(__OpenBSD__)
#  include <uvm/uvmexp.h>     // struct uvmexp
#endif

static uint64_t get_total_memory() {
#if defined(HW_MEMSIZE) || defined(HW_PHYSMEM64)
  uint64_t physmem;
#else
  u_long physmem;
#endif
  int mib[] = {
    CTL_HW,
#if defined(HW_MEMSIZE)
    HW_MEMSIZE,
#elif defined(HW_PHYSMEM64)
    HW_PHYSMEM64,
#else
    HW_PHYSMEM,
#endif
  };
  u_int miblen = sizeof(mib) / sizeof(mib[0]);
  size_t sz = sizeof(physmem);
  if (sysctl(mib, miblen, &physmem, &sz, NULL, 0)) {
    throw std::runtime_error("sysctl hw.physmem failed");
  }
  return physmem;
}

static uint64_t get_free_memory() {
#if defined(__DragonFly__)
  struct vmstats vms;
  size_t sz = sizeof(vms);
  if (sysctlbyname("vm.vmstats", &vms, &sz, NULL, 0)) {
    throw std::runtime_error("sysctl vm.vmstats failed");
  }
  return static_cast<uint64_t>
    (vms.v_free_count + vms.v_inactive_count + vms.v_cache_count)
    * getpagesize();
#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
  u_int v_free_count = 0, v_inactive_count = 0, v_cache_count = 0;
  size_t sz = sizeof(u_int);
  sysctlbyname("vm.stats.vm.v_free_count",
               &v_free_count, &sz, NULL, 0);
  sysctlbyname("vm.stats.vm.v_inactive_count",
               &v_inactive_count, &sz, NULL, 0);
  sysctlbyname("vm.stats.vm.v_cache_count",
               &v_cache_count, &sz, NULL, 0);
  return static_cast<uint64_t>
    (v_free_count + v_inactive_count + v_cache_count)
    * getpagesize();
#elif defined(__NetBSD__) || defined(__OpenBSD__)
#ifdef VM_UVMEXP2
#  undef VM_UVMEXP
#  define VM_UVMEXP VM_UVMEXP2
#  define uvmexp uvmexp_sysctl
#else
#  define filepages vnodepages
#  define execpages vtextpages
#endif
  int mib[] = {
    CTL_VM,
    VM_UVMEXP,
  };
  u_int miblen = sizeof(mib) / sizeof(mib[0]);
  struct uvmexp uvmexp;
  size_t sz = sizeof(uvmexp);
  if (sysctl(mib, miblen, &uvmexp, &sz, NULL, 0)) {
    throw std::runtime_error("sysctl vm.uvmexp failed");
  }
  return static_cast<uint64_t>
    (uvmexp.free + uvmexp.inactive + uvmexp.filepages + uvmexp.execpages)
    * uvmexp.pagesize;
#endif
}

void waybar::modules::Memory::parseMeminfo() {
  meminfo_["MemTotal"] = get_total_memory() / 1024;
  meminfo_["MemAvailable"] = get_free_memory() / 1024;
}
