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