require "mach"

module MacCPUs
  OPTIMIZATION_FLAGS = {
    :penryn => "-march=core2 -msse4.1",
    :core2 => "-march=core2",
    :core => "-march=prescott",
    :g3 => "-mcpu=750",
    :g4 => "-mcpu=7400",
    :g4e => "-mcpu=7450",
    :g5 => "-mcpu=970",
    :g5_64 => "-mcpu=970 -arch ppc64"
  }.freeze
  def optimization_flags
    OPTIMIZATION_FLAGS
  end

  # These methods use info spewed out by sysctl.
  # Look in <mach/machine.h> for decoding info.
  def type
    case sysctl_int("hw.cputype")
    when 7
      :intel
    when 18
      :ppc
    else
      :dunno
    end
  end

  def family
    if intel?
      case sysctl_int("hw.cpufamily")
      when 0x73d67300 # Yonah: Core Solo/Duo
        :core
      when 0x426f69ef # Merom: Core 2 Duo
        :core2
      when 0x78ea4fbc # Penryn
        :penryn
      when 0x6b5a4cd2 # Nehalem
        :nehalem
      when 0x573B5EEC # Arrandale
        :arrandale
      when 0x5490B78C # Sandy Bridge
        :sandybridge
      when 0x1F65E835 # Ivy Bridge
        :ivybridge
      when 0x10B282DC # Haswell
        :haswell
      when 0x582ed09c # Broadwell
        :broadwell
      else
        :dunno
      end
    elsif ppc?
      case sysctl_int("hw.cpusubtype")
      when 9
        :g3  # PowerPC 750
      when 10
        :g4  # PowerPC 7400
      when 11
        :g4e # PowerPC 7450
      when 100
        # This is the only 64-bit PPC CPU type, so it's useful
        # to distinguish in `brew config` output and in bottle tags
        MacOS.prefer_64_bit? ? :g5_64 : :g5 # PowerPC 970
      else
        :dunno
      end
    end
  end

  def extmodel
    sysctl_int("machdep.cpu.extmodel")
  end

  def cores
    sysctl_int("hw.ncpu")
  end

  def bits
    sysctl_bool("hw.cpu64bit_capable") ? 64 : 32
  end

  def arch_32_bit
    intel? ? :i386 : :ppc
  end

  def arch_64_bit
    intel? ? :x86_64 : :ppc64
  end

  # Returns an array that's been extended with ArchitectureListExtension,
  # which provides helpers like #as_arch_flags and #as_cmake_arch_flags.
  def universal_archs
    # Building 64-bit is a no-go on Tiger, and pretty hit or miss on Leopard.
    # Don't even try unless Tigerbrew's experimental 64-bit Leopard support is enabled.
    if MacOS.version <= :leopard && !MacOS.prefer_64_bit?
      [arch_32_bit].extend ArchitectureListExtension
    else
      [arch_32_bit, arch_64_bit].extend ArchitectureListExtension
    end
  end

  # Determines whether the current CPU and macOS combination
  # can run an executable of the specified architecture.
  # `arch` is a symbol in the same format returned by
  # Hardware::CPU.family
  def can_run?(arch)
    if Hardware::CPU.intel?
      intel_can_run? arch
    elsif Hardware::CPU.ppc?
      ppc_can_run? arch
    else
      false
    end
  end

  def features
    @features ||= sysctl_n(
      "machdep.cpu.features",
      "machdep.cpu.extfeatures",
      "machdep.cpu.leaf7_features"
    ).split(" ").map { |s| s.downcase.to_sym }
  end

  def aes?
    sysctl_bool("hw.optional.aes")
  end

  def altivec?
    sysctl_bool("hw.optional.altivec")
  end

  def avx?
    sysctl_bool("hw.optional.avx1_0")
  end

  def avx2?
    sysctl_bool("hw.optional.avx2_0")
  end

  def sse3?
    sysctl_bool("hw.optional.sse3")
  end

  def ssse3?
    sysctl_bool("hw.optional.supplementalsse3")
  end

  def sse4?
    sysctl_bool("hw.optional.sse4_1")
  end

  def sse4_2?
    sysctl_bool("hw.optional.sse4_2")
  end

  private

  def sysctl_bool(key)
    sysctl_int(key) == 1
  end

  def sysctl_int(key)
    sysctl_n(key).to_i
  end

  def sysctl_n(*keys)
    (@properties ||= {}).fetch(keys) do
      @properties[keys] = Utils.popen_read("/usr/sbin/sysctl", "-n", *keys)
    end
  end

  def intel_can_run?(arch)
    case arch
    when :ppc
      # Rosetta is still available
      MacOS.version < :lion
    when :ppc64
      # Rosetta never supported PPC64
      false
    when :x86_64
      Hardware::CPU.is_64_bit?
    when :i386
      true
    else # dunno
      false
    end
  end

  def ppc_can_run?(arch)
    case arch
    when :ppc
      true
    when :ppc64
      Hardware::CPU.is_64_bit?
    else
      # Intel is never supported
      false
    end
  end
end
