/// Pure directional intra prediction. Availability is resolved by the caller:
/// top/right and bottom/left counts describe legal reconstructed references,
/// not merely samples present in a frame buffer. The private edge arrays keep
/// two leading entries for the negative indices used after upsampling.

///|
fn av1_directional_derivative(angle : Int) -> Int {
  match angle {
    3 => 1023
    6 => 547
    9 => 372
    14 => 273
    17 => 215
    20 => 178
    23 => 151
    26 => 132
    29 => 116
    32 => 102
    36 => 90
    39 => 80
    42 => 71
    45 => 64
    48 => 57
    51 => 51
    54 => 45
    58 => 40
    61 => 35
    64 => 31
    67 => 27
    70 => 23
    73 => 19
    76 => 15
    81 => 11
    84 => 7
    87 => 3
    _ => 0
  }
}

///|
fn av1_directional_filter_strength(
  width : Int,
  height : Int,
  delta : Int,
  neighbor_smooth : Bool,
) -> Int {
  let d = av1_intra_abs(delta)
  let sum = width + height
  if !neighbor_smooth {
    if sum <= 8 {
      if d >= 56 {
        1
      } else {
        0
      }
    } else if sum <= 16 {
      if d >= 40 {
        1
      } else {
        0
      }
    } else if sum <= 24 {
      if d >= 32 {
        3
      } else if d >= 16 {
        2
      } else if d >= 8 {
        1
      } else {
        0
      }
    } else if sum <= 32 {
      if d >= 32 {
        3
      } else if d >= 4 {
        2
      } else if d >= 1 {
        1
      } else {
        0
      }
    } else if d >= 1 {
      3
    } else {
      0
    }
  } else if sum <= 8 {
    if d >= 64 {
      2
    } else if d >= 40 {
      1
    } else {
      0
    }
  } else if sum <= 16 {
    if d >= 48 {
      2
    } else if d >= 20 {
      1
    } else {
      0
    }
  } else if sum <= 24 {
    if d >= 4 {
      3
    } else {
      0
    }
  } else if d >= 1 {
    3
  } else {
    0
  }
}

///|
/// `length` starts at the corner (edge[1]); the first sample is unchanged.
fn av1_directional_filter_edge(
  edge : Array[Int],
  length : Int,
  strength : Int,
) -> Unit {
  if strength == 0 {
    return
  }
  let taps = if strength == 1 {
    [0, 4, 8, 4, 0]
  } else if strength == 2 {
    [0, 5, 6, 5, 0]
  } else {
    [2, 4, 4, 4, 2]
  }
  let original = edge.copy()
  for i in 1..= length {
        length - 1
      } else {
        index
      }
      sum = sum + original[index + 1] * taps[j]
    }
    edge[i + 1] = (sum + 8) >> 4
  }
}

///|
fn av1_directional_upsample_edge(
  edge : Array[Int],
  length : Int,
  bit_depth : Int,
) -> Unit {
  let original = Array::make(length + 3, 0)
  original[0] = edge[1]
  original[1] = edge[1]
  for i in 0..> 4, bit_depth)
    edge[2 * i + 2] = original[i + 2]
  }
}

///|
fn av1_directional_use_upsample(
  width : Int,
  height : Int,
  delta : Int,
  neighbor_smooth : Bool,
) -> Bool {
  let d = av1_intra_abs(delta)
  d > 0 && d < 40 && width + height <= (if neighbor_smooth { 8 } else { 16 })
}

