///|
const INVERSE_SIN_60 : Double = 1.1547005383792515290182975610039149113

///|
const INVERSE_RES0_U_GNOMONIC : Double = 2.61803398874989588842

///|
const SQRT7 : Double = 2.6457513110645905905016157536392604257

///|
let face_ijk_base_cells : Array[(Int, Int)] = [
  // face 0
  (16, 0),
  (18, 0),
  (24, 0),
  (33, 0),
  (30, 0),
  (32, 3),
  (49, 1),
  (48, 3),
  (50, 3),
  (8, 0),
  (5, 5),
  (10, 5),
  (22, 0),
  (16, 0),
  (18, 0),
  (41, 1),
  (33, 0),
  (30, 0),
  (4, 0),
  (0, 5),
  (2, 5),
  (15, 1),
  (8, 0),
  (5, 5),
  (31, 1),
  (22, 0),
  (16, 0),
  // face 1
  (2, 0),
  (6, 0),
  (14, 0),
  (10, 0),
  (11, 0),
  (17, 3),
  (24, 1),
  (23, 3),
  (25, 3),
  (0, 0),
  (1, 5),
  (9, 5),
  (5, 0),
  (2, 0),
  (6, 0),
  (18, 1),
  (10, 0),
  (11, 0),
  (4, 1),
  (3, 5),
  (7, 5),
  (8, 1),
  (0, 0),
  (1, 5),
  (16, 1),
  (5, 0),
  (2, 0),
  // face 2
  (7, 0),
  (21, 0),
  (38, 0),
  (9, 0),
  (19, 0),
  (34, 3),
  (14, 1),
  (20, 3),
  (36, 3),
  (3, 0),
  (13, 5),
  (29, 5),
  (1, 0),
  (7, 0),
  (21, 0),
  (6, 1),
  (9, 0),
  (19, 0),
  (4, 2),
  (12, 5),
  (26, 5),
  (0, 1),
  (3, 0),
  (13, 5),
  (2, 1),
  (1, 0),
  (7, 0),
  // face 3
  (26, 0),
  (42, 0),
  (58, 0),
  (29, 0),
  (43, 0),
  (62, 3),
  (38, 1),
  (47, 3),
  (64, 3),
  (12, 0),
  (28, 5),
  (44, 5),
  (13, 0),
  (26, 0),
  (42, 0),
  (21, 1),
  (29, 0),
  (43, 0),
  (4, 3),
  (15, 5),
  (31, 5),
  (3, 1),
  (12, 0),
  (28, 5),
  (7, 1),
  (13, 0),
  (26, 0),
  // face 4
  (31, 0),
  (41, 0),
  (49, 0),
  (44, 0),
  (53, 0),
  (61, 3),
  (58, 1),
  (65, 3),
  (75, 3),
  (15, 0),
  (22, 5),
  (33, 5),
  (28, 0),
  (31, 0),
  (41, 0),
  (42, 1),
  (44, 0),
  (53, 0),
  (4, 4),
  (8, 5),
  (16, 5),
  (12, 1),
  (15, 0),
  (22, 5),
  (26, 1),
  (28, 0),
  (31, 0),
  // face 5
  (50, 0),
  (48, 0),
  (49, 3),
  (32, 0),
  (30, 3),
  (33, 3),
  (24, 3),
  (18, 3),
  (16, 3),
  (70, 0),
  (67, 0),
  (66, 3),
  (52, 3),
  (50, 0),
  (48, 0),
  (37, 3),
  (32, 0),
  (30, 3),
  (83, 0),
  (87, 3),
  (85, 3),
  (74, 3),
  (70, 0),
  (67, 0),
  (57, 1),
  (52, 3),
  (50, 0),
  // face 6
  (25, 0),
  (23, 0),
  (24, 3),
  (17, 0),
  (11, 3),
  (10, 3),
  (14, 3),
  (6, 3),
  (2, 3),
  (45, 0),
  (39, 0),
  (37, 3),
  (35, 3),
  (25, 0),
  (23, 0),
  (27, 3),
  (17, 0),
  (11, 3),
  (63, 0),
  (59, 3),
  (57, 3),
  (56, 3),
  (45, 0),
  (39, 0),
  (46, 3),
  (35, 3),
  (25, 0),
  // face 7
  (36, 0),
  (20, 0),
  (14, 3),
  (34, 0),
  (19, 3),
  (9, 3),
  (38, 3),
  (21, 3),
  (7, 3),
  (55, 0),
  (40, 0),
  (27, 3),
  (54, 3),
  (36, 0),
  (20, 0),
  (51, 3),
  (34, 0),
  (19, 3),
  (72, 0),
  (60, 3),
  (46, 3),
  (73, 3),
  (55, 0),
  (40, 0),
  (71, 3),
  (54, 3),
  (36, 0),
  // face 8
  (64, 0),
  (47, 0),
  (38, 3),
  (62, 0),
  (43, 3),
  (29, 3),
  (58, 3),
  (42, 3),
  (26, 3),
  (84, 0),
  (69, 0),
  (51, 3),
  (82, 3),
  (64, 0),
  (47, 0),
  (76, 3),
  (62, 0),
  (43, 3),
  (97, 0),
  (89, 3),
  (71, 3),
  (98, 3),
  (84, 0),
  (69, 0),
  (96, 3),
  (82, 3),
  (64, 0),
  // face 9
  (75, 0),
  (65, 0),
  (58, 3),
  (61, 0),
  (53, 3),
  (44, 3),
  (49, 3),
  (41, 3),
  (31, 3),
  (94, 0),
  (86, 0),
  (76, 3),
  (81, 3),
  (75, 0),
  (65, 0),
  (66, 3),
  (61, 0),
  (53, 3),
  (107, 0),
  (104, 3),
  (96, 3),
  (101, 3),
  (94, 0),
  (86, 0),
  (85, 3),
  (81, 3),
  (75, 0),
  // face 10
  (57, 0),
  (59, 0),
  (63, 3),
  (74, 0),
  (78, 3),
  (79, 3),
  (83, 3),
  (92, 3),
  (95, 3),
  (37, 0),
  (39, 3),
  (45, 3),
  (52, 0),
  (57, 0),
  (59, 0),
  (70, 3),
  (74, 0),
  (78, 3),
  (24, 0),
  (23, 3),
  (25, 3),
  (32, 3),
  (37, 0),
  (39, 3),
  (50, 3),
  (52, 0),
  (57, 0),
  // face 11
  (46, 0),
  (60, 0),
  (72, 3),
  (56, 0),
  (68, 3),
  (80, 3),
  (63, 3),
  (77, 3),
  (90, 3),
  (27, 0),
  (40, 3),
  (55, 3),
  (35, 0),
  (46, 0),
  (60, 0),
  (45, 3),
  (56, 0),
  (68, 3),
  (14, 0),
  (20, 3),
  (36, 3),
  (17, 3),
  (27, 0),
  (40, 3),
  (25, 3),
  (35, 0),
  (46, 0),
  // face 12
  (71, 0),
  (89, 0),
  (97, 3),
  (73, 0),
  (91, 3),
  (103, 3),
  (72, 3),
  (88, 3),
  (105, 3),
  (51, 0),
  (69, 3),
  (84, 3),
  (54, 0),
  (71, 0),
  (89, 0),
  (55, 3),
  (73, 0),
  (91, 3),
  (38, 0),
  (47, 3),
  (64, 3),
  (34, 3),
  (51, 0),
  (69, 3),
  (36, 3),
  (54, 0),
  (71, 0),
  // face 13
  (96, 0),
  (104, 0),
  (107, 3),
  (98, 0),
  (110, 3),
  (115, 3),
  (97, 3),
  (111, 3),
  (119, 3),
  (76, 0),
  (86, 3),
  (94, 3),
  (82, 0),
  (96, 0),
  (104, 0),
  (84, 3),
  (98, 0),
  (110, 3),
  (58, 0),
  (65, 3),
  (75, 3),
  (62, 3),
  (76, 0),
  (86, 3),
  (64, 3),
  (82, 0),
  (96, 0),
  // face 14
  (85, 0),
  (87, 0),
  (83, 3),
  (101, 0),
  (102, 3),
  (100, 3),
  (107, 3),
  (112, 3),
  (114, 3),
  (66, 0),
  (67, 3),
  (70, 3),
  (81, 0),
  (85, 0),
  (87, 0),
  (94, 3),
  (101, 0),
  (102, 3),
  (49, 0),
  (48, 3),
  (50, 3),
  (61, 3),
  (66, 0),
  (67, 3),
  (75, 3),
  (81, 0),
  (85, 0),
  // face 15
  (95, 0),
  (92, 0),
  (83, 0),
  (79, 0),
  (78, 0),
  (74, 3),
  (63, 1),
  (59, 3),
  (57, 3),
  (109, 0),
  (108, 0),
  (100, 5),
  (93, 1),
  (95, 0),
  (92, 0),
  (77, 1),
  (79, 0),
  (78, 0),
  (117, 4),
  (118, 5),
  (114, 5),
  (106, 1),
  (109, 0),
  (108, 0),
  (90, 1),
  (93, 1),
  (95, 0),
  // face 16
  (90, 0),
  (77, 0),
  (63, 0),
  (80, 0),
  (68, 0),
  (56, 3),
  (72, 1),
  (60, 3),
  (46, 3),
  (106, 0),
  (93, 0),
  (79, 5),
  (99, 1),
  (90, 0),
  (77, 0),
  (88, 1),
  (80, 0),
  (68, 0),
  (117, 3),
  (109, 5),
  (95, 5),
  (113, 1),
  (106, 0),
  (93, 0),
  (105, 1),
  (99, 1),
  (90, 0),
  // face 17
  (105, 0),
  (88, 0),
  (72, 0),
  (103, 0),
  (91, 0),
  (73, 3),
  (97, 1),
  (89, 3),
  (71, 3),
  (113, 0),
  (99, 0),
  (80, 5),
  (116, 1),
  (105, 0),
  (88, 0),
  (111, 1),
  (103, 0),
  (91, 0),
  (117, 2),
  (106, 5),
  (90, 5),
  (121, 1),
  (113, 0),
  (99, 0),
  (119, 1),
  (116, 1),
  (105, 0),
  // face 18
  (119, 0),
  (111, 0),
  (97, 0),
  (115, 0),
  (110, 0),
  (98, 3),
  (107, 1),
  (104, 3),
  (96, 3),
  (121, 0),
  (116, 0),
  (103, 5),
  (120, 1),
  (119, 0),
  (111, 0),
  (112, 1),
  (115, 0),
  (110, 0),
  (117, 1),
  (113, 5),
  (105, 5),
  (118, 1),
  (121, 0),
  (116, 0),
  (114, 1),
  (120, 1),
  (119, 0),
  // face 19
  (114, 0),
  (112, 0),
  (107, 0),
  (100, 0),
  (102, 0),
  (101, 3),
  (83, 1),
  (87, 3),
  (85, 3),
  (118, 0),
  (120, 0),
  (115, 5),
  (108, 1),
  (114, 0),
  (112, 0),
  (92, 1),
  (100, 0),
  (102, 0),
  (117, 0),
  (121, 5),
  (119, 5),
  (109, 1),
  (118, 0),
  (120, 0),
  (95, 1),
  (108, 1),
  (114, 0),
]

