///|
/// 布局:把调用树算成一组矩形坐标。
///
/// 采用 icicle 方向(根在顶部、逐层向下)。算法本身很简单,要点有三个:
///
///   1. **按值比例切分**:子项宽度 = 父宽度 × (子值 / 父值)。因此不需要任何
///      矩形打包算法,天然不重叠、不留缝;
///   2. **最后一个子项直接取父节点右边界**,而不是用累加值。否则浮点误差会
///      一点点累积,几千个矩形之后就会出现肉眼可见的缝或重叠;
///   3. **自身耗时作为合成子项参与切分**,这样「父宽 = 全部子项宽之和」严格成立,
///      图面上不会出现空洞。
///
/// 布局不做最小宽度过滤:它产出的是**精确**的几何数据,是否绘制小于若干像素的
/// 矩形属于渲染层的取舍。

///|
/// 一个待绘制的矩形。
pub struct Rect {
  x : Double
  /// 纵向位置。由 `layout` 在排布完成后统一确定:
  /// 火焰图方向下根在底部,因此 y 依赖整棵树的最大深度。
  mut y : Double
  width : Double
  height : Double
  name : String
  /// 累计权重;合成项(自身耗时)则为该帧的自身耗时
  value : Double
  depth : Int
  /// 差异模式下的变化量;普通模式下为 `None`
  delta : Double?
} derive(Eq)

///|
/// 显式声明 `Eq` 的方法可被当作常规方法调用。
///
/// 新版编译器不再自动完成这个提升,不声明会报 `implicit_impl_as_method`。
pub extend Rect with Eq::{not_equal, equal}

///|
/// 布局结果。渲染器拿到的布局自带完整几何信息,无需再传画布参数。
pub struct Layout {
  rects : Array[Rect]
  /// 画布宽度,与传入值一致
  width : Double
  /// 每行高度,与传入值一致
  row_height : Double
  /// 最深的深度下标(渲染高度为 `(max_depth + 1) * row_height`)
  max_depth : Int
  /// 全部权重之和,用于计算占比
  total : Double
  /// 差异模式下最大的变化量绝对值,用于颜色归一化;非差异模式为 0
  max_delta : Double
  /// 纵向方向:`false` 火焰图(根在底部),`true` 冰柱图(根在顶部)。
  /// 交互脚本判断「祖先在上还是在下」时需要它。
  inverted : Bool
} derive(Eq)

///|
/// 显式声明 `Eq` 的方法可被当作常规方法调用。
///
/// 新版编译器不再自动完成这个提升,不声明会报 `implicit_impl_as_method`。
pub extend Layout with Eq::{not_equal, equal}

///|
/// 「自身耗时」合成帧的名字。
pub fn self_placeholder() -> String {
  "(self)"
}

///|
/// 参与切分的一个子项:要么是真实子节点,要么是自身耗时的合成项。
priv struct Item {
  name : String
  value : Double
  node : Node?
}

///|
/// 排序规则:值降序;同值时按名字升序,保证确定性。
fn compare_items(a : Item, b : Item) -> Int {
  if a.value > b.value {
    -1
  } else if a.value < b.value {
    1
  } else {
    String::compare(a.name, b.name)
  }
}

