# frozen_string_literal: true

module Sys
  module Memory
    require 'ffi'
    extend FFI::Library
    ffi_lib 'kernel32'

    typedef :uint32, :dword
    typedef :uint64, :dwordlong

    class MemoryStatusEx < FFI::Struct
      layout(
        :dwLength, :dword,
        :dwMemoryLoad, :dword,
        :ullTotalPhys, :dwordlong,
        :ullAvailPhys, :dwordlong,
        :ullTotalPageFile, :dwordlong,
        :ullAvailPageFile, :dwordlong,
        :ullTotalVirtual, :dwordlong,
        :ullAvailVirtual, :dwordlong,
        :ullAvailExtendedVirtual, :dwordlong
      )
    end

    private_constant :MemoryStatusEx

    attach_function :GlobalMemoryStatusEx, [MemoryStatusEx], :bool
    private_class_method :GlobalMemoryStatusEx

    ffi_lib 'psapi'

    class PerformanceInformation < FFI::Struct
      layout(
        :cb, :dword,
        :CommitTotal, :size_t,
        :CommitLimit, :size_t,
        :CommitPeak, :size_t,
        :PhysicalTotal, :size_t,
        :PhysicalAvailable, :size_t,
        :SystemCache, :size_t,
        :KernelTotal, :size_t,
        :KernelPaged, :size_t,
        :KernelNonpaged, :size_t,
        :PageSize, :size_t,
        :HandleCount, :dword,
        :ProcessCount, :dword,
        :ThreadCount, :dword
      )
    end

    private_constant :PerformanceInformation

    attach_function :GetPerformanceInfo, [PerformanceInformation, :dword], :bool
    private_class_method :GetPerformanceInfo

    # Obtain detailed memory information about your host in the form of a hash.
    # Note that the exact nature of this hash is largely dependent on your
    # operating system.
    #
    def memory
      struct = MemoryStatusEx.new
      struct[:dwLength] = struct.size

      unless GlobalMemoryStatusEx(struct)
        raise SystemCallError.new('GlobalMemoryStatusEx', FFI.errno)
      end

      hash = {}

      struct.members.each do |member|
        key = member.to_s.sub(/^ull|^dw/, '')
        hash[key] = struct[member]
      end

      hash.delete('Length')

      perf = PerformanceInformation.new

      unless GetPerformanceInfo(perf, perf.size)
        raise SystemCallError.new('GetPerformanceInfo', FFI.errno)
      end

      perf.members.each do |member|
        key = member.to_s
        hash[key] = perf[member]
      end

      hash.delete('cb')

      hash
    end

    # Total memory in bytes. By default this is only physical memory, but
    # if the +extended+ option is set to true then swap (pagefile) memory is
    # included as part of the total.
    #
    def total(extended: false)
      hash = memory
      extended ? hash['TotalPhys'] + hash['TotalPageFile'] : hash['TotalPhys']
    end

    # The physical memory currently available, in bytes. This is the amount of
    # physical memory that can be immediately reused without having to write
    # its contents to disk first.
    #
    # If the +extended+ option is set to true then available swap (pagefile)
    # memory is included as part of the total.
    #
    def free(extended: false)
      hash = memory
      extended ? hash['AvailPhys'] + hash['AvailPageFile'] : hash['AvailPhys']
    end

    # The memory, in bytes, currently in use. By default this is only
    # physical memory, but if the +extended+ option is set to true then
    # swap (pagefile) is included in the calculation.
    #
    def used(extended: false)
      total(extended: extended) - free(extended: extended)
    end

    # A number between 0 and 100 that specifies the approximate percentage of
    # physical memory that is in use.
    #
    # On MS Windows the +extended+ option is ignored, but present for interface
    # compatibility with other platforms.
    #
    def load(_extended: false)
      memory['MemoryLoad']
    end

    module_function :memory, :total, :free, :load, :used
  end
end
