///|
pub struct CsrGraph {
  priv nvtxs : Int
  priv ncon : Int
  priv xadj : FixedArray[Int]
  priv adjncy : FixedArray[Int]
  priv vwgt : FixedArray[Int]?
  priv vsize : FixedArray[Int]?
  priv adjwgt : FixedArray[Int]?
}

///|
pub fn CsrGraph::new(
  nvtxs : Int,
  xadj : FixedArray[Int],
  adjncy : FixedArray[Int],
  ncon? : Int = 1,
  vwgt? : FixedArray[Int],
  vsize? : FixedArray[Int],
  adjwgt? : FixedArray[Int],
) -> CsrGraph raise MetisError {
  let graph = { nvtxs, ncon, xadj, adjncy, vwgt, vsize, adjwgt }
  graph.validate()
  graph
}

///|
pub fn CsrGraph::nvtxs(self : CsrGraph) -> Int {
  self.nvtxs
}

///|
pub fn CsrGraph::ncon(self : CsrGraph) -> Int {
  self.ncon
}

///|
pub fn CsrGraph::xadj(self : CsrGraph) -> FixedArray[Int] {
  self.xadj
}

///|
pub fn CsrGraph::adjncy(self : CsrGraph) -> FixedArray[Int] {
  self.adjncy
}

///|
pub fn CsrGraph::vertex_weights(self : CsrGraph) -> FixedArray[Int]? {
  self.vwgt
}

///|
pub fn CsrGraph::vertex_sizes(self : CsrGraph) -> FixedArray[Int]? {
  self.vsize
}

///|
pub fn CsrGraph::adjacency_weights(self : CsrGraph) -> FixedArray[Int]? {
  self.adjwgt
}

///|
fn CsrGraph::validate(self : CsrGraph) -> Unit raise MetisError {
  if self.nvtxs <= 0 || self.ncon <= 0 {
    raise InvalidGraph
  }
  if self.xadj.length() != self.nvtxs + 1 {
    raise InvalidGraph
  }
  if self.xadj.length() == 0 || self.xadj[0] != 0 {
    raise InvalidGraph
  }
  for i in 0.. self.xadj[i + 1] {
      raise InvalidGraph
    }
  }
  if self.xadj[self.nvtxs] != self.adjncy.length() {
    raise InvalidGraph
  }
  for vertex in self.adjncy {
    if vertex < 0 || vertex >= self.nvtxs {
      raise InvalidGraph
    }
  }
  match self.vwgt {
    None => ()
    Some(vwgt) =>
      if vwgt.length() != self.nvtxs * self.ncon {
        raise InvalidGraph
      }
  }
  match self.vsize {
    None => ()
    Some(vsize) => if vsize.length() != self.nvtxs { raise InvalidGraph }
  }
  match self.adjwgt {
    None => ()
    Some(adjwgt) =>
      if adjwgt.length() != self.adjncy.length() {
        raise InvalidGraph
      }
  }
}

///|
fn int_array_or_empty(value : FixedArray[Int]?) -> FixedArray[Int] {
  match value {
    None => []
    Some(value) => value
  }
}

///|
fn float_array_or_empty(value : FixedArray[Float]?) -> FixedArray[Float] {
  match value {
    None => []
    Some(value) => value
  }
}

///|
fn validate_partition_inputs(
  graph : CsrGraph,
  nparts : Int,
  target_partition_weights : FixedArray[Float]?,
  imbalance : FixedArray[Float]?,
) -> Unit raise MetisError {
  graph.validate()
  if nparts <= 0 {
    raise InvalidOptions
  }
  match target_partition_weights {
    None => ()
    Some(tpwgts) =>
      if tpwgts.length() != nparts * graph.ncon {
        raise InvalidOptions
      }
  }
  match imbalance {
    None => ()
    Some(ubvec) => if ubvec.length() != graph.ncon { raise InvalidOptions }
  }
}