/*
 *       _____  _       _    _____                                _
 *      |  __ \| |     | |  / ____|                              | |
 *      | |__) | | ___ | |_| (___   __ _ _   _  __ _ _ __ ___  __| |
 *      |  ___/| |/ _ \| __|\___ \ / _` | | | |/ _` | '__/ _ \/ _` |
 *      | |    | | (_) | |_ ____) | (_| | |_| | (_| | | |  __/ (_| |
 *      |_|    |_|\___/ \__|_____/ \__, |\__,_|\__,_|_|  \___|\__,_|
 *                                    | |
 *                                    |_|
 *            PlotSquared plot management system for Minecraft
 *                  Copyright (C) 2020 IntellectualSites
 *
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.plotsquared.bukkit.chat;

import org.apache.commons.lang.Validate;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;

/**
 * Represents a wrapper around an array class of an arbitrary reference type,
 * which properly implements "value" hash code and equality functions.
 * <p>
 * This class is intended for use as a key to a map.
 * </p>
 *
 * @param <E> The type of elements in the array.
 * @author Glen Husman
 * @see Arrays
 */
public final class ArrayWrapper<E> {

    private E[] _array;

    /**
     * Creates an array wrapper with some elements.
     *
     * @param elements The elements of the array.
     */
    public ArrayWrapper(E... elements) {
        setArray(elements);
    }

    /**
     * Converts an iterable element collection to an array of elements.
     * The iteration order of the specified object will be used as the array element order.
     *
     * @param list The iterable of objects which will be converted to an array.
     * @param c    The type of the elements of the array.
     * @return An array of elements in the specified iterable.
     */
    @SuppressWarnings("unchecked") public static <T> T[] toArray(Iterable<? extends T> list,
        Class<T> c) {
        int size = -1;
        if (list instanceof Collection<?>) {
            @SuppressWarnings("rawtypes") Collection coll = (Collection) list;
            size = coll.size();
        }


        if (size < 0) {
            size = 0;
            // Ugly hack: Count it ourselves
            for (@SuppressWarnings("unused") T element : list) {
                size++;
            }
        }

        T[] result = (T[]) Array.newInstance(c, size);
        int i = 0;
        for (T element : list) { // Assumes iteration order is consistent
            result[i++] = element; // Assign array element at index THEN increment counter
        }
        return result;
    }

    /**
     * Retrieves a reference to the wrapped array instance.
     *
     * @return The array wrapped by this instance.
     */
    public E[] getArray() {
        return _array;
    }

    /**
     * Set this wrapper to wrap a new array instance.
     *
     * @param array The new wrapped array.
     */
    public void setArray(E[] array) {
        Validate.notNull(array, "The array must not be null.");
        _array = array;
    }

    /**
     * Determines if this object has a value equivalent to another object.
     *
     * @see Arrays#equals(Object[], Object[])
     */
    @SuppressWarnings("rawtypes") @Override public boolean equals(Object other) {
        if (!(other instanceof ArrayWrapper)) {
            return false;
        }
        return Arrays.equals(_array, ((ArrayWrapper) other)._array);
    }

    /**
     * Gets the hash code represented by this objects value.
     *
     * @return This object's hash code.
     * @see Arrays#hashCode(Object[])
     */
    @Override public int hashCode() {
        return Arrays.hashCode(_array);
    }

}
