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