///|
fn face_ijk_to_base_cell(face_ijk : FaceIJK) -> (Int, Int) {
  let coordinate = face_ijk.1
  let index = face_ijk.0 * 27 +
    coordinate.0 * 9 +
    coordinate.1 * 3 +
    coordinate.2
  face_ijk_base_cells[index]
}

///|
fn base_cell_is_cw_offset(base_cell : Int, face : Int) -> Bool {
  match base_cell {
    14 => face == 2 || face == 6
    24 => face == 1 || face == 5
    38 => face == 3 || face == 7
    49 => face == 0 || face == 9
    58 => face == 4 || face == 8
    63 => face == 11 || face == 15
    72 => face == 12 || face == 16
    83 => face == 10 || face == 19
    97 => face == 13 || face == 17
    107 => face == 14 || face == 18
    _ => false
  }
}

///|
fn rotate_digit_60ccw(digit : Int) -> Int {
  match digit {
    1 => 5
    5 => 4
    4 => 6
    6 => 2
    2 => 3
    3 => 1
    _ => digit
  }
}

///|
fn rotate_index_60ccw(value : UInt64) -> UInt64 {
  let mut rotated = value
  let mut current = 1
  while current <= resolution_of(value) {
    rotated = set_digit(
      rotated,
      current,
      rotate_digit_60ccw(digit_of(rotated, current)),
    )
    current += 1
  }
  rotated
}

