]> git.lizzy.rs Git - go-anidb.git/blob - misc/episoderange.go
misc: CountEpisodes and convenience conversion functions
[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 // Returns true if both ranges are of the same type and
148 // have identical start/end positions
149 func (a *EpisodeRange) Equals(b *EpisodeRange) bool {
150         if a == b { // pointers to the same thing
151                 return true
152         }
153         if a == nil || b == nil {
154                 return false
155         }
156
157         if a.Type == b.Type {
158                 if a.End == b.End || (a.End != nil && b.End != nil &&
159                         a.End.Number == b.End.Number && a.End.Part == b.End.Part) {
160                         if a.Start == b.Start || a.Start.Number == b.Start.Number && a.Start.Part == b.Start.Part {
161                                 return true
162                         }
163                 }
164         }
165         return false
166 }
167
168 // CORNER CASE: e.g. 1.3,2.0 (or 1.3,2) always touch,
169 // even if there's an unlisted 1.4 between them; unless
170 // the part count is known.
171 func (a *EpisodeRange) touches(b *EpisodeRange) bool {
172         if a == nil || b == nil || a.Type != b.Type {
173                 return false
174         }
175
176         switch {
177         case a == b:
178                 // log.Println("same pointers")
179                 return true
180         case a.Start == b.Start, a.End != nil && a.End == b.End:
181                 // log.Println("share pointers")
182                 return true
183
184         case a.End == nil:
185                 switch {
186                 case b.End == nil:
187                         // log.Println("both infinite")
188                         return true
189
190                 case b.End.Number == a.Start.Number:
191                         switch {
192                         // either is whole, or parts are adjacent/overlap
193                         case b.End.Part == -1, a.Start.Part == -1,
194                                 b.End.Part >= a.Start.Part-1:
195                                 // log.Printf("{ %s [} %s ...", b.End, a.Start)
196                                 return true
197                         }
198                 // only if start of next range is whole or is first part
199                 case b.End.Number == a.Start.Number-1 && a.Start.Part <= 0:
200                         switch {
201                         // end is whole, or is last part, or part count is unknown
202                         case b.End.Part == -1, b.End.Parts == 0,
203                                 b.End.Part == b.End.Parts:
204                                 // log.Printf("{ %s }[ %s ...", b.End, a.Start)
205                                 return true
206                         }
207                 case b.End.Number > a.Start.Number:
208                         // log.Printf("{ %s [ } %s ...", b.End, a.Start)
209                         return true
210                 }
211
212         case b.End == nil:
213                 switch {
214                 case a.End.Number == b.Start.Number:
215                         switch {
216                         case a.End.Part == -1, b.Start.Part == -1,
217                                 a.End.Part >= b.Start.Part-1:
218                                 // log.Printf("[ %s {] %s ...", a.End, b.Start)
219                                 return true
220                         }
221                 case a.End.Number == b.Start.Number-1 && b.Start.Part <= 0:
222                         switch {
223                         case a.End.Part == -1, a.End.Parts == 0,
224                                 a.End.Part == a.End.Parts:
225                                 // log.Printf("[ %s ]{ %s ...", a.End, b.Start)
226                                 return true
227                         }
228                 case a.End.Number > b.Start.Number:
229                         // log.Printf("[ %s { ] %s ...", a.End, b.Start)
230                         return true
231                 }
232
233         case a.Start.Number == b.Start.Number:
234                 // touching
235                 switch {
236                 // either is whole, or parts are immediately adjacent
237                 case a.Start.Part == -1, b.Start.Part == -1,
238                         a.Start.Part == b.Start.Part,
239                         a.Start.Part == b.Start.Part-1,
240                         a.Start.Part == b.Start.Part+1:
241                         // log.Printf("[{ %s - %s ]}", a.End, b.Start)
242                         return true
243                 }
244         case a.End.Number == b.End.Number:
245                 switch {
246                 case a.End.Part == -1, b.End.Part == -1,
247                         a.End.Part == b.End.Part,
248                         a.End.Part == b.End.Part-1,
249                         a.End.Part == b.End.Part+1:
250                         // log.Printf("{[ %s - %s }]", b.End, a.Start)
251                         return true
252                 }
253
254         case a.End.Number < b.End.Number:
255                 switch {
256                 case a.End.Number == b.Start.Number:
257                         switch {
258                         case a.End.Part == -1, b.Start.Part == -1,
259                                 a.End.Part >= b.Start.Part-1:
260                                 // log.Printf("[ %s {] %s }", a.End, b.Start)
261                                 return true
262                         }
263                 case a.End.Number == b.Start.Number-1 && b.Start.Part <= 0:
264                         switch {
265                         case b.End.Part == -1, b.End.Parts == 0,
266                                 b.End.Part == b.End.Parts:
267                                 // log.Printf("[ %s ]{ %s }", a.End, b.Start)
268                                 return true
269                         }
270                 case a.End.Number > b.Start.Number:
271                         // log.Printf("[ %s { ] %s }", a.End, b.Start)
272                         return true
273                 }
274
275         case b.End.Number < a.End.Number:
276                 switch {
277                 case b.End.Number == a.Start.Number:
278                         switch {
279                         case b.End.Part == -1, a.Start.Part == -1,
280                                 b.End.Part >= a.Start.Part-1:
281                                 // log.Printf("{ %s [} %s ]", b.End, a.Start)
282                                 return true
283                         }
284                 case b.End.Number == a.Start.Number-1 && a.Start.Part <= 0:
285                         switch {
286                         case b.End.Part == -1, b.End.Parts == 0,
287                                 b.End.Part == b.End.Parts:
288                                 // log.Printf("{ %s }[ %s ]", b.End, a.Start)
289                                 return true
290                         }
291                 case b.End.Number > a.Start.Number:
292                         // log.Printf("{ %s [ } %s ]", b.End, a.Start)
293                         return true
294                 }
295         }
296         return false
297 }
298
299 // Parses a string in the AniDB API range format and converts into an EpisodeRange.
300 func ParseEpisodeRange(s string) *EpisodeRange {
301         parts := strings.Split(s, "-")
302         if len(parts) > 2 {
303                 return nil
304         }
305
306         eps := [2]*Episode{}
307         for i := range parts {
308                 eps[i] = ParseEpisode(parts[i])
309         }
310         if eps[0] == nil {
311                 return nil
312         }
313
314         // Not an interval (just "epno") --
315         // convert into interval starting and ending in the same episode
316         if len(parts) == 1 {
317                 eps[1] = eps[0]
318         }
319
320         if len(parts) > 1 && eps[1] != nil && eps[0].Type != eps[1].Type {
321                 return nil
322         }
323         return &EpisodeRange{
324                 Type:  eps[0].Type,
325                 Start: eps[0],
326                 End:   eps[1],
327         }
328 }