// ArtboardDocument scene graph engine and tree transformation manager.

///|
pub(all) struct ArtboardDocument {
  title : String
  nodes : Map[String, Node]
  root_ids : Array[NodeID]
  mut width : Double
  mut height : Double
} derive(Debug)

///|
pub fn ArtboardDocument::new(
  title : String,
  w : Double,
  h : Double,
) -> ArtboardDocument {
  { title, nodes: Map([]), root_ids: [], width: w, height: h }
}

///|
pub fn ArtboardDocument::add_node(
  self : ArtboardDocument,
  node : Node,
  parent_id : NodeID?,
) -> Unit {
  let n = { ..node, parent_id, }
  self.nodes[n.id.value] = n

  match parent_id {
    None => self.root_ids.push(n.id)
    Some(pid) =>
      match self.nodes.get(pid.value) {
        Some(parent_node) =>
          match parent_node.node_type {
            GroupNode(children) => children.push(n.id)
            _ => ()
          }
        None => self.root_ids.push(n.id)
      }
  }
}

///|
pub fn ArtboardDocument::get_node(
  self : ArtboardDocument,
  id : NodeID,
) -> Node? {
  self.nodes.get(id.value)
}

///|
pub fn ArtboardDocument::update_node(
  self : ArtboardDocument,
  node : Node,
) -> Unit {
  self.nodes[node.id.value] = node
}

///|
pub fn ArtboardDocument::remove_node(
  self : ArtboardDocument,
  id : NodeID,
) -> Bool {
  match self.nodes.get(id.value) {
    None => false
    Some(node) => {
      // Remove from parent children if any
      match node.parent_id {
        Some(pid) =>
          match self.nodes.get(pid.value) {
            Some(parent_node) =>
              match parent_node.node_type {
                GroupNode(children) => {
                  let new_c : Array[NodeID] = []
                  for i = 0; i < children.length(); i = i + 1 {
                    if !(children[i] == id) {
                      new_c.push(children[i])
                    }
                  }
                  self.nodes[pid.value] = {
                    ..parent_node,
                    node_type: GroupNode(new_c),
                  }
                }
                _ => ()
              }
            None => ()
          }
        None => {
          let new_roots : Array[NodeID] = []
          for i = 0; i < self.root_ids.length(); i = i + 1 {
            if !(self.root_ids[i] == id) {
              new_roots.push(self.root_ids[i])
            }
          }
          self.root_ids.clear()
          for i = 0; i < new_roots.length(); i = i + 1 {
            self.root_ids.push(new_roots[i])
          }
        }
      }

      // If group, recursively remove children
      match node.node_type {
        GroupNode(children) =>
          for i = 0; i < children.length(); i = i + 1 {
            ignore(self.remove_node(children[i]))
          }
        _ => ()
      }

      self.nodes.remove(id.value)
      true
    }
  }
}

///|
pub fn ArtboardDocument::get_world_matrix(
  self : ArtboardDocument,
  id : NodeID,
) -> @math.Matrix2D {
  match self.nodes.get(id.value) {
    None => @math.Matrix2D::identity()
    Some(node) =>
      match node.parent_id {
        None => node.transform
        Some(pid) => self.get_world_matrix(pid).multiply(node.transform)
      }
  }
}

///|
pub fn ArtboardDocument::get_world_bounds(
  self : ArtboardDocument,
  id : NodeID,
) -> @math.Rect2D {
  match self.nodes.get(id.value) {
    None => @math.Rect2D::empty()
    Some(node) =>
      match node.node_type {
        GroupNode(children) => {
          let mut rect = @math.Rect2D::empty()
          for i = 0; i < children.length(); i = i + 1 {
            rect = rect.union(self.get_world_bounds(children[i]))
          }
          rect
        }
        _ => {
          let local_b = node.local_bounds()
          let world_m = self.get_world_matrix(id)
          world_m.transform_bounds(local_b)
        }
      }
  }
}

///|
pub fn ArtboardDocument::group_nodes(
  self : ArtboardDocument,
  node_ids : Array[NodeID],
) -> NodeID? {
  if node_ids.length() < 2 {
    None
  } else {
    let group_id = NodeID::generate("group")
    let group_node = Node::create_group(group_id, "Group", node_ids)
    self.add_node(group_node, None)

    for i = 0; i < node_ids.length(); i = i + 1 {
      let nid = node_ids[i]
      match self.nodes.get(nid.value) {
        Some(child) =>
          self.nodes[nid.value] = { ..child, parent_id: Some(group_id) }
        None => ()
      }
    }
    Some(group_id)
  }
}

///|
pub fn ArtboardDocument::ungroup_node(
  self : ArtboardDocument,
  group_id : NodeID,
) -> Array[NodeID] {
  match self.nodes.get(group_id.value) {
    None => []
    Some(group_node) =>
      match group_node.node_type {
        GroupNode(children) => {
          for i = 0; i < children.length(); i = i + 1 {
            let cid = children[i]
            match self.nodes.get(cid.value) {
              Some(child) =>
                self.nodes[cid.value] = {
                  ..child,
                  parent_id: group_node.parent_id,
                }
              None => ()
            }
          }
          ignore(self.remove_node(group_id))
          children
        }
        _ => []
      }
  }
}