///|
fn rotate_pentagon_index_60ccw(value : UInt64) -> UInt64 {
  let mut rotated = value
  let mut found_nonzero = false
  let mut current = 1
  while current <= resolution_of(value) {
    rotated = set_digit(
      rotated,
      current,
      rotate_digit_60ccw(digit_of(rotated, current)),
    )
    if !found_nonzero && digit_of(rotated, current) != 0 {
      found_nonzero = true
      if leading_nonzero_digit(rotated) == 1 {
        rotated = rotate_index_60ccw(rotated)
      }
    }
    current += 1
  }
  rotated
}

///|
fn coord_up_ap7(value : CoordIJK) -> CoordIJK {
  let i = value.0 - value.2
  let j = value.1 - value.2
  coord_normalize(
    CoordIJK(
      ((3 * i - j).to_double() / 7.0).round().to_int(),
      ((i + 2 * j).to_double() / 7.0).round().to_int(),
      0,
    ),
  )
}

///|
fn unit_ijk_to_digit(value : CoordIJK) -> Int {
  let normalized = coord_normalize(value)
  match normalized {
    CoordIJK(0, 0, 0) => 0
    CoordIJK(0, 0, 1) => 1
    CoordIJK(0, 1, 0) => 2
    CoordIJK(0, 1, 1) => 3
    CoordIJK(1, 0, 0) => 4
    CoordIJK(1, 0, 1) => 5
    CoordIJK(1, 1, 0) => 6
    _ => 7
  }
}

