1package songlist
2
3import (
4	"github.com/ambientsound/pms/song"
5	"github.com/ambientsound/pms/utils"
6)
7
8type Column struct {
9	tag        string
10	items      int
11	totalWidth int
12	maxWidth   int
13	avg        int
14	width      int
15}
16
17type Columns []*Column
18
19type ColumnMap map[string]*Column
20
21// NewColumn returns a new Column.
22func NewColumn(tag string) *Column {
23	return &Column{tag: tag}
24}
25
26// Set calculates all song's widths.
27func (c *Column) Set(s Songlist) {
28	c.Reset()
29	for _, song := range s.Songs() {
30		c.Add(song)
31	}
32}
33
34// Add a single song's width to the total and maximum width.
35func (c *Column) Add(song *song.Song) {
36	l := len(song.Tags[c.tag])
37	if l == 0 {
38		return
39	}
40	c.avg = 0
41	c.items++
42	c.totalWidth += l
43	c.maxWidth = utils.Max(c.maxWidth, l)
44}
45
46// Remove a single song's tag width from the total and maximum width.
47func (c *Column) Remove(song *song.Song) {
48	l := len(song.Tags[c.tag])
49	if l == 0 {
50		return
51	}
52	c.avg = 0
53	c.items--
54	c.totalWidth -= l
55	// TODO: c.maxWidth is not updated
56}
57
58// Reset sets all values to zero.
59func (c *Column) Reset() {
60	c.items = 0
61	c.totalWidth = 0
62	c.maxWidth = 0
63	c.avg = 0
64	c.width = 0
65}
66
67// Weight returns the relative usefulness of this column. It might happen that
68// a tag appears rarely, but is very long. In this case we reduce the field so
69// that other tags get more space.
70func (c *Column) Weight(max int) float64 {
71	return float64(c.items) / float64(max)
72}
73
74// Avg returns the average length of the tag values in this column.
75func (c *Column) Avg() int {
76	if c.avg == 0 {
77		if c.items == 0 {
78			c.avg = 0
79		} else {
80			c.avg = c.totalWidth / c.items
81		}
82	}
83	//console.Log("Avg() of %s is %d", c.Tag(), c.avg)
84	return c.avg
85}
86
87// Tag returns the tag name.
88func (c *Column) Tag() string {
89	return c.tag
90}
91
92// MaxWidth returns the length of the longest tag value in this column.
93func (c *Column) MaxWidth() int {
94	return c.maxWidth
95}
96
97// Width returns the column width.
98func (c *Column) Width() int {
99	return c.width
100}
101
102// SetWidth sets the width that the column should consume.
103func (c *Column) SetWidth(width int) {
104	c.width = width
105}
106
107// expand adjusts the column widths equally between the different columns,
108// giving affinity to weight.
109func (columns Columns) Expand(totalWidth int) {
110	if len(columns) == 0 {
111		return
112	}
113
114	usedWidth := 0
115	poolSize := len(columns)
116	saturated := make([]bool, poolSize)
117
118	// Start with the average value
119	for i := range columns {
120		avg := columns[i].Avg()
121		columns[i].SetWidth(avg)
122		usedWidth += avg
123	}
124
125	// expand as long as there is space left
126	for {
127		for i := range columns {
128			if usedWidth > totalWidth {
129				return
130			}
131			if poolSize > 0 && saturated[i] {
132				continue
133			}
134			col := columns[i]
135			if poolSize > 0 && col.Width() > col.MaxWidth() {
136				saturated[i] = true
137				poolSize--
138				continue
139			}
140			col.SetWidth(col.Width() + 1)
141			usedWidth++
142		}
143	}
144}
145
146// Add adds song tags to all applicable columns.
147func (c ColumnMap) Add(song *song.Song) {
148	for tag := range song.StringTags {
149		c[tag].Add(song)
150	}
151}
152
153// Remove removes song tags from all applicable columns.
154func (c ColumnMap) Remove(song *song.Song) {
155	for tag := range song.StringTags {
156		c[tag].Remove(song)
157	}
158}
159
160// ensureColumns makes sure that all of a song's tags exists in the column map.
161func (s *BaseSonglist) ensureColumns(song *song.Song) {
162	for tag := range song.StringTags {
163		if _, ok := s.columns[tag]; !ok {
164			s.columns[tag] = NewColumn(tag)
165		}
166	}
167}
168
169// Columns returns a slice of columns, containing only the columns which has
170// the specified tags.
171func (s *BaseSonglist) Columns(columns []string) Columns {
172	cols := make(Columns, 0)
173	for _, tag := range columns {
174		col := s.columns[tag]
175		if col == nil {
176			col = NewColumn(tag)
177		}
178		cols = append(cols, col)
179	}
180	return cols
181}
182