1'use strict'
2const TrackerBase = require('./tracker-base.js')
3const Tracker = require('./tracker.js')
4const TrackerStream = require('./tracker-stream.js')
5
6class TrackerGroup extends TrackerBase {
7  parentGroup = null
8  trackers = []
9  completion = {}
10  weight = {}
11  totalWeight = 0
12  finished = false
13  bubbleChange = bubbleChange(this)
14
15  nameInTree () {
16    var names = []
17    var from = this
18    while (from) {
19      names.unshift(from.name)
20      from = from.parentGroup
21    }
22    return names.join('/')
23  }
24
25  addUnit (unit, weight) {
26    if (unit.addUnit) {
27      var toTest = this
28      while (toTest) {
29        if (unit === toTest) {
30          throw new Error(
31            'Attempted to add tracker group ' +
32            unit.name + ' to tree that already includes it ' +
33            this.nameInTree(this))
34        }
35        toTest = toTest.parentGroup
36      }
37      unit.parentGroup = this
38    }
39    this.weight[unit.id] = weight || 1
40    this.totalWeight += this.weight[unit.id]
41    this.trackers.push(unit)
42    this.completion[unit.id] = unit.completed()
43    unit.on('change', this.bubbleChange)
44    if (!this.finished) {
45      this.emit('change', unit.name, this.completion[unit.id], unit)
46    }
47    return unit
48  }
49
50  completed () {
51    if (this.trackers.length === 0) {
52      return 0
53    }
54    var valPerWeight = 1 / this.totalWeight
55    var completed = 0
56    for (var ii = 0; ii < this.trackers.length; ii++) {
57      var trackerId = this.trackers[ii].id
58      completed +=
59        valPerWeight * this.weight[trackerId] * this.completion[trackerId]
60    }
61    return completed
62  }
63
64  newGroup (name, weight) {
65    return this.addUnit(new TrackerGroup(name), weight)
66  }
67
68  newItem (name, todo, weight) {
69    return this.addUnit(new Tracker(name, todo), weight)
70  }
71
72  newStream (name, todo, weight) {
73    return this.addUnit(new TrackerStream(name, todo), weight)
74  }
75
76  finish () {
77    this.finished = true
78    if (!this.trackers.length) {
79      this.addUnit(new Tracker(), 1, true)
80    }
81    for (var ii = 0; ii < this.trackers.length; ii++) {
82      var tracker = this.trackers[ii]
83      tracker.finish()
84      tracker.removeListener('change', this.bubbleChange)
85    }
86    this.emit('change', this.name, 1, this)
87  }
88
89  debug (depth = 0) {
90    const indent = ' '.repeat(depth)
91    let output = `${indent}${this.name || 'top'}: ${this.completed()}\n`
92
93    this.trackers.forEach(function (tracker) {
94      output += tracker instanceof TrackerGroup
95        ? tracker.debug(depth + 1)
96        : `${indent} ${tracker.name}: ${tracker.completed()}\n`
97    })
98    return output
99  }
100}
101
102function bubbleChange (trackerGroup) {
103  return function (name, completed, tracker) {
104    trackerGroup.completion[tracker.id] = completed
105    if (trackerGroup.finished) {
106      return
107    }
108    trackerGroup.emit('change', name || trackerGroup.name, trackerGroup.completed(), trackerGroup)
109  }
110}
111
112module.exports = TrackerGroup
113