///|
/// A `Graph` is a graph data structure that stores nodes and edges.
pub struct Graph[X] {
  nodes : Array[X]
  edges : Array[Array[Double]]
} derive(Show, Eq)

///|
pub fn[X] Graph::new() -> Graph[X] {
  { nodes: [], edges: [] }
}

///|
/// add_node adds the given node to the graph.
pub fn[X] Graph::add_node(self : Graph[X], node : X) -> Unit {
  self.nodes.push(node)
}

///|
/// add_nodes_from adds the given nodes to the graph.
pub fn[X] Graph::add_nodes_from(self : Graph[X], nodes : Array[X]) -> Unit {
  nodes.each(fn(node) { self.add_node(node) })
}

///|
/// remove_node removes the node with the given index from the graph and returns it.
pub fn[X] Graph::remove_node(self : Graph[X], idx : Int) -> X {
  self.nodes.remove(idx)
}

///|
/// remove_nodes_from removes the nodes with the given indices from the graph and returns them.
pub fn[X] Graph::remove_nodes_from(
  self : Graph[X],
  idxs : Array[Int],
) -> Array[X] {
  let removed = []
  idxs.each(fn(idx) { removed.push(self.remove_node(idx)) })
  removed
}

///|
/// add_edge adds an edge from the node with the given index to the node with the given index.
pub fn[X] Graph::add_edge(
  self : Graph[X],
  from : Int,
  to : Int,
  weight? : Double = 1.0,
) -> Unit {
  let max = self.nodes.length() - 1
  while self.edges.length() <= max {
    self.edges.push([])
  }
  if self.edges[from].length() <= max {
    self.edges[from].resize(max + 1, @double.infinity)
  }
  if self.edges[to].length() <= max {
    self.edges[to].resize(max + 1, @double.infinity)
  }
  self.edges[from][to] = weight
  self.edges[to][from] = weight
}

///|
/// add_edges_from adds the given edges to the graph.
pub fn[X] Graph::add_edges_from(
  self : Graph[X],
  edges : Array[(Int, Int)],
  weight? : Double = 1.0,
) -> Unit {
  edges.each(fn(edge) {
    let (from, to) = edge
    self.add_edge(from, to, weight~)
  })
}

///|
/// add_weighted_edges_from adds the given weighted edges to the graph.
pub fn[X] Graph::add_weighted_edges_from(
  self : Graph[X],
  edges : Array[(Int, Int, Double)],
) -> Unit {
  edges.each(fn(edge) {
    let (from, to, weight) = edge
    self.add_edge(from, to, weight~)
  })
}

///|
/// remove_edge removes the edge from the node with the given index to the node with the given index.
pub fn[X] Graph::remove_edge(self : Graph[X], from : Int, to : Int) -> Unit {
  self.edges[from][to] = @double.infinity
}

///|
/// has_edge returns true if there is an edge from the node with the given index
pub fn[X] Graph::has_edge(self : Graph[X], from : Int, to : Int) -> Bool {
  if self.edges.length() <= from {
    false
  } else if self.edges[from].length() <= to {
    false
  } else {
    self.edges[from][to] < @double.infinity
  }
}

///|
pub fn[X : Eq] Graph::has_node(self : Graph[X], x : X) -> Bool {
  self.nodes.search(x) is Some(_)
}

///|
/// get_edges returns the edges in the graph.
pub fn[X] Graph::get_edges(self : Graph[X]) -> Array[Array[Double]] {
  self.edges
}

///|
/// get_nodes returns the nodes in the graph.
pub fn[X] Graph::get_nodes(self : Graph[X]) -> Array[X] {
  self.nodes
}

///|
/// returns the node at the given index, or None if the index is out of bounds.
pub fn[X] Graph::op_get(self : Graph[X], idx : Int) -> X? {
  if idx < 0 || idx >= self.nodes.length() {
    None
  } else {
    Some(self.nodes[idx])
  }
}

///|
/// neighbors returns the indices of the neighbors of the node with the given index.
pub fn[X] Graph::neighbors(self : Graph[X], idx : Int) -> Array[Int] {
  let neighbors = []
  for i = 0; i < self.edges.length(); i = i + 1 {
    if self.edges[i].length() > idx {
      if self.edges[i][idx] < @double.infinity {
        neighbors.push(i)
      }
    }
  }
  neighbors
}

///|
/// edges_from returns the edges that end at the given node.
pub fn[X] Graph::edges_from(self : Graph[X], idx : Int) -> Array[(Int, Double)] {
  let edges = []
  for i = 0; i < self.edges.length(); i = i + 1 {
    if self.edges[i].length() > idx {
      if self.edges[i][idx] < @double.infinity {
        edges.push((i, self.edges[i][idx]))
      }
    }
  }
  edges
}

///|
/// edges_to returns the edges that start at the given node.
pub fn[X] Graph::edges_to(self : Graph[X], idx : Int) -> Array[(Int, Double)] {
  let edges = []
  for i = 0; i < self.edges.length(); i = i + 1 {
    if self.edges[idx].length() > i {
      if self.edges[idx][i] < @double.infinity {
        edges.push((i, self.edges[idx][i]))
      }
    }
  }
  edges
}

