// ORB Descriptor Engine (Intensity Centroid Orientation & Rotated BRIEF 256-bit Descriptors)

///|
pub(all) struct OrbConfig {
  patch_radius : Int
  descriptor_bits : Int
  use_orientation : Bool
}

///|
pub fn OrbConfig::new(
  patch_radius : Int,
  descriptor_bits : Int,
  use_orientation : Bool,
) -> OrbConfig {
  { patch_radius, descriptor_bits, use_orientation }
}

///|
pub fn OrbConfig::default() -> OrbConfig {
  OrbConfig::new(16, 256, true)
}

///|
// Standard 256 ORB sampling pair pattern offsets (-15 to 15)
let orb_pattern : Array[(Int, Int, Int, Int)] = [
  (8, -3, 9, 5),
  (4, 2, 7, -12),
  (-11, 9, -8, 2),
  (7, -12, 12, -13),
  (2, -13, 2, 12),
  (1, -7, 1, 6),
  (-2, -10, -2, 9),
  (-13, -13, -11, -8),
  (-13, -3, -12, 12),
  (12, -12, 14, 5),
  (3, -1, 3, 14),
  (-10, -10, -7, 13),
  (-5, -8, -3, 12),
  (-7, -13, -1, 10),
  (-3, -13, -3, 8),
  (-13, -14, -10, 10),
  (-12, -14, -8, 7),
  (-10, -14, -6, 4),
  (-8, -14, -4, 1),
  (-6, -14, -2, -2),
  (-4, -14, 0, -5),
  (-2, -14, 2, -8),
  (0, -14, 4, -11),
  (2, -14, 6, -14),
  (4, -14, 8, -14),
  (6, -14, 10, -14),
  (8, -14, 12, -14),
  (10, -14, 14, -14),
  (-14, -12, -11, 7),
  (-12, -12, -9, 4),
  (-10, -12, -7, 1),
  (-8, -12, -5, -2),
  (-6, -12, -3, -5),
  (-4, -12, -1, -8),
  (-2, -12, 1, -11),
  (0, -12, 3, -14),
  (2, -12, 5, -14),
  (4, -12, 7, -14),
  (6, -12, 9, -14),
  (8, -12, 11, -14),
  (10, -12, 13, -14),
  (-14, -10, -11, 4),
  (-12, -10, -9, 1),
  (-10, -10, -7, -2),
  (-8, -10, -5, -5),
  (-6, -10, -3, -8),
  (-4, -10, -1, -11),
  (-2, -10, 1, -14),
  (0, -10, 3, -14),
  (2, -10, 5, -14),
  (4, -10, 7, -14),
  (6, -10, 9, -14),
  (8, -10, 11, -14),
  (-14, -8, -11, 1),
  (-12, -8, -9, -2),
  (-10, -8, -7, -5),
  (-8, -8, -5, -8),
  (-6, -8, -3, -11),
  (-4, -8, -1, -14),
  (-2, -8, 1, -14),
  (0, -8, 3, -14),
  (2, -8, 5, -14),
  (4, -8, 7, -14),
  (6, -8, 9, -14),
  (-14, -6, -11, -2),
  (-12, -6, -9, -5),
  (-10, -6, -7, -8),
  (-8, -6, -5, -11),
  (-6, -6, -3, -14),
  (-4, -6, -1, -14),
  (-2, -6, 1, -14),
  (0, -6, 3, -14),
  (2, -6, 5, -14),
  (4, -6, 7, -14),
  (-14, -4, -11, -5),
  (-12, -4, -9, -8),
  (-10, -4, -7, -11),
  (-8, -4, -5, -14),
  (-6, -4, -3, -14),
  (-4, -4, -1, -14),
  (-2, -4, 1, -14),
  (0, -4, 3, -14),
  (2, -4, 5, -14),
  (-14, -2, -11, -8),
  (-12, -2, -9, -11),
  (-10, -2, -7, -14),
  (-8, -2, -5, -14),
  (-6, -2, -3, -14),
  (-4, -2, -1, -14),
  (-2, -2, 1, -14),
  (0, -2, 3, -14),
  (-14, 0, -11, -11),
  (-12, 0, -9, -14),
  (-10, 0, -7, -14),
  (-8, 0, -5, -14),
  (-6, 0, -3, -14),
  (-4, 0, -1, -14),
  (-2, 0, 1, -14),
  (-14, 2, -11, -14),
  (-12, 2, -9, -14),
  (-10, 2, -7, -14),
  (-8, 2, -5, -14),
  (-6, 2, -3, -14),
  (-4, 2, -1, -14),
  (-14, 4, -11, -14),
  (-12, 4, -9, -14),
  (-10, 4, -7, -14),
  (-8, 4, -5, -14),
  (-6, 4, -3, -14),
  (-14, 6, -11, -14),
  (-12, 6, -9, -14),
  (-10, 6, -7, -14),
  (-8, 6, -5, -14),
  (-14, 8, -11, -14),
  (-12, 8, -9, -14),
  (-10, 8, -7, -14),
  (-14, 10, -11, -14),
  (-12, 10, -9, -14),
  (-14, 12, -11, -14),
  (5, -3, 6, 4),
  (2, 4, 7, -10),
  (-9, 7, -6, 4),
  (6, -10, 10, -11),
  (4, -11, 4, 10),
  (3, -5, 3, 8),
  (-4, -8, -4, 7),
  (-11, -11, -9, -6),
  (-11, -1, -10, 10),
  (10, -10, 12, 7),
  (5, -3, 5, 12),
  (-8, -8, -5, 11),
  (-3, -6, -1, 10),
  (-5, -11, 1, 8),
  (-1, -11, -1, 6),
  (-11, -12, -8, 8),
  (-10, -12, -6, 5),
  (-8, -12, -4, 2),
  (-6, -12, -2, -1),
  (-4, -12, 0, -4),
  (-2, -12, 2, -7),
  (0, -12, 4, -10),
  (2, -12, 6, -13),
  (4, -12, 8, -13),
  (6, -12, 10, -13),
  (8, -12, 12, -13),
  (10, -12, 14, -13),
  (-12, -10, -9, 5),
  (-10, -10, -7, 2),
  (-8, -10, -5, -1),
  (-6, -10, -3, -4),
  (-4, -10, -1, -7),
  (-2, -10, 1, -10),
  (0, -10, 3, -13),
  (2, -10, 5, -13),
  (4, -10, 7, -13),
  (6, -10, 9, -13),
  (8, -10, 11, -13),
  (10, -10, 13, -13),
  (-12, -8, -9, 2),
  (-10, -8, -7, -1),
  (-8, -8, -5, -4),
  (-6, -8, -3, -7),
  (-4, -8, -1, -10),
  (-2, -8, 1, -13),
  (0, -8, 3, -13),
  (2, -8, 5, -13),
  (4, -8, 7, -13),
  (6, -8, 9, -13),
  (8, -8, 11, -13),
  (-12, -6, -9, -1),
  (-10, -6, -7, -4),
  (-8, -6, -5, -7),
  (-6, -6, -3, -10),
  (-4, -6, -1, -13),
  (-2, -6, 1, -13),
  (0, -6, 3, -13),
  (2, -6, 5, -13),
  (4, -6, 7, -13),
  (6, -6, 9, -13),
  (-12, -4, -9, -4),
  (-10, -4, -7, -7),
  (-8, -4, -5, -10),
  (-6, -4, -3, -13),
  (-4, -4, -1, -13),
  (-2, -4, 1, -13),
  (0, -4, 3, -13),
  (2, -4, 5, -13),
  (4, -4, 7, -13),
  (-12, -2, -9, -7),
  (-10, -2, -7, -10),
  (-8, -2, -5, -13),
  (-6, -2, -3, -13),
  (-4, -2, -1, -13),
  (-2, -2, 1, -13),
  (0, -2, 3, -13),
  (2, -2, 5, -13),
  (-12, 0, -9, -10),
  (-10, 0, -7, -13),
  (-8, 0, -5, -13),
  (-6, 0, -3, -13),
  (-4, 0, -1, -13),
  (-2, 0, 1, -13),
  (0, 0, 3, -13),
  (-12, 2, -9, -13),
  (-10, 2, -7, -13),
  (-8, 2, -5, -13),
  (-6, 2, -3, -13),
  (-4, 2, -1, -13),
  (-2, 2, 1, -13),
  (-12, 4, -9, -13),
  (-10, 4, -7, -13),
  (-8, 4, -5, -13),
  (-6, 4, -3, -13),
  (-4, 4, -1, -13),
  (-12, 6, -9, -13),
  (-10, 6, -7, -13),
  (-8, 6, -5, -13),
  (-6, 6, -3, -13),
  (-12, 8, -9, -13),
  (-10, 8, -7, -13),
  (-8, 8, -5, -13),
  (-12, 10, -9, -13),
  (-10, 10, -7, -13),
  (-12, 12, -9, -13),
  (3, -2, 4, 3),
  (1, 3, 5, -8),
  (-7, 5, -4, 2),
  (4, -8, 8, -9),
  (2, -9, 2, 8),
  (1, -3, 1, 6),
  (-2, -6, -2, 5),
  (-9, -9, -7, -4),
  (-9, -1, -8, 8),
  (8, -8, 10, 5),
  (3, -3, 3, 10),
  (-6, -6, -3, 9),
  (-1, -4, 1, 8),
  (-3, -9, 3, 6),
  (1, -9, 1, 4),
  (-9, -10, -6, 6),
  (-8, -10, -4, 3),
  (-6, -10, -2, 0),
  (-4, -10, 0, -2),
  (-2, -10, 2, -5),
  (0, -10, 4, -8),
  (2, -10, 6, -11),
  (4, -10, 8, -11),
  (6, -10, 10, -11),
  (8, -10, 12, -11),
  (10, -10, 14, -11),
  (-10, -8, -7, 3),
  (-8, -8, -5, 0),
  (-6, -8, -3, -3),
  (-4, -8, -1, -5),
  (-2, -8, 1, -8),
  (0, -8, 3, -11),
  (2, -8, 5, -11),
  (4, -8, 7, -11),
  (6, -8, 9, -11),
  (8, -8, 11, -11),
  (10, -8, 13, -11),
  (-10, -6, -7, 0),
  (-8, -6, -5, -3),
  (-6, -6, -3, -6),
  (-4, -6, -1, -8),
  (-2, -6, 1, -11),
  (0, -6, 3, -11),
  (2, -6, 5, -11),
  (4, -6, 7, -11),
  (6, -6, 9, -11),
  (8, -6, 11, -11),
  (-10, -4, -7, -3),
  (-8, -4, -5, -6),
  (-6, -4, -3, -8),
  (-4, -4, -1, -11),
  (-2, -4, 1, -11),
  (0, -4, 3, -11),
  (2, -4, 5, -11),
  (4, -4, 7, -11),
  (6, -4, 9, -11),
  (-10, -2, -7, -6),
  (-8, -2, -5, -8),
  (-6, -2, -3, -11),
  (-4, -2, -1, -11),
  (-2, -2, 1, -11),
  (0, -2, 3, -11),
  (2, -2, 5, -11),
  (4, -2, 7, -11),
  (-10, 0, -7, -8),
  (-8, 0, -5, -11),
  (-6, 0, -3, -11),
  (-4, 0, -1, -11),
  (-2, 0, 1, -11),
  (0, 0, 3, -11),
  (2, 0, 5, -11),
  (-10, 2, -7, -11),
  (-8, 2, -5, -11),
  (-6, 2, -3, -11),
  (-4, 2, -1, -11),
  (-2, 2, 1, -11),
  (0, 2, 3, -11),
  (-10, 4, -7, -11),
  (-8, 4, -5, -11),
  (-6, 4, -3, -11),
  (-4, 4, -1, -11),
  (-2, 4, 1, -11),
  (-10, 6, -7, -11),
  (-8, 6, -5, -11),
  (-6, 6, -3, -11),
  (-4, 6, -1, -11),
  (-10, 8, -7, -11),
  (-8, 8, -5, -11),
  (-6, 8, -3, -11),
  (-10, 10, -7, -11),
  (-8, 10, -5, -11),
  (-10, 12, -7, -11),
]

