1 #pragma once
2 
3 #include <QtDebug>
4 #include <iosfwd>
5 #include <utility>
6 
7 #include "util/assert.h"
8 #include "util/optional.h"
9 #include "util/types.h"
10 
11 namespace mixxx {
12 
13 // A half-open, directed range of indices with limited mutability.
14 class IndexRange final: private std::pair<SINT, SINT> {
15     typedef std::pair<SINT, SINT> Super;
16 
17   public:
18     using Super::swap;
19 
IndexRange()20     IndexRange()
21         : Super(0, 0) {
22         DEBUG_ASSERT(empty());
23     }
24 
between(SINT start,SINT end)25     static IndexRange between(SINT start, SINT end) {
26         return IndexRange(start, end);
27     }
forward(SINT start,SINT length)28     static IndexRange forward(SINT start, SINT length) {
29         DEBUG_ASSERT(length >= 0);
30         return IndexRange(start, start + length);
31     }
backward(SINT start,SINT length)32     static IndexRange backward(SINT start, SINT length) {
33         DEBUG_ASSERT(length >= 0);
34         return IndexRange(start, start - length);
35     }
36 
37     // The first index within this range (inclusive)
start()38     SINT start() const {
39         return first;
40     }
41     // The next index beyond this range (exclusive)
end()42     SINT end() const {
43         return second;
44     }
45 
empty()46     bool empty() const {
47         return start() == end();
48     }
49 
50     enum class Orientation {
51         Empty,
52         Forward,
53         Backward,
54     };
orientation()55     Orientation orientation() const {
56         if (empty()) {
57             return Orientation::Empty; // undefined
58         } else {
59             if (start() < end()) {
60                 return Orientation::Forward;
61             } else {
62                 return Orientation::Backward;
63             }
64         }
65     }
66 
length()67     SINT length() const {
68         return (start() <= end()) ? (end() - start()) : (start() - end());
69     }
70 
71     // Clamps index by this range including both start() and end()
72     // boundaries.
clampIndex(SINT index)73     SINT clampIndex(SINT index) const {
74         if (start() <= end()) {
75             return std::max(start(), std::min(end(), index));
76         } else {
77             return std::min(start(), std::max(end(), index));
78         }
79     }
80 
containsIndex(SINT index)81     bool containsIndex(SINT index) const {
82         if (start() <= end()) {
83             return (start() <= index) && (index < end());
84         } else {
85             return (start() >= index) && (index > end());
86         }
87     }
88 
89     // Grow the range by appending the given length at the front side.
growFront(SINT frontLength)90     void growFront(SINT frontLength) {
91         DEBUG_ASSERT(frontLength >= 0);
92         if (start() <= end()) {
93             first -= frontLength;
94         } else {
95             first += frontLength;
96         }
97     }
98 
99     // Grow the range by appending the given length at the back side.
growBack(SINT backLength)100     void growBack(SINT backLength) {
101         DEBUG_ASSERT(backLength >= 0);
102         if (start() <= end()) {
103             second += backLength;
104         } else {
105             second -= backLength;
106         }
107     }
108 
109     // Shrink the range by cutting off the given length at the front side.
shrinkFront(SINT frontLength)110     void shrinkFront(SINT frontLength) {
111         DEBUG_ASSERT(frontLength >= 0);
112         DEBUG_ASSERT(frontLength <= length());
113         if (start() <= end()) {
114             first += frontLength;
115         } else {
116             first -= frontLength;
117         }
118     }
119 
120     // Shrink the range by cutting off the given length at the back side.
shrinkBack(SINT backLength)121     void shrinkBack(SINT backLength) {
122         DEBUG_ASSERT(backLength >= 0);
123         DEBUG_ASSERT(backLength <= length());
124         if (start() <= end()) {
125             second -= backLength;
126         } else {
127             second += backLength;
128         }
129     }
130 
131     // Splits this range into two adjacent parts by slicing off
132     // and returning a range of given length and same direction
133     // from the front side. The given front length must not exceed
134     // the length of this range.
135     IndexRange splitAndShrinkFront(SINT frontLength);
136 
137     // Splits this range into two adjacent parts by slicing off
138     // and returning a range of given length and same direction
139     // from the back side. The given back length must not exceed
140     // the length of this range.
141     IndexRange splitAndShrinkBack(SINT backLength);
142 
143     bool isSubrangeOf(IndexRange outerIndexRange) const;
144 
145     friend
146     bool operator==(IndexRange lhs, IndexRange rhs) {
147         return (lhs.first == rhs.first) && (lhs.second == rhs.second);
148     }
149 
150   private:
IndexRange(SINT start,SINT end)151     IndexRange(SINT start, SINT end)
152         : Super(start, end) {
153     }
154 };
155 
156 /// Intersect two ranges with compatible orientations.
157 ///
158 /// Returns std::nullopt of the ranges are disconnected without
159 /// any junction point or on orientation mismatch.
160 ///
161 /// TODO: Rename as intersect() after removing the original function
162 /// with the non-optional return type
163 std::optional<IndexRange> intersect2(IndexRange lhs, IndexRange rhs);
164 
165 /// Deprecated, only needed for backward compatibility
166 ///
167 /// TODO: Replace with intersect2()
intersect(IndexRange lhs,IndexRange rhs)168 inline IndexRange intersect(IndexRange lhs, IndexRange rhs) {
169     auto res = intersect2(lhs, rhs);
170     return res ? *res : IndexRange();
171 }
172 
173 inline
174 bool operator!=(IndexRange lhs, IndexRange rhs) {
175     return !(lhs == rhs);
176 }
177 
178 std::ostream& operator<<(std::ostream& os, IndexRange arg);
179 
180 QDebug operator<<(QDebug dbg, IndexRange arg);
181 
182 
183 } // namespace mixxx
184