///|
/// degree returns the degree of the node with the given index.
pub fn[X] Graph::degree(self : Graph[X], idx : Int) -> Int {
  let mut degree = 0
  for i = 0; i < self.edges.length(); i = i + 1 {
    if self.edges[i].length() > idx {
      if self.edges[i][idx] < @double.infinity {
        degree = degree + 1
      }
    }
  }
  degree
}

///|
/// degrees returns the degree of each node in the graph.
pub fn[X] Graph::degrees(self : Graph[X]) -> Array[Int] {
  let degrees = []
  for i = 0; i < self.nodes.length(); i = i + 1 {
    degrees.push(self.degree(i))
  }
  degrees
}

///|
/// is_connected returns true if the graph is connected, false otherwise.
pub fn[X] Graph::is_connected(self : Graph[X]) -> Bool {
  let visited = Array::make(self.nodes.length(), false)
  let queue = [0]
  let mut visited_count = 0
  while queue.length() > 0 {
    let current = queue.pop()
    match current {
      Some(current) =>
        if visited[current] == false {
          visited[current] = true
          visited_count = visited_count + 1
          self
          .neighbors(current)
          .each(fn(neighbor) {
            if visited[neighbor] == false {
              queue.push(neighbor)
            }
          })
        }
      None => continue
    }
  }
  visited_count == self.nodes.length()
}

///|
/// is_bipartite returns true if the graph is bipartite, false otherwise.
pub fn[X] Graph::is_bipartite(self : Graph[X]) -> Bool {
  let color = Array::make(self.nodes.length(), -1)
  let queue = [0]
  let mut color_count = 2
  while queue.length() > 0 {
    let current = queue.pop()
    match current {
      Some(current) =>
        if color[current] == -1 {
          color[current] = 0
          color_count = color_count - 1
          self
          .neighbors(current)
          .each(fn(neighbor) {
            if color[neighbor] == -1 {
              color[neighbor] = 1 - color[current]
              queue.push(neighbor)
            } else if color[neighbor] == color[current] {
              color_count = 1
              return
            }
          })
        }
      None => continue
    }
  }
  color_count == 0
}

///|
/// is_tree returns true if the graph is a tree, false otherwise.
pub fn[X] Graph::is_tree(self : Graph[X]) -> Bool {
  let visited = Array::make(self.nodes.length(), false)
  let queue = [0]
  let mut visited_count = 0
  while queue.length() > 0 {
    let current = queue.pop()
    match current {
      Some(current) =>
        if visited[current] == false {
          visited[current] = true
          visited_count = visited_count + 1
          self
          .neighbors(current)
          .each(fn(neighbor) {
            if visited[neighbor] == false {
              if self.has_edge(current, neighbor) == false {
                visited_count = 1
                return
              }
              queue.push(neighbor)
            }
          })
        }
      None => continue
    }
  }
  visited_count == self.nodes.length()
}

///|
/// is_dag returns true if the graph is a directed acyclic graph (DAG), false otherwise.
pub fn[X] Graph::is_dag(self : Graph[X]) -> Bool {
  let visited = Array::make(self.nodes.length(), false)
  let queue = [0]
  let mut visited_count = 0
  while queue.length() > 0 {
    let current = queue.pop()
    match current {
      Some(current) =>
        if visited[current] == false {
          visited[current] = true
          visited_count = visited_count + 1
          self
          .neighbors(current)
          .each(fn(neighbor) {
            if visited[neighbor] == false {
              if self.has_edge(current, neighbor) == false {
                visited_count = 1
                return
              }
              queue.push(neighbor)
            }
          })
        }
      None => continue
    }
  }
  visited_count == self.nodes.length()
}

///|
/// transitive_closure returns the transitive closure of the graph, i.e., the graph with all transitive edges.
pub fn[X] Graph::transitive_closure(self : Graph[X]) -> Graph[X] {
  let closure = Graph::new()
  closure.add_nodes_from(self.nodes)
  for i = 0; i < self.nodes.length(); i = i + 1 {
    for j = 0; j < self.nodes.length(); j = j + 1 {
      if self.has_edge(i, j) {
        closure.add_edge(i, j)
      }
    }
  }
  for k = 0; k < self.nodes.length(); k = k + 1 {
    for i = 0; i < self.nodes.length(); i = i + 1 {
      for j = 0; j < self.nodes.length(); j = j + 1 {
        if closure.has_edge(i, j) && self.has_edge(i, k) && self.has_edge(k, j) {
          closure.add_edge(i, j)
        }
      }
    }
  }
  closure
}

///|
/// reverse returns the reverse of the graph, i.e., the graph with all edges reversed.
pub fn[X] Graph::reverse(self : Graph[X]) -> Graph[X] {
  let reversed = Graph::new()
  reversed.add_nodes_from(self.nodes)
  for i = 0; i < self.nodes.length(); i = i + 1 {
    for j = 0; j < self.nodes.length(); j = j + 1 {
      if self.has_edge(i, j) {
        reversed.add_edge(j, i)
      }
    }
  }
  reversed
}

