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