001package squidpony.squidmath;
002
003import java.io.Serializable;
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Iterator;
007import java.util.Map;
008
009/**
010 * A small extension of OrderedMap that specifically handles {@code short[]} regions as produced by {@link CoordPacker}.
011 * The methods {@link #allAt(int, int)}, {@link #containsRegion(short[])}, and {@link #regionsContaining(int, int)} are
012 * added here, and the minor extra work needed to handle array keys in OrderedMap is taken care of automatically.
013 * {@link #toString()} also produces nicer output by default for this usage, with the keys printed in a usable way.
014 * Created by Tommy Ettinger on 11/24/2016.
015 */
016public class RegionMap<V> extends OrderedMap<short[], V> implements Serializable {
017    private static final long serialVersionUID = 2L;
018
019    public RegionMap(final int expected, final float f) {
020        super(expected, f, CrossHash.shortHasher);
021        CoordPacker.init();
022    }
023
024    /**
025     * Creates a new RegionMap with 0.75f as load factor.
026     *
027     * @param expected the expected number of elements in the RegionMap.
028     */
029    public RegionMap(final int expected) {
030        this(expected, DEFAULT_LOAD_FACTOR);
031    }
032
033    /**
034     * Creates a new RegionMap with initial expected 16 entries and 0.75f as load factor.
035     */
036    public RegionMap() {
037        this(DEFAULT_INITIAL_SIZE, DEFAULT_LOAD_FACTOR);
038    }
039
040    /**
041     * Creates a new RegionMap copying a given one.
042     *
043     * @param m a {@link Map} to be copied into the new RegionMap.
044     * @param f the load factor.
045     */
046    public RegionMap(final Map<short[], ? extends V> m, final float f) {
047        this(m.size(), f);
048        putAll(m);
049    }
050
051    /**
052     * Creates a new RegionMap with 0.75f as load factor copying a given one.
053     *
054     * @param m a {@link Map} to be copied into the new RegionMap.
055     */
056    public RegionMap(final Map<short[], ? extends V> m) {
057        this(m, DEFAULT_LOAD_FACTOR);
058    }
059
060    /**
061     * Creates a new RegionMap using the elements of two parallel arrays.
062     *
063     * @param keyArray   the array of keys of the new RegionMap.
064     * @param valueArray the array of corresponding values in the new RegionMap.
065     * @param f          the load factor.
066     * @throws IllegalArgumentException if <code>k</code> and <code>v</code> have different lengths.
067     */
068    public RegionMap(final short[][] keyArray, final V[] valueArray, final float f) {
069        this(keyArray.length, f);
070        if (keyArray.length != valueArray.length)
071            throw new IllegalArgumentException("The key array and the value array have different lengths (" + keyArray.length + " and " + valueArray.length + ")");
072        for (int i = 0; i < keyArray.length; i++)
073            put(keyArray[i], valueArray[i]);
074    }
075
076    /**
077     * Creates a new RegionMap using the elements of two parallel arrays.
078     *
079     * @param keyColl   the collection of keys of the new RegionMap.
080     * @param valueColl the collection of corresponding values in the new RegionMap.
081     * @param f         the load factor.
082     * @throws IllegalArgumentException if <code>k</code> and <code>v</code> have different lengths.
083     */
084    public RegionMap(final Collection<short[]> keyColl, final Collection<V> valueColl, final float f) {
085        this(keyColl.size(), f);
086        if (keyColl.size() != valueColl.size())
087            throw new IllegalArgumentException("The key array and the value array have different lengths (" + keyColl.size() + " and " + valueColl.size() + ")");
088        Iterator<short[]> ki = keyColl.iterator();
089        Iterator<V> vi = valueColl.iterator();
090        while (ki.hasNext() && vi.hasNext()) {
091            put(ki.next(), vi.next());
092        }
093    }
094
095    /**
096     * Creates a new RegionMap with 0.75f as load factor using the elements of two parallel arrays.
097     *
098     * @param keyArray   the array of keys of the new RegionMap.
099     * @param valueArray the array of corresponding values in the new RegionMap.
100     * @throws IllegalArgumentException if <code>k</code> and <code>v</code> have different lengths.
101     */
102    public RegionMap(final short[][] keyArray, final V[] valueArray) {
103        this(keyArray, valueArray, DEFAULT_LOAD_FACTOR);
104    }
105
106    /**
107     * Gets a List of all values associated with regions containing a given x,y point.
108     *
109     * @param x the x coordinate of the point in question
110     * @param y the y coordinate of the point in question
111     * @return an ArrayList of all V values corresponding to regions containing the given x,y point.
112     */
113    public ArrayList<V> allAt(int x, int y) {
114        ArrayList<V> found = new ArrayList<>(size);
115        OrderedSet<short[]> regions = CoordPacker.findManyPacked(x, y, keySet());
116        int count = regions.size;
117        for (int i = 0; i < count; i++) {
118            found.add(get(regions.getAt(i)));
119        }
120        return found;
121    }
122
123    /**
124     * Checks if a region, stored as packed data (possibly from CoordPacker or this class) overlaps with regions stored
125     * in this object as keys. Returns true if there is any overlap, false otherwise
126     *
127     * @param region the packed region to check for overlap with regions this stores values for
128     * @return true if the region overlaps at all, false otherwise
129     */
130    public boolean containsRegion(short[] region) {
131        return CoordPacker.regionsContain(region, keySet());
132    }
133
134    /**
135     * Gets a List of all regions containing a given x,y point.
136     *
137     * @param x the x coordinate of the point in question
138     * @param y the y coordinate of the point in question
139     * @return an ArrayList of all regions in this data structure containing the given x,y point.
140     */
141    public OrderedSet<short[]> regionsContaining(int x, int y) {
142        return CoordPacker.findManyPacked(x, y, keySet());
143    }
144
145    public String toString(String separator) {
146        return toString(separator, false);
147    }
148
149    @Override
150    public String toString() {
151        return toString(", ", true);
152    }
153
154    private String toString(String separator, boolean braces) {
155        if (size == 0) return braces ? "{}" : "";
156        StringBuilder buffer = new StringBuilder(32);
157        if (braces) buffer.append('{');
158        short[][] keyTable = this.key;
159        V[] valueTable = this.value;
160        int i = keyTable.length;
161        while (i-- > 0) {
162            short[] key = keyTable[i];
163            if (key == null) continue;
164            buffer.append("Packed Region:")
165                    .append(CoordPacker.encodeASCII(key))
166                    .append('=')
167                    .append(valueTable[i]);
168            break;
169        }
170        while (i-- > 0) {
171            short[] key = keyTable[i];
172            if (key == null) continue;
173            buffer.append(separator)
174                    .append("Packed Region:")
175                    .append(CoordPacker.encodeASCII(key))
176                    .append('=')
177                    .append(valueTable[i]);
178        }
179        if (braces) buffer.append('}');
180        return buffer.toString();
181    }
182}