///|
/// subgraph returns the subgraph of the graph with the given nodes.
pub fn[X] Graph::subgraph(self : Graph[X], idxs : Array[Int]) -> Graph[X] {
  let sub = Graph::new()
  sub.add_nodes_from(idxs.map(fn(idx) { self.nodes[idx] }))
  for i = 0; i < idxs.length(); i = i + 1 {
    for j = 0; j < idxs.length(); j = j + 1 {
      if self.has_edge(idxs[i], idxs[j]) {
        sub.add_edge(i, j)
      }
    }
  }
  sub
}

///|
/// union returns the union of the two graphs, i.e., the graph with all edges that are in either graph.
pub fn[X] Graph::union(self : Graph[X], other : Graph[X]) -> Graph[X] {
  let union = Graph::new()
  union.add_nodes_from(self.nodes)
  union.add_nodes_from(other.nodes)
  for i = 0; i < self.nodes.length(); i = i + 1 {
    for j = 0; j < other.nodes.length(); j = j + 1 {
      if self.has_edge(i, j) || other.has_edge(i, j) {
        union.add_edge(i, j)
      }
    }
  }
  union
}

///|
/// intersection returns the intersection of the two graphs, i.e., the graph with all edges that are in both graphs.
pub fn[X] Graph::intersection(self : Graph[X], other : Graph[X]) -> Graph[X] {
  let intersection = Graph::new()
  intersection.add_nodes_from(self.nodes)
  intersection.add_nodes_from(other.nodes)
  for i = 0; i < self.nodes.length(); i = i + 1 {
    for j = 0; j < other.nodes.length(); j = j + 1 {
      if self.has_edge(i, j) && other.has_edge(i, j) {
        intersection.add_edge(i, j)
      }
    }
  }
  intersection
}

///|
/// difference returns the difference of the two graphs, i.e., the graph with all edges that are in the first graph but not in the second.
pub fn[X] Graph::difference(self : Graph[X], other : Graph[X]) -> Graph[X] {
  let difference = Graph::new()
  difference.add_nodes_from(self.nodes)
  for i = 0; i < self.nodes.length(); i = i + 1 {
    for j = 0; j < other.nodes.length(); j = j + 1 {
      if self.has_edge(i, j) && other.has_edge(i, j) == false {
        difference.add_edge(i, j)
      }
    }
  }
  difference
}

///|
/// symmetric_difference returns the symmetric difference of the two graphs, i.e., the graph with all edges that are in either graph but not both.
pub fn[X] Graph::symmetric_difference(
  self : Graph[X],
  other : Graph[X],
) -> Graph[X] {
  let symmetric_difference = Graph::new()
  symmetric_difference.add_nodes_from(self.nodes)
  symmetric_difference.add_nodes_from(other.nodes)
  for i = 0; i < self.nodes.length(); i = i + 1 {
    for j = 0; j < other.nodes.length(); j = j + 1 {
      if self.has_edge(i, j) && other.has_edge(i, j) == false {
        symmetric_difference.add_edge(i, j)
      } else if self.has_edge(i, j) == false && other.has_edge(i, j) {
        symmetric_difference.add_edge(i, j)
      }
    }
  }
  symmetric_difference
}

///|
/// complement returns the complement of the graph, i.e., the graph with all edges reversed.
pub fn[X] Graph::complement(self : Graph[X]) -> Graph[X] {
  let complement = Graph::new()
  complement.add_nodes_from(self.nodes)
  for i = 0; i < self.nodes.length(); i = i + 1 {
    for j = 0; j < self.nodes.length(); j = j + 1 {
      if i != j && self.has_edge(i, j) == false {
        complement.add_edge(i, j)
      }
    }
  }
  complement
}

///|
/// to_dot converts a graph to a dot format string.
pub fn[X] Graph::to_dot(self : Graph[X]) -> String {
  let mut dot = "graph G {\n"
  for i = 0; i < self.nodes.length(); i = i + 1 {
    dot = dot + "  " + i.to_string() + ";\n"
    for j = 0; j < self.nodes.length(); j = j + 1 {
      if self.has_edge(i, j) {
        dot = dot + "  " + i.to_string() + " -- " + j.to_string() + ";\n"
      }
    }
  }
  dot = dot + "}\n"
  dot
}

///|
/// single_source_dijkstra 返回从 source 到所有其他节点的最短路径,使用 Dijkstra 算法。
pub fn[X] Graph::single_source_dijkstra(
  self : Graph[X],
  source~ : Int,
) -> (Array[Double], Array[Array[Int]]) {
  // 检查起点是否合法
  if source < 0 || source >= self.nodes.length() {
    return (
      Array::make(self.nodes.length(), @double.infinity),
      Array::make(self.nodes.length(), []),
    )
  }
  let n = self.nodes.length()
  // 初始化距离数组,所有节点距离为无穷大
  let dist = Array::make(n, @double.infinity)
  // 初始化路径数组
  let paths = Array::make(n, [])
  // 初始化已访问数组
  let visited = Array::make(n, false)
  // 初始化父节点数组
  let parent = Array::make(n, -1)
  dist[source] = 0.0
  for _ in 0..