]> git.lizzy.rs Git - go-anidb.git/blob - misc/episoderange.go
misc: Support partial episodes
[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 // Converts the EpisodeRange into AniDB API range format.
16 func (er *EpisodeRange) String() string {
17         return er.Format(er.scale())
18 }
19
20 func (er *EpisodeRange) Format(width int) string {
21         if er.Start == er.End || (er.End != nil && *(er.Start) == *(er.End)) {
22                 return er.Start.Format(width)
23         }
24
25         if er.End == nil {
26                 return fmt.Sprintf("%s-", er.Start.Format(width))
27         }
28         return fmt.Sprintf("%s-%s", er.Start.Format(width), er.End.Format(width))
29 }
30
31 func (er *EpisodeRange) FormatLog(max int) string {
32         return er.Format(scale(max))
33 }
34
35 func (er *EpisodeRange) scale() int {
36         if er == nil {
37                 return 1
38         }
39         s, e := er.Start.scale(), er.End.scale()
40         if e > s {
41                 return e
42         }
43         return s
44 }
45
46 // If ec is an *Episode, returns true if the Episode is of the same type as the range
47 // and has a Number >= Start.Number; if End is defined, then the episode's Number must
48 // also be <= End.Number.
49 //
50 // If ec is an *EpisodeRange, returns true if they are both of the same type and
51 // the ec's Start.Number is >= this range's Start.Number;
52 // also returns true if this EpisodeRange is unbounded or if the ec is bounded
53 // and ec's End.Number is <= this range's End.Number.
54 //
55 // If ec is an EpisodeList, returns true if all listed EpisodeRanges are contained
56 // by this EpisodeRange.
57 //
58 // Returns false otherwise.
59 func (er *EpisodeRange) ContainsEpisodes(ec EpisodeContainer) bool {
60         if er == nil {
61                 return false
62         }
63         if er.Start == nil || er.Start.Type != er.Type ||
64                 (er.End != nil && er.End.Type != er.Type) {
65                 panic("Invalid EpisodeRange used")
66         }
67
68         switch e := ec.(type) {
69         case *Episode:
70                 if e.Type == er.Type && e.Number >= er.Start.Number {
71                         if er.End == nil {
72                                 return true
73                         } else if e.Number <= er.End.Number {
74                                 return true
75                         }
76                 }
77         case *EpisodeRange:
78                 if e.Type == er.Type {
79                         if e.Start.Number >= er.Start.Number {
80                                 if er.End == nil {
81                                         return true
82                                 } else if e.End == nil {
83                                         return false // a finite set can't contain an infinite one
84                                 } else if e.End.Number <= er.End.Number {
85                                         return true
86                                 }
87                         }
88                 }
89         case EpisodeList:
90                 for _, ec := range e {
91                         if !er.ContainsEpisodes(ec) {
92                                 return false
93                         }
94                 }
95                 return true
96         default:
97         }
98         return false
99 }
100
101 // Tries to merge a with b, returning a new *EpisodeRange that's
102 // a superset of both a and b.
103 //
104 // Returns nil if a and b don't intersect, or are not adjacent.
105 func (a *EpisodeRange) Merge(b *EpisodeRange) (c *EpisodeRange) {
106         if a.touches(b) {
107                 c = &EpisodeRange{Type: a.Type}
108
109                 if a.Start.Number == b.Start.Number {
110                         if a.Start.Part <= b.Start.Part {
111                                 c.Start = a.Start
112                         } else {
113                                 c.Start = b.Start
114                         }
115                 } else if a.Start.Number < b.Start.Number {
116                         c.Start = a.Start
117                 } else {
118                         c.Start = b.Start
119                 }
120
121                 switch {
122                 case a.End == nil || b.End == nil:
123                         c.End = nil
124                 case a.End.Number == b.End.Number:
125                         if a.End.Part >= b.End.Part {
126                                 c.End = a.End
127                         } else {
128                                 c.End = b.End
129                         }
130                 case a.End.Number > b.End.Number:
131                         c.End = a.End
132                 default:
133                         c.End = b.End
134                 }
135         }
136         return
137 }
138
139 // Returns true if both ranges are of the same type and
140 // have identical start/end positions
141 func (a *EpisodeRange) Equals(b *EpisodeRange) bool {
142         if a == b { // pointers to the same thing
143                 return true
144         }
145         if a == nil || b == nil {
146                 return false
147         }
148
149         if a.Type == b.Type {
150                 if a.End == b.End || (a.End != nil && b.End != nil &&
151                         a.End.Number == b.End.Number && a.End.Part == b.End.Part) {
152                         if a.Start == b.Start || a.Start.Number == b.Start.Number && a.Start.Part == b.Start.Part {
153                                 return true
154                         }
155                 }
156         }
157         return false
158 }
159
160 // CORNER CASE: e.g. 1.3,2.0 (or 1.3,2) always touch,
161 // even if there's an unlisted 1.4 between them; unless
162 // the part count is known.
163 func (a *EpisodeRange) touches(b *EpisodeRange) bool {
164         if a == nil || b == nil || a.Type != b.Type {
165                 return false
166         }
167
168         switch {
169         case a == b:
170                 // log.Println("same pointers")
171                 return true
172         case a.Start == b.Start, a.End != nil && a.End == b.End:
173                 // log.Println("share pointers")
174                 return true
175
176         case a.End == nil:
177                 switch {
178                 case b.End == nil:
179                         // log.Println("both infinite")
180                         return true
181
182                 case b.End.Number == a.Start.Number:
183                         switch {
184                         // either is whole, or parts are adjacent/overlap
185                         case b.End.Part == -1, a.Start.Part == -1,
186                                 b.End.Part >= a.Start.Part-1:
187                                 // log.Printf("{ %s [} %s ...", b.End, a.Start)
188                                 return true
189                         }
190                 // only if start of next range is whole or is first part
191                 case b.End.Number == a.Start.Number-1 && a.Start.Part <= 0:
192                         switch {
193                         // end is whole, or is last part, or part count is unknown
194                         case b.End.Part == -1, b.End.Parts == 0,
195                                 b.End.Part == b.End.Parts:
196                                 // log.Printf("{ %s }[ %s ...", b.End, a.Start)
197                                 return true
198                         }
199                 case b.End.Number > a.Start.Number:
200                         // log.Printf("{ %s [ } %s ...", b.End, a.Start)
201                         return true
202                 }
203
204         case b.End == nil:
205                 switch {
206                 case a.End.Number == b.Start.Number:
207                         switch {
208                         case a.End.Part == -1, b.Start.Part == -1,
209                                 a.End.Part >= b.Start.Part-1:
210                                 // log.Printf("[ %s {] %s ...", a.End, b.Start)
211                                 return true
212                         }
213                 case a.End.Number == b.Start.Number-1 && b.Start.Part <= 0:
214                         switch {
215                         case a.End.Part == -1, a.End.Parts == 0,
216                                 a.End.Part == a.End.Parts:
217                                 // log.Printf("[ %s ]{ %s ...", a.End, b.Start)
218                                 return true
219                         }
220                 case a.End.Number > b.Start.Number:
221                         // log.Printf("[ %s { ] %s ...", a.End, b.Start)
222                         return true
223                 }
224
225         case a.Start.Number == b.Start.Number:
226                 // touching
227                 switch {
228                 // either is whole, or parts are immediately adjacent
229                 case a.Start.Part == -1, b.Start.Part == -1,
230                         a.Start.Part == b.Start.Part,
231                         a.Start.Part == b.Start.Part-1,
232                         a.Start.Part == b.Start.Part+1:
233                         // log.Printf("[{ %s - %s ]}", a.End, b.Start)
234                         return true
235                 }
236         case a.End.Number == b.End.Number:
237                 switch {
238                 case a.End.Part == -1, b.End.Part == -1,
239                         a.End.Part == b.End.Part,
240                         a.End.Part == b.End.Part-1,
241                         a.End.Part == b.End.Part+1:
242                         // log.Printf("{[ %s - %s }]", b.End, a.Start)
243                         return true
244                 }
245
246         case a.End.Number < b.End.Number:
247                 switch {
248                 case a.End.Number == b.Start.Number:
249                         switch {
250                         case a.End.Part == -1, b.Start.Part == -1,
251                                 a.End.Part >= b.Start.Part-1:
252                                 // log.Printf("[ %s {] %s }", a.End, b.Start)
253                                 return true
254                         }
255                 case a.End.Number == b.Start.Number-1 && b.Start.Part <= 0:
256                         switch {
257                         case b.End.Part == -1, b.End.Parts == 0,
258                                 b.End.Part == b.End.Parts:
259                                 // log.Printf("[ %s ]{ %s }", a.End, b.Start)
260                                 return true
261                         }
262                 case a.End.Number > b.Start.Number:
263                         // log.Printf("[ %s { ] %s }", a.End, b.Start)
264                         return true
265                 }
266
267         case b.End.Number < a.End.Number:
268                 switch {
269                 case b.End.Number == a.Start.Number:
270                         switch {
271                         case b.End.Part == -1, a.Start.Part == -1,
272                                 b.End.Part >= a.Start.Part-1:
273                                 // log.Printf("{ %s [} %s ]", b.End, a.Start)
274                                 return true
275                         }
276                 case b.End.Number == a.Start.Number-1 && a.Start.Part <= 0:
277                         switch {
278                         case b.End.Part == -1, b.End.Parts == 0,
279                                 b.End.Part == b.End.Parts:
280                                 // log.Printf("{ %s }[ %s ]", b.End, a.Start)
281                                 return true
282                         }
283                 case b.End.Number > a.Start.Number:
284                         // log.Printf("{ %s [ } %s ]", b.End, a.Start)
285                         return true
286                 }
287         }
288         return false
289 }
290
291 // Parses a string in the AniDB API range format and converts into an EpisodeRange.
292 func ParseEpisodeRange(s string) *EpisodeRange {
293         parts := strings.Split(s, "-")
294         if len(parts) > 2 {
295                 return nil
296         }
297
298         eps := [2]*Episode{}
299         for i := range parts {
300                 eps[i] = ParseEpisode(parts[i])
301         }
302         if eps[0] == nil {
303                 return nil
304         }
305
306         // Not an interval (just "epno") --
307         // convert into interval starting and ending in the same episode
308         if len(parts) == 1 {
309                 eps[1] = eps[0]
310         }
311
312         if len(parts) > 1 && eps[1] != nil && eps[0].Type != eps[1].Type {
313                 return nil
314         }
315         return &EpisodeRange{
316                 Type:  eps[0].Type,
317                 Start: eps[0],
318                 End:   eps[1],
319         }
320 }