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