]> git.lizzy.rs Git - go-anidb.git/blob - misc/episodelist.go
misc: (*EpisodeList).Sub: allow subtracting arbitrary containers
[go-anidb.git] / misc / episodelist.go
1 package misc
2
3 import (
4         "fmt"
5         "sort"
6         "strings"
7 )
8
9 type EpisodeCount struct {
10         RegularCount int
11         SpecialCount int
12         CreditsCount int
13         OtherCount   int
14         TrailerCount int
15         ParodyCount  int
16 }
17
18 type EpisodeList []*EpisodeRange
19
20 func EpisodeToList(ep *Episode) EpisodeList {
21         return RangesToList(EpisodeToRange(ep))
22 }
23
24 func RangesToList(ranges ...*EpisodeRange) EpisodeList {
25         return EpisodeList(ranges)
26 }
27
28 func ContainerToList(ec EpisodeContainer) EpisodeList {
29         switch v := ec.(type) {
30         case *Episode:
31                 return EpisodeToList(v)
32         case *EpisodeRange:
33                 return RangesToList(v)
34         case EpisodeList:
35                 return v
36         default:
37                 panic("unimplemented")
38         }
39 }
40
41 // Converts the EpisodeList into the AniDB API list format.
42 func (el EpisodeList) String() string {
43         scales := map[EpisodeType]int{}
44
45         for _, er := range el {
46                 if er == nil {
47                         continue
48                 }
49
50                 s := er.scale()
51                 if s > scales[er.Type] {
52                         scales[er.Type] = s
53                 }
54         }
55
56         parts := make([]string, len(el))
57         for i, er := range el {
58                 parts[i] = er.Format(scales[er.Type])
59         }
60
61         return strings.Join(parts, ",")
62 }
63
64 // Formats the list according to the number of digits of
65 // the count for its type, given in the EpisodeCount.
66 func (el EpisodeList) FormatLog(ec EpisodeCount) string {
67         parts := make([]string, len(el))
68         for i, er := range el {
69                 switch er.Type {
70                 case EpisodeTypeRegular:
71                         parts[i] = er.FormatLog(ec.RegularCount)
72                 case EpisodeTypeSpecial:
73                         parts[i] = er.FormatLog(ec.SpecialCount)
74                 case EpisodeTypeCredits:
75                         parts[i] = er.FormatLog(ec.CreditsCount)
76                 case EpisodeTypeOther:
77                         parts[i] = er.FormatLog(ec.OtherCount)
78                 case EpisodeTypeTrailer:
79                         parts[i] = er.FormatLog(ec.TrailerCount)
80                 case EpisodeTypeParody:
81                         parts[i] = er.FormatLog(ec.ParodyCount)
82                 default:
83                         parts[i] = er.Format(er.scale())
84                 }
85         }
86
87         return strings.Join(parts, ",")
88 }
89
90 func (el EpisodeList) Infinite() bool {
91         for i := range el {
92                 if el[i].Infinite() {
93                         return true
94                 }
95         }
96         return false
97 }
98
99 // Returns a channel that can be used to iterate using for/range.
100 //
101 // If the EpisodeList is infinite, then the channel is also infinite.
102 // The caller is allowed to close the channel in such case.
103 //
104 // NOTE: Not thread safe.
105 func (el EpisodeList) Episodes() chan Episode {
106         ch := make(chan Episode, 1)
107
108         go func() {
109                 abort := false
110
111                 if el.Infinite() {
112                         defer func() { recover(); abort = true }()
113                 } else {
114                         defer close(ch)
115                 }
116
117                 for _, er := range el {
118                         for ep := range er.Episodes() {
119                                 ch <- ep
120
121                                 if abort {
122                                         return
123                                 }
124                         }
125                 }
126         }()
127         return ch
128 }
129
130 // Returns true if any of the contained EpisodeRanges contain the
131 // given EpisodeContainer.
132 func (el EpisodeList) ContainsEpisodes(ec EpisodeContainer) bool {
133         for _, i := range el {
134                 if i != nil && i.ContainsEpisodes(ec) {
135                         return true
136                 }
137         }
138         return false
139 }
140
141 // Parses a string in the AniDB API list format and converts into
142 // an EpisodeList.
143 //
144 //      ParseEpisodeList("01")       <=> EpisodeList{ParseEpisodeRange("01")}
145 //      ParseEpisodeList("S2-S3")    <=> EpisodeList{ParseEpisodeRange("S2-S3")}
146 //      ParseEpisodeList("T1,C1-C3") <=> EpisodeList{ParseEpisodeRange("T1"), ParseEpisodeRange("C1-C3")}
147 func ParseEpisodeList(s string) (el EpisodeList) {
148         parts := strings.Split(s, ",")
149
150         el = make(EpisodeList, len(parts))
151         for i := range parts {
152                 el[i] = ParseEpisodeRange(parts[i])
153         }
154
155         return el.Simplify()
156 }
157
158 // Returns a simplified version of the EpisodeList (removes nil ranges, merges mergeable ranges, sorts).
159 func (el EpisodeList) Simplify() EpisodeList {
160         nl := make(EpisodeList, 0, len(el))
161
162         // drop nil ranges
163         for _, er := range el {
164                 if er != nil {
165                         nl = append(nl, er)
166                 }
167         }
168
169         // merge ranges
170         for n, changed := 0, true; changed; n++ {
171                 tmp := EpisodeList{}
172                 used := map[int]bool{}
173                 changed = false
174
175                 for i, a := range nl {
176                         if used[i] {
177                                 continue
178                         }
179                         for j, b := range nl[i+1:] {
180                                 if c := a.Merge(b); c != nil {
181                                         changed = true
182                                         used[j+i+1] = true
183                                         a = c
184                                 }
185                         }
186                         tmp = append(tmp, a)
187                 }
188                 nl = tmp
189
190                 if n > len(el) {
191                         panic(fmt.Sprintf("Too many iterations (%d) when simplifing %s!", n, el))
192                 }
193         }
194         sort.Sort(nl)
195         return nl
196 }
197
198 func (el EpisodeList) CountEpisodes() (ec EpisodeCount) {
199         for _, er := range el {
200                 var c *int
201                 switch er.Type {
202                 case EpisodeTypeRegular:
203                         c = &ec.RegularCount
204                 case EpisodeTypeSpecial:
205                         c = &ec.SpecialCount
206                 case EpisodeTypeCredits:
207                         c = &ec.CreditsCount
208                 case EpisodeTypeOther:
209                         c = &ec.OtherCount
210                 case EpisodeTypeTrailer:
211                         c = &ec.TrailerCount
212                 case EpisodeTypeParody:
213                         c = &ec.ParodyCount
214                 default:
215                         continue
216                 }
217                 if *c < 0 {
218                         continue
219                 }
220                 if er.End == nil {
221                         *c = -1
222                         continue
223                 }
224                 *c += er.End.Number - er.Start.Number
225         }
226         return
227 }
228
229 func (el EpisodeList) Len() int {
230         return len(el)
231 }
232
233 func (el EpisodeList) Less(i, j int) bool {
234         switch {
235         case el[i] == nil:
236                 return true
237         case el[j] == nil:
238                 return false
239         case el[i].Type < el[j].Type:
240                 return true
241         case el[i].Type > el[j].Type:
242                 return false
243         case el[i].Start.Number < el[j].Start.Number:
244                 return true
245         }
246         return false
247 }
248
249 func (el EpisodeList) Swap(i, j int) {
250         el[i], el[j] = el[j], el[i]
251 }
252
253 func (el *EpisodeList) Add(ec EpisodeContainer) {
254         *el = append(*el, ContainerToList(ec)...)
255         *el = el.Simplify()
256 }
257
258 func (el *EpisodeList) Sub(ec EpisodeContainer) {
259         el2 := make(EpisodeList, 0, len(*el)*2)
260         switch e, ok := ec.(canInfinite); {
261         case ok:
262                 if e.Infinite() {
263                         eCh := e.Episodes()
264                         ep := <-eCh
265                         close(eCh)
266
267                         for _, r := range *el {
268                                 el2 = append(el2, r.Split(&ep)[0])
269                         }
270                         break
271                 }
272                 fallthrough
273         default:
274                 for ep := range ec.Episodes() {
275                         for _, r := range *el {
276                                 el2 = append(el2, r.Split(&ep)...)
277                         }
278                         el2 = el2.Simplify()
279                 }
280         }
281         *el = append(*el, el2.Simplify()...)
282 }