///|
fn set_base_cell(value : UInt64, base_cell : Int) -> UInt64 {
  (value & UInt64::lnot(127UL << 45)) | (base_cell.to_uint64() << 45)
}

///|
fn face_ijk_to_h3(
  face_ijk : FaceIJK,
  resolution : Int,
) -> Result[UInt64, H3Error] {
  let mut value = 35184372088831UL
  value = value | (1UL << 59)
  value = with_resolution(value, resolution)
  if resolution == 0 {
    let coordinate = face_ijk.1
    if coordinate.0 > 2 || coordinate.1 > 2 || coordinate.2 > 2 {
      return Err(InvalidCoordinate)
    }
    let (base_cell, _) = face_ijk_to_base_cell(face_ijk)
    value = set_base_cell(value, base_cell)
    return Ok(value)
  }
  let mut coordinate = face_ijk.1
  let mut current = resolution - 1
  while current >= 0 {
    let last = coordinate
    let last_center = if is_resolution_class_iii(current + 1) {
      coordinate = coord_up_ap7(coordinate)
      coord_down_ap7(coordinate)
    } else {
      coordinate = coord_up_ap7r(coordinate)
      coord_down_ap7r(coordinate)
    }
    let difference = coord_normalize(coord_sub(last, last_center))
    let digit = unit_ijk_to_digit(difference)
    if digit == 7 {
      return Err(InvalidCoordinate)
    }
    value = set_digit(value, current + 1, digit)
    current -= 1
  }
  if coordinate.0 > 2 || coordinate.1 > 2 || coordinate.2 > 2 {
    return Err(InvalidCoordinate)
  }
  let (base_cell, rotations) = face_ijk_to_base_cell(
    FaceIJK(face_ijk.0, coordinate),
  )
  value = set_base_cell(value, base_cell)
  if is_pentagon_base_cell(base_cell) {
    if leading_nonzero_digit(value) == 1 {
      value = if base_cell_is_cw_offset(base_cell, face_ijk.0) {
        rotate_index_60cw(value)
      } else {
        rotate_index_60ccw(value)
      }
    }
    let mut count = 0
    while count < rotations {
      value = rotate_pentagon_index_60ccw(value)
      count += 1
    }
  } else {
    let mut count = 0
    while count < rotations {
      value = rotate_index_60ccw(value)
      count += 1
    }
  }
  Ok(value)
}

///|
fn vec3_to_closest_face(point : Vec3) -> (Int, Double) {
  let mut face = 0
  let mut squared_distance = 5.0
  let mut current = 0
  while current < face_center_points.length() {
    let difference = vec3_linear_combination(
      1.0,
      face_center_points[current],
      -1.0,
      point,
    )
    let candidate = vec3_dot(difference, difference)
    if candidate < squared_distance {
      face = current
      squared_distance = candidate
    }
    current += 1
  }
  (face, squared_distance)
}

