// AArch64 immediates (bitmask/logical form)
// Ported from Cranelift (ImmLogic::maybe_from_u64), originally from VIXL.

///|
fn lowest_set_bit(value : UInt64) -> UInt64 {
  if value == 0UL {
    return 0UL
  }
  let bit = value.ctz()
  if bit < 0 || bit >= 64 {
    return 0UL
  }
  1UL << bit
}

///|
fn is_power_of_two_i(value : Int) -> Bool {
  if value <= 0 {
    return false
  }
  (value & (value - 1)) == 0
}

///|
fn multiplier_for_period(period : Int) -> UInt64? {
  match period {
    64 => Some(0x0000000000000001UL)
    32 => Some(0x0000000100000001UL)
    16 => Some(0x0001000100010001UL)
    8 => Some(0x0101010101010101UL)
    4 => Some(0x1111111111111111UL)
    2 => Some(0x5555555555555555UL)
    _ => None
  }
}

///|
/// AArch64 logical immediates are encoded as N:immr:imms (1:6:6), placed at bits 22..10.
///
/// `is_64` selects whether the encoding is for a 64-bit or 32-bit instruction.
/// For 32-bit, we follow Cranelift: duplicate the low 32 bits into both halves of a u64,
/// then run the same encoder.
pub fn aarch64_logic_imm_enc_bits(value : Int64, is_64 : Bool) -> Int? {
  let original = value.reinterpret_as_uint64()
  let mut v = if is_64 {
    original
  } else {
    let v32 = original & 0xFFFFFFFFUL
    (v32 << 32) | v32
  }

  // If the low bit is set, invert to avoid the rotated-wrap-around case.
  let mut inverted = false
  if (v & 1UL) == 1UL {
    v = v.lnot()
    inverted = true
  }
  if v == 0UL {
    return None
  }
  let a = lowest_set_bit(v)
  if a == 0UL {
    return None
  }
  let value_plus_a = v + a
  let b = lowest_set_bit(value_plus_a)
  let value_plus_a_minus_b = value_plus_a - b
  let c = lowest_set_bit(value_plus_a_minus_b)
  let mut period = 0
  let mut clz_a = 0
  let mut out_n = 0
  let mut mask = 0UL
  if c != 0UL {
    clz_a = a.clz()
    let clz_c = c.clz()
    period = clz_a - clz_c
    out_n = 0
    if period <= 0 || period >= 64 {
      return None
    }
    mask = (1UL << period) - 1UL
  } else {
    // Single run: period is 64 and N is 1.
    period = 64
    clz_a = a.clz()
    out_n = 1
    mask = 0xFFFFFFFFFFFFFFFFUL
  }
  if !is_power_of_two_i(period) {
    return None
  }
  guard multiplier_for_period(period) is Some(multiplier) else { return None }
  let stretch = b - a
  if (stretch & mask.lnot()) != 0UL {
    return None
  }
  let candidate = stretch * multiplier
  if v != candidate {
    return None
  }

  // Count set bits in the base stretch.
  let clz_b = if b == 0UL { -1 } else { b.clz() }
  let mut s = clz_a - clz_b
  let mut r = 0
  if inverted {
    s = period - s
    r = (clz_b + 1) & (period - 1)
  } else {
    r = (clz_a + 1) & (period - 1)
  }

  // Encode imms: ((2*-period) | (s-1)) & 0x3f
  let imms = (-(period * 2) | (s - 1)) & 63
  let immr = r & 63
  let n = out_n & 1
  Some((n << 12) | (immr << 6) | imms)
}