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.plot;
020
021import com.plotsquared.core.location.Direction;
022import org.checkerframework.checker.nullness.qual.NonNull;
023import org.checkerframework.checker.nullness.qual.Nullable;
024
025import java.util.Iterator;
026import java.util.NoSuchElementException;
027
028/**
029 * Plot (X,Y) tuples for plot locations
030 * within a plot area
031 */
032public final class PlotId {
033
034    private final int x;
035    private final int y;
036    private final int hash;
037
038    /**
039     * PlotId class (PlotId x,y values do not correspond to Block locations)
040     *
041     * @param x The plot x coordinate
042     * @param y The plot y coordinate
043     */
044    private PlotId(final int x, final int y) {
045        this.x = x;
046        this.y = y;
047        this.hash = (this.getX() << 16) | (this.getY() & 0xFFFF);
048    }
049
050    /**
051     * Create a new plot ID instance
052     *
053     * @param x The plot x coordinate
054     * @param y The plot y coordinate
055     * @return a new PlotId at x,y
056     */
057    public static @NonNull PlotId of(final int x, final int y) {
058        return new PlotId(x, y);
059    }
060
061    /**
062     * Get a Plot Id based on a string
063     *
064     * @param string to create id from
065     * @return the PlotId representation of the argument
066     * @throws IllegalArgumentException if the string does not contain a valid PlotId
067     */
068    public static @NonNull PlotId fromString(final @NonNull String string) {
069        final PlotId plot = fromStringOrNull(string);
070        if (plot == null) {
071            throw new IllegalArgumentException("Cannot create PlotID. String invalid.");
072        }
073        return plot;
074    }
075
076    /**
077     * Attempt to parse a plot ID from a string
078     *
079     * @param string ID string
080     * @return Plot ID, or {@code null} if none could be parsed
081     */
082    public static @Nullable PlotId fromStringOrNull(final @NonNull String string) {
083        final String[] parts = string.split("[;_,.]");
084        if (parts.length < 2) {
085            return null;
086        }
087        int x;
088        int y;
089        try {
090            x = Integer.parseInt(parts[0]);
091            y = Integer.parseInt(parts[1]);
092        } catch (final NumberFormatException ignored) {
093            return null;
094        }
095        return of(x, y);
096    }
097
098    /**
099     * Gets the PlotId from the HashCode<br>
100     * Note: Only accurate for small x,z values (short)
101     *
102     * @param hash ID hash
103     * @return Plot ID
104     */
105    public static @NonNull PlotId unpair(final int hash) {
106        return PlotId.of(hash >> 16, hash & 0xFFFF);
107    }
108
109    /**
110     * Get a copy of the plot ID
111     *
112     * @return Plot ID copy
113     * @deprecated PlotId is immutable, copy is not required.
114     */
115    @Deprecated(forRemoval = true, since = "6.10.2")
116    public @NonNull PlotId copy() {
117        return this;
118    }
119
120    /**
121     * Get the ID X component
122     *
123     * @return X component
124     */
125    public int getX() {
126        return this.x;
127    }
128
129    /**
130     * Get the ID Y component
131     *
132     * @return Y component
133     */
134    public int getY() {
135        return this.y;
136    }
137
138    /**
139     * Get the next plot ID for claiming purposes
140     *
141     * @return Next plot ID
142     */
143    public @NonNull PlotId getNextId() {
144        final int absX = Math.abs(x);
145        final int absY = Math.abs(y);
146        if (absX > absY) {
147            if (x > 0) {
148                return PlotId.of(x, y + 1);
149            } else {
150                return PlotId.of(x, y - 1);
151            }
152        } else if (absY > absX) {
153            if (y > 0) {
154                return PlotId.of(x - 1, y);
155            } else {
156                return PlotId.of(x + 1, y);
157            }
158        } else {
159            if (x == y && x > 0) {
160                return PlotId.of(x, y + 1);
161            }
162            if (x == absX) {
163                return PlotId.of(x, y + 1);
164            }
165            if (y == absY) {
166                return PlotId.of(x, y - 1);
167            }
168            return PlotId.of(x + 1, y);
169        }
170    }
171
172    /**
173     * Get the PlotId in a relative direction
174     *
175     * @param direction Direction
176     * @return Relative plot ID
177     */
178    public @NonNull PlotId getRelative(final @NonNull Direction direction) {
179        return switch (direction) {
180            case NORTH -> PlotId.of(this.getX(), this.getY() - 1);
181            case EAST -> PlotId.of(this.getX() + 1, this.getY());
182            case SOUTH -> PlotId.of(this.getX(), this.getY() + 1);
183            case WEST -> PlotId.of(this.getX() - 1, this.getY());
184            default -> this;
185        };
186    }
187
188    @Override
189    public boolean equals(final Object obj) {
190        if (this == obj) {
191            return true;
192        }
193        if (obj == null) {
194            return false;
195        }
196        if (this.hashCode() != obj.hashCode()) {
197            return false;
198        }
199        if (getClass() != obj.getClass()) {
200            return false;
201        }
202        final PlotId other = (PlotId) obj;
203        return this.getX() == other.getX() && this.getY() == other.getY();
204    }
205
206    /**
207     * Get a String representation of the plot ID where the
208     * components are separated by ";"
209     *
210     * @return {@code x + ";" + y}
211     */
212    @Override
213    public @NonNull String toString() {
214        return this.getX() + ";" + this.getY();
215    }
216
217    /**
218     * Get a String representation of the plot ID where the
219     * components are separated by a specified string
220     *
221     * @param separator Separator
222     * @return {@code x + separator + y}
223     */
224    public @NonNull String toSeparatedString(String separator) {
225        return this.getX() + separator + this.getY();
226    }
227
228    /**
229     * Get a String representation of the plot ID where the
230     * components are separated by ","
231     *
232     * @return {@code x + "," + y}
233     */
234    public @NonNull String toCommaSeparatedString() {
235        return this.getX() + "," + this.getY();
236    }
237
238    /**
239     * Get a String representation of the plot ID where the
240     * components are separated by "_"
241     *
242     * @return {@code x + "_" + y}
243     */
244    public @NonNull String toUnderscoreSeparatedString() {
245        return this.getX() + "_" + this.getY();
246    }
247
248    /**
249     * Get a String representation of the plot ID where the
250     * components are separated by "-"
251     *
252     * @return {@code x + "-" + y}
253     */
254    public @NonNull String toDashSeparatedString() {
255        return this.getX() + "-" + this.getY();
256    }
257
258    @Override
259    public int hashCode() {
260        return this.hash;
261    }
262
263
264    public static final class PlotRangeIterator implements Iterator<PlotId>, Iterable<PlotId> {
265
266        private final PlotId start;
267        private final PlotId end;
268
269        private int x;
270        private int y;
271
272        private PlotRangeIterator(final @NonNull PlotId start, final @NonNull PlotId end) {
273            this.start = start;
274            this.end = end;
275            this.x = this.start.getX();
276            this.y = this.start.getY();
277        }
278
279        public static PlotRangeIterator range(final @NonNull PlotId start, final @NonNull PlotId end) {
280            return new PlotRangeIterator(start, end);
281        }
282
283        @Override
284        public boolean hasNext() {
285            // end is fully included
286            return this.x <= this.end.getX() && this.y <= this.end.getY();
287        }
288
289        @Override
290        public PlotId next() {
291            if (!hasNext()) {
292                throw new NoSuchElementException("The iterator has no more entries");
293            }
294            // increment *after* getting the result to include the minimum
295            // the id to return
296            PlotId result = PlotId.of(this.x, this.y);
297            // first increase y, then x
298            if (this.y == this.end.getY()) {
299                this.x++;
300                this.y = this.start.getY();
301            } else {
302                this.y++;
303            }
304            return result;
305        }
306
307        @NonNull
308        @Override
309        public Iterator<PlotId> iterator() {
310            return this;
311        }
312
313    }
314
315}