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