///|
pub fn compute_orientation(img : GrayImage, kp : Keypoint) -> Double {
  let patch_radius = 15
  let mut m01 = 0.0
  let mut m10 = 0.0

  let mut dy = -patch_radius
  while dy <= patch_radius {
    let mut dx = -patch_radius
    while dx <= patch_radius {
      if dx * dx + dy * dy <= patch_radius * patch_radius {
        let val = Double::from_int(img.get(kp.x + dx, kp.y + dy))
        m10 = m10 + Double::from_int(dx) * val
        m01 = m01 + Double::from_int(dy) * val
      }
      dx = dx + 1
    }
    dy = dy + 1
  }

  if m10 == 0.0 && m01 == 0.0 {
    return 0.0
  }

  let abs_x = if m10 < 0.0 { -m10 } else { m10 }
  let abs_y = if m01 < 0.0 { -m01 } else { m01 }

  let mut angle = 0.0
  if abs_x >= abs_y {
    let ratio = m01 / m10
    angle = ratio - ratio * ratio * ratio / 3.0
    if m10 < 0.0 {
      if m01 >= 0.0 {
        angle = angle + 3.141592653589793
      } else {
        angle = angle - 3.141592653589793
      }
    }
  } else {
    let ratio = m10 / m01
    angle = ratio - ratio * ratio * ratio / 3.0
    if m01 >= 0.0 {
      angle = 1.5707963267948966 - angle
    } else {
      angle = -1.5707963267948966 - angle
    }
  }
  angle
}