///|
/// 布局一个节点的子树。`x0`/`x1` 是它占据的水平区间。
fn layout_node(
  node : Node,
  x0 : Double,
  x1 : Double,
  depth : Int,
  row_height : Double,
  rects : Array[Rect],
) -> Unit {
  let span = x1 - x0
  rects.push({
    x: x0,
    y: depth.to_double() * row_height,
    width: span,
    height: row_height,
    name: node.name,
    value: node.value,
    depth,
    delta: node.delta,
  })
  if span <= 0.0 || node.value <= 0.0 {
    return
  }
  // 子项 = 真实子节点 + 自身耗时合成项。
  // 两者之和恒等于 node.value,因此下面的比例切分不会留下空洞。
  //
  // 只有在**存在子节点**时才补合成项:叶节点的整块宽度本来就是它自己的耗时,
  // 再套一层 `(self)` 只会白白加深一层、徒增矩形。
  let items : Array[Item] = []
  for child in node.children {
    items.push({ name: child.name, value: child.value, node: Some(child), })
  }
  let own = node.self_time()
  if own > 0.0 && items.length() > 0 {
    items.push({ name: self_placeholder(), value: own, node: None, })
  }
  // 自身耗时的变化量 = 本节点的变化量 − 子节点变化量之和。
  // 任一子节点没有变化量(非差异模式)时,合成项也不带变化量。
  let self_delta = match node.delta {
    Some(total) => {
      let mut sum = 0.0
      let mut complete = true
      for child in node.children {
        match child.delta {
          Some(value) => sum = sum + value
          None => complete = false
        }
      }
      if complete {
        Some(total - sum)
      } else {
        None
      }
    }
    None => None
  }
  items.sort_by(compare_items)
  let mut cursor = x0
  let mut index = 0
  for item in items {
    index = index + 1
    let next = if index == items.length() {
      // 最后一个子项直接顶到父节点右边界,消除浮点累加误差
      x1
    } else {
      cursor + span * (item.value / node.value)
    }
    match item.node {
      Some(child) =>
        layout_node(child, cursor, next, depth + 1, row_height, rects)
      None =>
        rects.push({
          x: cursor,
          y: (depth + 1).to_double() * row_height,
          width: next - cursor,
          height: row_height,
          name: item.name,
          value: item.value,
          depth: depth + 1,
          delta: self_delta,
        })
    }
    cursor = next
  }
}

///|
/// 把调用树布局成矩形列表(前序遍历顺序:父节点先于子节点)。
///
/// 多个根节点时按各自权重水平切分画布。
///
/// `inverted` 选择纵向方向:
///
///   - **默认(false)火焰图**:根在**底部**,逐层向上生长。底部密、顶部疏,
///     高耗时路径表现为向上伸出的「火苗」——这是 Brendan Gregg 定义的经典形态。
///   - `inverted = true` 冰柱图(icicle):根在顶部,逐层向下。
///
/// 两种方向都只影响 y 坐标,水平切分完全一致。
pub fn layout(
  tree : CallTree,
  width : Double,
  row_height : Double,
  inverted? : Bool,
) -> Layout {
  let flip = match inverted {
    Some(value) => value
    None => false
  }
  let rects : Array[Rect] = []
  if width > 0.0 &&
    row_height > 0.0 &&
    tree.total > 0.0 &&
    tree.roots.length() > 0 {
    let mut cursor = 0.0
    let mut index = 0
    for root in tree.roots {
      index = index + 1
      let next = if index == tree.roots.length() {
        width
      } else {
        cursor + width * (root.value / tree.total)
      }
      layout_node(root, cursor, next, 0, row_height, rects)
      cursor = next
    }
  }
  let mut max_depth = 0
  for rect in rects {
    if rect.depth > max_depth {
      max_depth = rect.depth
    }
  }
  // 方向直到这里才能确定:火焰图的根在底部,y 取决于整棵树的最大深度
  if !flip {
    for rect in rects {
      rect.y = (max_depth - rect.depth).to_double() * row_height
    }
  }
  // 差异配色需要一个统一的量级做归一化,否则一处剧变就会让其它差别看不出来
  let mut max_delta = 0.0
  for rect in rects {
    match rect.delta {
      Some(value) => {
        let magnitude = if value < 0.0 { -value } else { value }
        if magnitude > max_delta {
          max_delta = magnitude
        }
      }
      None => ()
    }
  }
  {
    rects,
    width,
    row_height,
    max_depth,
    total: tree.total,
    max_delta,
    inverted: flip,
  }
}

///|
/// 需要绘制的行数(= 最深深度 + 1)。
pub fn Layout::row_count(self : Layout) -> Int {
  if self.rects.length() == 0 {
    0
  } else {
    self.max_depth + 1
  }
}

///|
/// 画布所需高度。
pub fn Layout::height(self : Layout) -> Double {
  self.row_count().to_double() * self.row_height
}