// Ported from georust/geo
// https://github.com/georust/geo/blob/main/geo/src/algorithm/is_convex.rs

///|
/// Returns `Some(true)` if the linestring is convex, `Some(false)` if not,
/// and `None` if the linestring has too few coords or is degenerate.
///
/// `strict = true` requires the strict variant: no three consecutive coords
/// may be collinear. The default `is_convex` uses `strict = false`.
pub fn is_convex(ls : @type.LineString) -> Bool {
  is_convex_with_strictness(ls, false)
}

///|
pub fn is_strictly_convex(ls : @type.LineString) -> Bool {
  is_convex_with_strictness(ls, true)
}

///|

///|
/// Per-vertex orientation outcome for the convexity fold:
/// `Skip` keeps the running sign unchanged (collinear, non-strict mode);
/// `Sign` carries +1 / -1 for CCW / CW; `Fail` short-circuits to `false`.
priv enum ConvexStep {
  Skip
  Sign(Int)
  Fail
}

///|
fn is_convex_with_strictness(ls : @type.LineString, strict : Bool) -> Bool {
  let coords = ls.coords()
  // Use a closed view: if the first and last coords are equal, drop the last.
  let n = coords.length()
  let effective_n = if n > 1 && coords[0] == coords[n - 1] { n - 1 } else { n }
  if effective_n < 3 {
    return false
  }
  // Fold returns `Some(running_sign)` if the polygon stayed convex so far
  // (where `running_sign = 0` means no non-collinear triple seen yet) and
  // `None` once a sign flip / collinear-strict failure is detected.
  let result = (0)
    .until(effective_n)
    .fold(init=Some(0), fn(acc : Int?, i : Int) {
      match acc {
        None => None
        Some(sign) => {
          let p = coords[i]
          let q = coords[(i + 1) % effective_n]
          let r = coords[(i + 2) % effective_n]
          let det = @robust.orient2d(p, q, r)
          let step : ConvexStep = if det == 0.0 {
            if strict {
              Fail
            } else {
              Skip
            }
          } else if det > 0.0 {
            Sign(1)
          } else {
            Sign(-1)
          }
          match step {
            Fail => None
            Skip => Some(sign)
            Sign(s) => if sign == 0 || sign == s { Some(s) } else { None }
          }
        }
      }
    })
  result is Some(_)
}