///|
pub fn compute_orb_descriptors(
  img : GrayImage,
  kps : Array[Keypoint],
) -> (Array[Keypoint], Array[Descriptor]) {
  compute_orb_descriptors_with_config(img, kps, OrbConfig::default())
}

///|
pub fn compute_orb_descriptors_with_config(
  img : GrayImage,
  kps : Array[Keypoint],
  config : OrbConfig,
) -> (Array[Keypoint], Array[Descriptor]) {
  let valid_kps : Array[Keypoint] = []
  let descriptors : Array[Descriptor] = []
  let border = if config.patch_radius < 15 { 15 } else { config.patch_radius }
  let bits = if config.descriptor_bits < 1 {
    1
  } else if config.descriptor_bits > 256 {
    256
  } else {
    config.descriptor_bits
  }

  let mut idx = 0
  while idx < kps.length() {
    let kp = kps[idx]
    if kp.x >= border &&
      kp.x < img.width - border &&
      kp.y >= border &&
      kp.y < img.height - border {
      let angle = if config.use_orientation {
        compute_orientation(img, kp)
      } else {
        0.0
      }
      let oriented_kp = Keypoint::new(kp.x, kp.y, kp.response, angle)

      // Taylor expansion approximation for cos/sin in Double
      let mut a = angle
      while a > 3.141592653589793 {
        a = a - 6.283185307179586
      }
      while a < -3.141592653589793 {
        a = a + 6.283185307179586
      }
      let a2 = a * a
      let cos_a = 1.0 - a2 / 2.0 + a2 * a2 / 24.0
      let sin_a = a - a2 * a / 6.0 + a2 * a2 * a / 120.0

      let desc = Descriptor::new((bits + 7) / 8)

      let mut pair_idx = 0
      while pair_idx < bits {
        let (ax, ay, bx, by) = orb_pattern[pair_idx]

        let fax = Double::from_int(ax)
        let fay = Double::from_int(ay)
        let fbx = Double::from_int(bx)
        let fby = Double::from_int(by)

        let rx_a = (fax * cos_a - fay * sin_a + 0.5).to_int()
        let ry_a = (fax * sin_a + fay * cos_a + 0.5).to_int()

        let rx_b = (fbx * cos_a - fby * sin_a + 0.5).to_int()
        let ry_b = (fbx * sin_a + fby * cos_a + 0.5).to_int()

        let val_a = img.get(oriented_kp.x + rx_a, oriented_kp.y + ry_a)
        let val_b = img.get(oriented_kp.x + rx_b, oriented_kp.y + ry_b)

        if val_a < val_b {
          let byte_idx = pair_idx / 8
          let bit_idx = pair_idx % 8
          let current_byte = desc.data[byte_idx].to_int()
          let updated_byte = current_byte | (1 << bit_idx)
          desc.data[byte_idx] = updated_byte.to_byte()
        }

        pair_idx = pair_idx + 1
      }

      valid_kps.push(oriented_kp)
      descriptors.push(desc)
    }
    idx = idx + 1
  }

  (valid_kps, descriptors)
}