///|
fn vec3_azimuth_rads(origin : Vec3, point : Vec3) -> Double {
  let (north, east) = tangent_basis(origin)
  let projected = vec3_normalize(
    vec3_linear_combination(1.0, point, -vec3_dot(point, origin), origin),
  )
  @math.atan2(vec3_dot(projected, east), vec3_dot(projected, north))
}

///|
fn vec3_to_hex_2d(point : Vec3, resolution : Int) -> (Int, Double, Double) {
  let (face, squared_distance) = vec3_to_closest_face(point)
  let distance = @math.acos(1.0 - squared_distance * 0.5)
  if distance < EPSILON {
    return (face, 0.0, 0.0)
  }
  let mut theta = positive_angle(
    face_axes_azimuth[face] -
    positive_angle(vec3_azimuth_rads(face_center_points[face], point)),
  )
  if is_resolution_class_iii(resolution) {
    theta = positive_angle(theta - AP7_ROT_RADS)
  }
  let mut radius = @math.tan(distance) * INVERSE_RES0_U_GNOMONIC
  let mut current = 0
  while current < resolution {
    radius *= SQRT7
    current += 1
  }
  (face, radius * @math.cos(theta), radius * @math.sin(theta))
}

///|
fn hex_2d_to_coord_ijk(x : Double, y : Double) -> CoordIJK {
  let a1 = x.abs()
  let a2 = y.abs()
  let x2 = a2 * INVERSE_SIN_60
  let x1 = a1 + x2 / 2.0
  let m1 = x1.to_int()
  let m2 = x2.to_int()
  let r1 = x1 - m1.to_double()
  let r2 = x2 - m2.to_double()
  let mut i = 0
  let mut j = 0
  if r1 < 0.5 {
    if r1 < 1.0 / 3.0 {
      i = m1
      j = if r2 < (1.0 + r1) / 2.0 { m2 } else { m2 + 1 }
    } else {
      j = if r2 < 1.0 - r1 { m2 } else { m2 + 1 }
      i = if 1.0 - r1 <= r2 && r2 < 2.0 * r1 { m1 + 1 } else { m1 }
    }
  } else if r1 < 2.0 / 3.0 {
    j = if r2 < 1.0 - r1 { m2 } else { m2 + 1 }
    i = if 2.0 * r1 - 1.0 < r2 && r2 < 1.0 - r1 { m1 } else { m1 + 1 }
  } else if r2 < r1 / 2.0 {
    i = m1 + 1
    j = m2
  } else {
    i = m1 + 1
    j = m2 + 1
  }
  if x < 0.0 {
    if j % 2 == 0 {
      let axis_i = j / 2
      let difference = i - axis_i
      i -= 2 * difference
    } else {
      let axis_i = (j + 1) / 2
      let difference = i - axis_i
      i -= 2 * difference + 1
    }
  }
  if y < 0.0 {
    i -= (2 * j + 1) / 2
    j = -j
  }
  coord_normalize(CoordIJK(i, j, 0))
}

///|
fn lat_lng_to_vec3(latitude : Double, longitude : Double) -> Vec3 {
  let latitude_radians = latitude / DEGREES_PER_RADIAN
  let longitude_radians = longitude / DEGREES_PER_RADIAN
  let ring_radius = @math.cos(latitude_radians)
  Vec3(
    @math.cos(longitude_radians) * ring_radius,
    @math.sin(longitude_radians) * ring_radius,
    @math.sin(latitude_radians),
  )
}

///|
/// Encodes decimal latitude and longitude as the containing H3 cell.
pub fn H3Index::from_lat_lng(
  latitude : Double,
  longitude : Double,
  resolution : Int,
) -> Result[H3Index, H3Error] {
  if resolution < 0 || resolution > MAX_RESOLUTION {
    return Err(InvalidResolution(resolution))
  }
  if latitude.is_nan() ||
    longitude.is_nan() ||
    latitude < -90.0 ||
    latitude > 90.0 ||
    longitude < -180.0 ||
    longitude > 180.0 {
    return Err(InvalidCoordinate)
  }
  let (face, x, y) = vec3_to_hex_2d(
    lat_lng_to_vec3(latitude, longitude),
    resolution,
  )
  let index = hex_2d_to_coord_ijk(x, y)
  let value = match face_ijk_to_h3(FaceIJK(face, index), resolution) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  H3Index::from_u64(value)
}