///|
/// Modes 1..8 use base angles 90,180,45,135,113,157,203,67. Angle delta is
/// the decoded symbol minus three, not an angle in degrees. `n_topright` and
/// `n_bottomleft` are -1 when that extension is not needed, zero when needed
/// but unavailable, and otherwise the available sample count. The caller
/// decides whether angle-delta syntax exists for the containing coding block.
fn av1_directional_predict(
  mode : Int,
  angle_delta : Int,
  width : Int,
  height : Int,
  above : Array[Int],
  left : Array[Int],
  top_left : Int,
  bit_depth : Int,
  n_top : Int,
  n_left : Int,
  n_topright : Int,
  n_bottomleft : Int,
  enable_intra_edge_filter : Bool,
  neighbor_smooth : Bool,
) -> Array[Int]? {
  if mode < 1 ||
    mode > 8 ||
    angle_delta < -3 ||
    angle_delta > 3 ||
    (bit_depth != 8 && bit_depth != 10 && bit_depth != 12) ||
    av1_intra_log2(width) is None ||
    av1_intra_log2(height) is None ||
    (width != height && !av1_tx_rect_supported(width, height)) {
    return None
  }
  let base_angles = [90, 180, 45, 135, 113, 157, 203, 67]
  let angle = base_angles[mode - 1] + 3 * angle_delta
  let need_right = angle < 90
  let need_bottom = angle > 180
  if n_top < 0 ||
    n_top > width ||
    n_left < 0 ||
    n_left > height ||
    n_topright < -1 ||
    n_topright > width ||
    n_bottomleft < -1 ||
    n_bottomleft > height ||
    need_right != (n_topright >= 0) ||
    need_bottom != (n_bottomleft >= 0) ||
    above.length() < n_top ||
    left.length() < n_left ||
    (n_topright > 0 && (n_top != width || above.length() < width + n_topright)) ||
    (
      n_bottomleft > 0 &&
      (n_left != height || left.length() < height + n_bottomleft)
    ) {
    return None
  }
  let maximum = (1 << bit_depth) - 1
  if top_left < 0 || top_left > maximum {
    return None
  }
  for sample in above {
    if sample < 0 || sample > maximum {
      return None
    }
  }
  for sample in left {
    if sample < 0 || sample > maximum {
      return None
    }
  }
  let base = 1 << (bit_depth - 1)
  let need_above = angle < 180
  let need_left = angle > 90
  if (!need_above && n_left == 0) || (!need_left && n_top == 0) {
    let value = if need_left {
      if n_top > 0 {
        above[0]
      } else {
        base + 1
      }
    } else if n_left > 0 {
      left[0]
    } else {
      base - 1
    }
    return Some(Array::make(width * height, value))
  }
  let edge_size = 2 * (width + height) + 4
  let a = Array::make(edge_size, base - 1)
  let l = Array::make(edge_size, base + 1)
  if need_left {
    let needed = height + (if n_bottomleft >= 0 { width } else { 0 })
    if n_left > 0 {
      for i in 0.. 0 {
        for i in 0.. 0 {
      for i in 0..= 0 { height } else { 0 })
    if n_top > 0 {
      for i in 0.. 0 {
        for i in 0.. 0 {
      for i in 0.. 0 && n_left > 0 {
    top_left
  } else if n_top > 0 {
    above[0]
  } else if n_left > 0 {
    left[0]
  } else {
    base
  }
  a[1] = corner
  l[1] = corner
  let mut up_above = 0
  let mut up_left = 0
  if enable_intra_edge_filter {
    if angle != 90 && angle != 180 {
      if need_above && need_left && width + height >= 24 {
        let filtered_corner = (5 * l[2] + 6 * a[1] + 5 * a[2] + 8) >> 4
        a[1] = filtered_corner
        l[1] = filtered_corner
      }
      if need_above && n_top > 0 {
        av1_directional_filter_edge(
          a,
          n_top + 1 + (if need_right { height } else { 0 }),
          av1_directional_filter_strength(
            width,
            height,
            angle - 90,
            neighbor_smooth,
          ),
        )
      }
      if need_left && n_left > 0 {
        av1_directional_filter_edge(
          l,
          n_left + 1 + (if need_bottom { width } else { 0 }),
          av1_directional_filter_strength(
            height,
            width,
            angle - 180,
            neighbor_smooth,
          ),
        )
      }
    }
    if need_above &&
      av1_directional_use_upsample(width, height, angle - 90, neighbor_smooth) {
      av1_directional_upsample_edge(
        a,
        width + (if need_right { height } else { 0 }),
        bit_depth,
      )
      up_above = 1
    }
    if need_left &&
      av1_directional_use_upsample(width, height, angle - 180, neighbor_smooth) {
      av1_directional_upsample_edge(
        l,
        height + (if need_bottom { width } else { 0 }),
        bit_depth,
      )
      up_left = 1
    }
  }
  let out = Array::make(width * height, 0)
  if angle == 90 {
    for row in 0..> (6 - up_above)) + (col << up_above)
        let maximum_index = (width + height - 1) << up_above
        if index >= maximum_index {
          a[maximum_index + 2]
        } else {
          let shift = ((x << up_above) & 63) >> 1
          (a[index + 2] * (32 - shift) + a[index + 3] * shift + 16) >> 5
        }
      } else if angle > 180 {
        let y = (col + 1) * dy
        let index = (y >> (6 - up_left)) + (row << up_left)
        let maximum_index = (width + height - 1) << up_left
        if index >= maximum_index {
          l[maximum_index + 2]
        } else {
          let shift = ((y << up_left) & 63) >> 1
          (l[index + 2] * (32 - shift) + l[index + 3] * shift + 16) >> 5
        }
      } else {
        let x = (col << 6) - (row + 1) * dx
        let index = x >> (6 - up_above)
        if index >= -(1 << up_above) {
          let shift = ((x * (1 << up_above)) & 63) >> 1
          (a[index + 2] * (32 - shift) + a[index + 3] * shift + 16) >> 5
        } else {
          let y = (row << 6) - (col + 1) * dy
          let index = y >> (6 - up_left)
          let shift = ((y * (1 << up_left)) & 63) >> 1
          (l[index + 2] * (32 - shift) + l[index + 3] * shift + 16) >> 5
        }
      }
      out[row * width + col] = value
    }
  }
  Some(out)
}