]> git.lizzy.rs Git - go-anidb.git/blob - misc/episoderange.go
misc: Add an iterator to EpisodeRange
[go-anidb.git] / misc / episoderange.go
1 package misc
2
3 import (
4         "fmt"
5         "strings"
6 )
7
8 // A range of episodes with a start and possibly without an end.
9 type EpisodeRange struct {
10         Type  EpisodeType // Must be equal to both the Start and End types; if End is nil, must be equal to the Start type
11         Start *Episode    // The start of the range
12         End   *Episode    // The end of the range; may be nil, which represents an endless range
13 }
14
15 func EpisodeToRange(ep *Episode) *EpisodeRange {
16         return &EpisodeRange{
17                 Type:  ep.Type,
18                 Start: ep,
19                 End:   ep,
20         }
21 }
22
23 // Converts the EpisodeRange into AniDB API range format.
24 func (er *EpisodeRange) String() string {
25         return er.Format(er.scale())
26 }
27
28 func (er *EpisodeRange) Format(width int) string {
29         if er.Start == er.End || (er.End != nil && *(er.Start) == *(er.End)) {
30                 return er.Start.Format(width)
31         }
32
33         if er.End == nil {
34                 return fmt.Sprintf("%s-", er.Start.Format(width))
35         }
36         return fmt.Sprintf("%s-%s", er.Start.Format(width), er.End.Format(width))
37 }
38
39 func (er *EpisodeRange) FormatLog(max int) string {
40         return er.Format(scale(max))
41 }
42
43 func (er *EpisodeRange) scale() int {
44         if er == nil {
45                 return 1
46         }
47         s, e := er.Start.scale(), er.End.scale()
48         if e > s {
49                 return e
50         }
51         return s
52 }
53
54 func (er *EpisodeRange) Infinite() bool {
55         return er != nil && er.End == nil
56 }
57
58 // Returns a channel that can be used to iterate using for/range.
59 //
60 // If the EpisodeRange is infinite, then the channel is also infinite.
61 // The caller is allowed to close the channel in such case.
62 func (er *EpisodeRange) Episodes() chan Episode {
63         ch := make(chan Episode, 1)
64         if er == nil || er.Start == nil {
65                 close(ch)
66                 return ch
67         }
68
69         start := *er.Start
70         inf := er.Infinite()
71         end := Episode{}
72         if !inf {
73                 end = *er.End
74         }
75
76         go func() {
77                 abort := false
78
79                 if inf {
80                         // we allow the caller to close the channel on infinite lists
81                         defer func() { recover(); abort = true }()
82                 } else {
83                         defer close(ch)
84                 }
85
86                 ep := start
87
88                 switch {
89                 case inf:
90                         for ; !abort && ep.Parts > 0 && ep.Number == start.Number; ep.IncPart() {
91                                 ch <- ep
92                         }
93                         for ; !abort; ep.IncNumber() {
94                                 ch <- ep
95                         }
96                 case start.Part == -1 && end.Part == -1:
97                         for ; ep.Number <= end.Number; ep.IncNumber() {
98                                 ch <- ep
99                         }
100                 case start.Parts > 0:
101                         for ; ep.Number == start.Number; ep.IncPart() {
102                                 ch <- ep
103                         }
104                         fallthrough
105                 default:
106                         for ; ep.Number < end.Number; ep.IncNumber() {
107                                 ch <- ep
108                         }
109                         if end.Part >= 0 {
110                                 ep.Part = 0
111                         }
112                         for ; ep.Part <= end.Part; ep.IncPart() {
113                                 ch <- ep
114                         }
115                 }
116         }()
117         return ch
118 }
119
120 // If ec is an *Episode, returns true if the Episode is of the same type as the range
121 // and has a Number >= Start.Number; if End is defined, then the episode's Number must
122 // also be <= End.Number.
123 //
124 // If ec is an *EpisodeRange, returns true if they are both of the same type and
125 // the ec's Start.Number is >= this range's Start.Number;
126 // also returns true if this EpisodeRange is unbounded or if the ec is bounded
127 // and ec's End.Number is <= this range's End.Number.
128 //
129 // If ec is an EpisodeList, returns true if all listed EpisodeRanges are contained
130 // by this EpisodeRange.
131 //
132 // Returns false otherwise.
133 func (er *EpisodeRange) ContainsEpisodes(ec EpisodeContainer) bool {
134         if er == nil {
135                 return false
136         }
137         if er.Start == nil || er.Start.Type != er.Type ||
138                 (er.End != nil && er.End.Type != er.Type) {
139                 panic("Invalid EpisodeRange used")
140         }
141
142         switch e := ec.(type) {
143         case *Episode:
144                 if e.Type == er.Type && e.Number >= er.Start.Number {
145                         if er.End == nil {
146                                 return true
147                         } else if e.Number <= er.End.Number {
148                                 return true
149                         }
150                 }
151         case *EpisodeRange:
152                 if e.Type == er.Type {
153                         if e.Start.Number >= er.Start.Number {
154                                 if er.End == nil {
155                                         return true
156                                 } else if e.End == nil {
157                                         return false // a finite set can't contain an infinite one
158                                 } else if e.End.Number <= er.End.Number {
159                                         return true
160                                 }
161                         }
162                 }
163         case EpisodeList:
164                 for _, ec := range e {
165                         if !er.ContainsEpisodes(ec) {
166                                 return false
167                         }
168                 }
169                 return true
170         default:
171         }
172         return false
173 }
174
175 // Tries to merge a with b, returning a new *EpisodeRange that's
176 // a superset of both a and b.
177 //
178 // Returns nil if a and b don't intersect, or are not adjacent.
179 func (a *EpisodeRange) Merge(b *EpisodeRange) (c *EpisodeRange) {
180         if a.touches(b) {
181                 c = &EpisodeRange{Type: a.Type}
182
183                 switch {
184                 case a.Start.Number == b.Start.Number:
185                         switch {
186                         case a.Start.Part < 0:
187                                 c.Start = a.Start
188                         case b.Start.Part < 0:
189                                 c.Start = b.Start
190                         case a.Start.Part <= b.Start.Part:
191                                 c.Start = a.Start
192                         default:
193                                 c.Start = b.Start
194                         }
195                 case a.Start.Number < b.Start.Number:
196                         c.Start = a.Start
197                 default:
198                         c.Start = b.Start
199                 }
200
201                 switch {
202                 case a.End == nil || b.End == nil:
203                         c.End = nil
204                 case a.End.Number == b.End.Number:
205                         switch {
206                         case a.End.Part < 0:
207                                 c.End = a.End
208                         case b.End.Part < 0:
209                                 c.End = b.End
210                         case a.End.Part >= b.End.Part:
211                                 c.End = a.End
212                         default:
213                                 c.End = b.End
214                         }
215                 case a.End.Number > b.End.Number:
216                         c.End = a.End
217                 default:
218                         c.End = b.End
219                 }
220         }
221         return
222 }
223
224 // Check if the given range is not nil, has a defined start
225 // and, if it has an end, that the end ends after the start.
226 func (er *EpisodeRange) Valid() bool {
227         switch {
228         case er == nil, er.Start == nil:
229                 return false
230         case er.End == nil:
231                 return true
232         case er.Start.Number < er.End.Number:
233                 return true
234         case er.Start.Number > er.End.Number:
235                 return false
236         case er.Start.Part <= er.End.Part:
237                 return true
238         default:
239                 return false
240         }
241 }
242
243 // Simplifies the Start/End ranges if one contains the other.
244 // Sets the pointers to be identical if the range is modified.
245 //
246 // Modifies in-place, returns itself.
247 func (er *EpisodeRange) Simplify() *EpisodeRange {
248         switch {
249         case er.Start.ContainsEpisodes(er.End):
250                 er.End = er.Start
251         case er.End != nil && er.End.ContainsEpisodes(er.Start):
252                 er.Start = er.End
253         }
254         return er
255 }
256
257 // Splits the range into one or two ranges, using the given
258 // Episode as the split point. The Episode is not included in
259 // the resulting ranges.
260 func (er *EpisodeRange) Split(ep *Episode) []*EpisodeRange {
261         if !er.ContainsEpisodes(ep) { // implies same type
262                 return []*EpisodeRange{er}
263         }
264         if !er.Valid() {
265                 return []*EpisodeRange{nil, nil}
266         }
267
268         a := *er.Start
269
270         inf := er.End == nil
271         b := Episode{}
272         if !inf {
273                 b = *er.End
274         }
275
276         end := &b
277         if inf {
278                 end = nil
279         }
280
281         switch {
282         case a.ContainsEpisodes(ep) && b.ContainsEpisodes(ep):
283                 return []*EpisodeRange{nil, nil}
284         case a.ContainsEpisodes(ep):
285                 if ep.Part >= 0 {
286                         a.IncPart()
287                 } else {
288                         a.IncNumber()
289                 }
290                 if a.Number == b.Number && b.Parts > 0 {
291                         a.Parts = b.Parts
292                 }
293
294                 r := &EpisodeRange{
295                         Type:  er.Type,
296                         Start: &a,
297                         End:   end,
298                 }
299                 return []*EpisodeRange{nil, r.Simplify()}
300         case b.ContainsEpisodes(ep):
301                 if ep.Part >= 0 {
302                         b.DecPart()
303                 } else {
304                         b.DecNumber()
305                 }
306                 if b.Number == a.Number {
307                         if a.Parts > 0 {
308                                 b.Parts = a.Parts
309                                 b.Part = a.Parts - 1
310                         } else if b.Part < 0 {
311                                 b.Part = a.Part
312                         }
313                 }
314                 r := &EpisodeRange{
315                         Type:  er.Type,
316                         Start: &a,
317                         End:   &b,
318                 }
319                 return []*EpisodeRange{r.Simplify(), nil}
320         default:
321                 ra := &EpisodeRange{
322                         Type:  er.Type,
323                         Start: &a,
324                         End:   ep,
325                 }
326                 rb := &EpisodeRange{
327                         Type:  er.Type,
328                         Start: ep,
329                         End:   end,
330                 }
331
332                 ra = ra.Split(ep)[0]
333                 rb = rb.Split(ep)[1]
334
335                 if ra.Valid() {
336                         ra.Simplify()
337                 } else {
338                         ra = nil
339                 }
340                 if rb.Valid() {
341                         rb.Simplify()
342                 } else {
343                         rb = nil
344                 }
345
346                 return []*EpisodeRange{ra, rb}
347         }
348 }
349
350 // Returns true if both ranges are of the same type and
351 // have identical start/end positions
352 func (a *EpisodeRange) Equals(b *EpisodeRange) bool {
353         if a == b { // pointers to the same thing
354                 return true
355         }
356         if a == nil || b == nil {
357                 return false
358         }
359
360         if a.Type == b.Type {
361                 if a.End == b.End || (a.End != nil && b.End != nil &&
362                         a.End.Number == b.End.Number && a.End.Part == b.End.Part) {
363                         if a.Start == b.Start || a.Start.Number == b.Start.Number && a.Start.Part == b.Start.Part {
364                                 return true
365                         }
366                 }
367         }
368         return false
369 }
370
371 // CORNER CASE: e.g. 1.3,2.0 (or 1.3,2) never touch,
372 // unless it's known that 1.3 is the last part.
373 func (a *EpisodeRange) touches(b *EpisodeRange) bool {
374         if a == nil || b == nil || a.Type != b.Type {
375                 return false
376         }
377
378         switch {
379         case a == b:
380                 // log.Println("same pointers")
381                 return true
382         case a.Start == b.Start, a.End != nil && a.End == b.End:
383                 // log.Println("share pointers")
384                 return true
385
386         case a.End == nil:
387                 switch {
388                 case b.End == nil:
389                         // log.Println("both infinite")
390                         return true
391
392                 case b.End.Number == a.Start.Number:
393                         switch {
394                         // either is whole, or parts are adjacent/overlap
395                         case b.End.Part == -1, a.Start.Part == -1,
396                                 b.End.Part >= a.Start.Part-1:
397                                 // log.Printf("{ %s [} %s ...", b.End, a.Start)
398                                 return true
399                         }
400                 // only if start of next range is whole or is first part
401                 case b.End.Number == a.Start.Number-1 && a.Start.Part <= 0:
402                         switch {
403                         // end is whole, or is last part, or part count is unknown
404                         case b.End.Part == -1, b.End.Parts == 0,
405                                 b.End.Part == b.End.Parts:
406                                 // log.Printf("{ %s }[ %s ...", b.End, a.Start)
407                                 return true
408                         }
409                 case b.End.Number > a.Start.Number:
410                         // log.Printf("{ %s [ } %s ...", b.End, a.Start)
411                         return true
412                 }
413
414         case b.End == nil:
415                 switch {
416                 case a.End.Number == b.Start.Number:
417                         switch {
418                         case a.End.Part == -1, b.Start.Part == -1,
419                                 a.End.Part >= b.Start.Part-1:
420                                 // log.Printf("[ %s {] %s ...", a.End, b.Start)
421                                 return true
422                         }
423                 case a.End.Number == b.Start.Number-1 && b.Start.Part <= 0:
424                         switch {
425                         case a.End.Part == -1, a.End.Parts == 0,
426                                 a.End.Part == a.End.Parts:
427                                 // log.Printf("[ %s ]{ %s ...", a.End, b.Start)
428                                 return true
429                         }
430                 case a.End.Number > b.Start.Number:
431                         // log.Printf("[ %s { ] %s ...", a.End, b.Start)
432                         return true
433                 }
434
435         case a.Start.Number == b.Start.Number:
436                 // touching
437                 switch {
438                 // either is whole, or parts are immediately adjacent
439                 case a.Start.Part == -1, b.Start.Part == -1,
440                         a.Start.Part == b.Start.Part,
441                         a.Start.Part == b.Start.Part-1,
442                         a.Start.Part == b.Start.Part+1:
443                         // log.Printf("[{ %s - %s ]}", a.End, b.Start)
444                         return true
445                 }
446         case a.End.Number == b.End.Number:
447                 switch {
448                 case a.End.Part == -1, b.End.Part == -1,
449                         a.End.Part == b.End.Part,
450                         a.End.Part == b.End.Part-1,
451                         a.End.Part == b.End.Part+1:
452                         // log.Printf("{[ %s - %s }]", b.End, a.Start)
453                         return true
454                 }
455
456         case a.End.Number < b.End.Number:
457                 switch {
458                 case a.End.Number == b.Start.Number:
459                         switch {
460                         case a.End.Part == -1, b.Start.Part == -1,
461                                 a.End.Part >= b.Start.Part-1:
462                                 // log.Printf("[ %s {] %s }", a.End, b.Start)
463                                 return true
464                         }
465                 case a.End.Number == b.Start.Number-1 && b.Start.Part <= 0:
466                         switch {
467                         case a.End.Part == -1, a.End.Part == a.End.Parts-1,
468                                 a.End.Part == b.Start.Parts:
469                                 // log.Printf("[ %s ]{ %s }", a.End, b.Start)
470                                 return true
471                         }
472                 case a.End.Number > b.Start.Number:
473                         // log.Printf("[ %s { ] %s }", a.End, b.Start)
474                         return true
475                 }
476
477         case b.End.Number < a.End.Number:
478                 switch {
479                 case b.End.Number == a.Start.Number:
480                         switch {
481                         case b.End.Part == -1, a.Start.Part == -1,
482                                 b.End.Part >= a.Start.Part-1:
483                                 // log.Printf("{ %s [} %s ]", b.End, a.Start)
484                                 return true
485                         }
486                 case b.End.Number == a.Start.Number-1 && a.Start.Part <= 0:
487                         switch {
488                         case b.End.Part == -1, b.End.Part == b.End.Parts-1,
489                                 b.End.Part == b.End.Parts:
490                                 // log.Printf("{ %s }[ %s ]", b.End, a.Start)
491                                 return true
492                         }
493                 case b.End.Number > a.Start.Number:
494                         // log.Printf("{ %s [ } %s ]", b.End, a.Start)
495                         return true
496                 }
497         }
498         return false
499 }
500
501 // Parses a string in the AniDB API range format and converts into an EpisodeRange.
502 func ParseEpisodeRange(s string) *EpisodeRange {
503         parts := strings.Split(s, "-")
504         if len(parts) > 2 {
505                 return nil
506         }
507
508         eps := [2]*Episode{}
509         for i := range parts {
510                 eps[i] = ParseEpisode(parts[i])
511         }
512         if eps[0] == nil {
513                 return nil
514         }
515
516         // Not an interval (just "epno") --
517         // convert into interval starting and ending in the same episode
518         if len(parts) == 1 {
519                 eps[1] = eps[0]
520         }
521
522         if len(parts) > 1 && eps[1] != nil && eps[0].Type != eps[1].Type {
523                 return nil
524         }
525         return &EpisodeRange{
526                 Type:  eps[0].Type,
527                 Start: eps[0],
528                 End:   eps[1],
529         }
530 }