001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.collection;
020
021import com.plotsquared.core.util.RegionUtil;
022import com.sk89q.worldedit.regions.CuboidRegion;
023
024import java.util.HashSet;
025import java.util.Set;
026
027public class QuadMap<T> {
028
029    public final int size;
030    public final int x;
031    public final int z;
032    private final int newsize;
033    private final int min;
034    public HashSet<T> objects;
035    public QuadMap<T> one;
036    public QuadMap<T> two;
037    public QuadMap<T> three;
038    public QuadMap<T> four;
039    public QuadMap<T> skip;
040
041    public QuadMap(int size, int x, int z) {
042        this.size = size;
043        this.x = x;
044        this.z = z;
045        this.newsize = size >> 1;
046        this.min = 512;
047    }
048
049    public QuadMap(int size, int x, int z, int min) {
050        this.size = size;
051        this.x = x;
052        this.z = z;
053        this.newsize = size >> 1;
054        this.min = min;
055    }
056
057    public int count() {
058        int size = countBelow();
059        if (this.objects != null) {
060            size += this.objects.size();
061        }
062        return size;
063    }
064
065    public Set<T> getAll() {
066        HashSet<T> all = new HashSet<>();
067        if (this.objects != null) {
068            all.addAll(this.objects);
069        }
070        if (this.skip != null) {
071            all.addAll(this.skip.getAll());
072            return all;
073        }
074        if (this.one != null) {
075            all.addAll(this.one.getAll());
076        }
077        if (this.two != null) {
078            all.addAll(this.two.getAll());
079        }
080        if (this.three != null) {
081            all.addAll(this.three.getAll());
082        }
083        if (this.four != null) {
084            all.addAll(this.four.getAll());
085        }
086        return all;
087    }
088
089    public int countCurrent() {
090        return this.objects == null ? 0 : this.objects.size();
091    }
092
093    public int countBelow() {
094        int size = 0;
095        if (this.one != null) {
096            size += this.one.count();
097        }
098        if (this.two != null) {
099            size += this.two.count();
100        }
101        if (this.three != null) {
102            size += this.three.count();
103        }
104        if (this.four != null) {
105            size += this.four.count();
106        }
107        return size;
108    }
109
110    public void add(T area) {
111        if (this.size <= this.min) {
112            if (this.objects == null) {
113                this.objects = new HashSet<>();
114            }
115            this.objects.add(area);
116            return;
117        }
118        CuboidRegion region = getRegion(area);
119        if (region.getMinimumPoint().getX() >= this.x) {
120            if (region.getMinimumPoint().getZ() >= this.z) {
121                if (this.one == null) {
122                    this.one =
123                            newInstance(this.newsize, this.x + this.newsize, this.z + this.newsize,
124                                    this.min
125                            );
126                }
127                this.one.add(area);
128                recalculateSkip();
129                return;
130            } else if (region.getMaximumPoint().getZ() < this.z) {
131                if (this.two == null) {
132                    this.two =
133                            newInstance(this.newsize, this.x + this.newsize, this.z - this.newsize,
134                                    this.min
135                            );
136                }
137                this.two.add(area);
138                recalculateSkip();
139                return;
140            }
141        } else if (region.getMaximumPoint().getX() < this.x) {
142            if (region.getMinimumPoint().getZ() >= this.z) {
143                if (this.four == null) {
144                    this.four =
145                            newInstance(this.newsize, this.x - this.newsize, this.z + this.newsize,
146                                    this.min
147                            );
148                }
149                this.four.add(area);
150                recalculateSkip();
151                return;
152            } else if (region.getMaximumPoint().getZ() < this.z) {
153                if (this.three == null) {
154                    this.three =
155                            newInstance(this.newsize, this.x - this.newsize, this.z - this.newsize,
156                                    this.min
157                            );
158                }
159                this.three.add(area);
160                recalculateSkip();
161                return;
162            }
163        }
164        if (this.objects == null) {
165            this.objects = new HashSet<>();
166        }
167        this.objects.add(area);
168    }
169
170    public CuboidRegion getRegion(T value) {
171        return null;
172    }
173
174    public QuadMap<T> newInstance(int newsize, int x, int z, int min) {
175        try {
176            return new QuadMap<T>(newsize, x, z, min) {
177                @Override
178                public CuboidRegion getRegion(T value) {
179                    return QuadMap.this.getRegion(value);
180                }
181            };
182        } catch (Throwable e) {
183            e.printStackTrace();
184            return null;
185        }
186    }
187
188    public boolean remove(T area) {
189        if (this.objects != null) {
190            if (this.objects.remove(area)) {
191                return this.objects.isEmpty();
192            }
193        }
194        if (this.skip != null) {
195            if (this.skip.remove(area)) {
196                this.skip = null;
197            }
198        } else {
199            CuboidRegion region = getRegion(area);
200            if (region.getMinimumPoint().getX() >= this.x) {
201                if (region.getMinimumPoint().getZ() >= this.z) {
202                    if (this.one != null) {
203                        if (this.one.remove(area)) {
204                            this.one = null;
205                        }
206                        return countCurrent() == 0;
207                    }
208                } else {
209                    if (this.two != null) {
210                        if (this.two.remove(area)) {
211                            this.two = null;
212                        }
213                        return countCurrent() == 0;
214                    }
215                }
216            } else {
217                if (region.getMinimumPoint().getZ() >= this.z) {
218                    if (this.four != null) {
219                        if (this.four.remove(area)) {
220                            this.four = null;
221                        }
222                        return countCurrent() == 0;
223                    }
224                } else {
225                    if (this.three != null) {
226                        if (this.three.remove(area)) {
227                            this.three = null;
228                        }
229                        return countCurrent() == 0;
230                    }
231                }
232            }
233        }
234        return false;
235    }
236
237    @SuppressWarnings("unchecked")
238    public void recalculateSkip() {
239        QuadMap<T> map = null;
240        for (QuadMap<T> current : new QuadMap[]{this.one, this.two, this.three, this.four}) {
241            if (current != null) {
242                if (map != null) {
243                    this.skip = null;
244                    return;
245                }
246                map = current;
247            }
248        }
249        this.skip = map.skip == null ? map : map.skip;
250    }
251
252    public Set<T> get(CuboidRegion region) {
253        HashSet<T> set = new HashSet<>();
254        if (this.objects != null) {
255            for (T obj : this.objects) {
256                if (RegionUtil.intersects(getRegion(obj), region)) {
257                    set.add(obj);
258                }
259            }
260        }
261        if (this.skip != null) {
262            if (this.skip.intersects(region)) {
263                set.addAll(this.skip.get(region));
264            }
265        } else {
266            if (this.one != null && this.one.intersects(region)) {
267                set.addAll(this.one.get(region));
268            }
269            if (this.two != null && this.two.intersects(region)) {
270                set.addAll(this.two.get(region));
271            }
272            if (this.three != null && this.three.intersects(region)) {
273                set.addAll(this.three.get(region));
274            }
275            if (this.four != null && this.four.intersects(region)) {
276                set.addAll(this.four.get(region));
277            }
278        }
279        return set;
280    }
281
282    public boolean intersects(CuboidRegion other) {
283        return (other.getMinimumPoint().getX() <= this.x + this.size) && (
284                other.getMaximumPoint().getX() >= this.x - this.size) && (other.getMinimumPoint().getZ()
285                <= this.z + this.size) && (other.getMaximumPoint().getZ() >= this.z - this.size);
286    }
287
288    public T get(int x, int z) {
289        if (this.objects != null) {
290            for (T obj : this.objects) {
291                if (RegionUtil.contains(getRegion(obj), x, z)) {
292                    return obj;
293                }
294            }
295        }
296        if (this.skip != null) {
297            return this.skip.get(x, z);
298        } else {
299            if (x >= this.x) {
300                if (z >= this.z) {
301                    if (this.one != null) {
302                        return this.one.get(x, z);
303                    }
304                } else {
305                    if (this.two != null) {
306                        return this.two.get(x, z);
307                    }
308                }
309            } else {
310                if (z >= this.z) {
311                    if (this.four != null) {
312                        return this.four.get(x, z);
313                    }
314                } else {
315                    if (this.three != null) {
316                        return this.three.get(x, z);
317                    }
318                }
319            }
320        }
321        return null;
322    }
323
324}