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 the ID X component
111     *
112     * @return X component
113     */
114    public int getX() {
115        return this.x;
116    }
117
118    /**
119     * Get the ID Y component
120     *
121     * @return Y component
122     */
123    public int getY() {
124        return this.y;
125    }
126
127    /**
128     * Get the next plot ID for claiming purposes
129     *
130     * @return Next plot ID
131     */
132    public @NonNull PlotId getNextId() {
133        final int absX = Math.abs(x);
134        final int absY = Math.abs(y);
135        if (absX > absY) {
136            if (x > 0) {
137                return PlotId.of(x, y + 1);
138            } else {
139                return PlotId.of(x, y - 1);
140            }
141        } else if (absY > absX) {
142            if (y > 0) {
143                return PlotId.of(x - 1, y);
144            } else {
145                return PlotId.of(x + 1, y);
146            }
147        } else {
148            if (x == y && x > 0) {
149                return PlotId.of(x, y + 1);
150            }
151            if (x == absX) {
152                return PlotId.of(x, y + 1);
153            }
154            if (y == absY) {
155                return PlotId.of(x, y - 1);
156            }
157            return PlotId.of(x + 1, y);
158        }
159    }
160
161    /**
162     * Get the PlotId in a relative direction
163     *
164     * @param direction Direction
165     * @return Relative plot ID
166     */
167    public @NonNull PlotId getRelative(final @NonNull Direction direction) {
168        return switch (direction) {
169            case NORTH -> PlotId.of(this.getX(), this.getY() - 1);
170            case EAST -> PlotId.of(this.getX() + 1, this.getY());
171            case SOUTH -> PlotId.of(this.getX(), this.getY() + 1);
172            case WEST -> PlotId.of(this.getX() - 1, this.getY());
173            default -> this;
174        };
175    }
176
177    @Override
178    public boolean equals(final Object obj) {
179        if (this == obj) {
180            return true;
181        }
182        if (obj == null) {
183            return false;
184        }
185        if (this.hashCode() != obj.hashCode()) {
186            return false;
187        }
188        if (getClass() != obj.getClass()) {
189            return false;
190        }
191        final PlotId other = (PlotId) obj;
192        return this.getX() == other.getX() && this.getY() == other.getY();
193    }
194
195    /**
196     * Get a String representation of the plot ID where the
197     * components are separated by ";"
198     *
199     * @return {@code x + ";" + y}
200     */
201    @Override
202    public @NonNull String toString() {
203        return this.getX() + ";" + this.getY();
204    }
205
206    /**
207     * Get a String representation of the plot ID where the
208     * components are separated by a specified string
209     *
210     * @param separator Separator
211     * @return {@code x + separator + y}
212     */
213    public @NonNull String toSeparatedString(String separator) {
214        return this.getX() + separator + this.getY();
215    }
216
217    /**
218     * Get a String representation of the plot ID where the
219     * components are separated by ","
220     *
221     * @return {@code x + "," + y}
222     */
223    public @NonNull String toCommaSeparatedString() {
224        return this.getX() + "," + this.getY();
225    }
226
227    /**
228     * Get a String representation of the plot ID where the
229     * components are separated by "_"
230     *
231     * @return {@code x + "_" + y}
232     */
233    public @NonNull String toUnderscoreSeparatedString() {
234        return this.getX() + "_" + this.getY();
235    }
236
237    /**
238     * Get a String representation of the plot ID where the
239     * components are separated by "-"
240     *
241     * @return {@code x + "-" + y}
242     */
243    public @NonNull String toDashSeparatedString() {
244        return this.getX() + "-" + this.getY();
245    }
246
247    @Override
248    public int hashCode() {
249        return this.hash;
250    }
251
252
253    public static final class PlotRangeIterator implements Iterator<PlotId>, Iterable<PlotId> {
254
255        private final PlotId start;
256        private final PlotId end;
257
258        private int x;
259        private int y;
260
261        private PlotRangeIterator(final @NonNull PlotId start, final @NonNull PlotId end) {
262            this.start = start;
263            this.end = end;
264            this.x = this.start.getX();
265            this.y = this.start.getY();
266        }
267
268        public static PlotRangeIterator range(final @NonNull PlotId start, final @NonNull PlotId end) {
269            return new PlotRangeIterator(start, end);
270        }
271
272        @Override
273        public boolean hasNext() {
274            // end is fully included
275            return this.x <= this.end.getX() && this.y <= this.end.getY();
276        }
277
278        @Override
279        public PlotId next() {
280            if (!hasNext()) {
281                throw new NoSuchElementException("The iterator has no more entries");
282            }
283            // increment *after* getting the result to include the minimum
284            // the id to return
285            PlotId result = PlotId.of(this.x, this.y);
286            // first increase y, then x
287            if (this.y == this.end.getY()) {
288                this.x++;
289                this.y = this.start.getY();
290            } else {
291                this.y++;
292            }
293            return result;
294        }
295
296        @NonNull
297        @Override
298        public Iterator<PlotId> iterator() {
299            return this;
300        }
301
302    }
303
304}