package shapes77.util;
// Příliš žluťoučký kůň úpěl ďábelské ó - PŘÍLIŠ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ Ó

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import java.util.function.IntFunction;



/*******************************************************************************
 * Knihovní třída {@code ContainerUtil} definuje sadu užitečných metod
 * pro práci s různými druhy kontejnerů: přepravkami, kolekcemi a poli.
 */
public class ContainerUtil
{
//== CONSTANT CLASS ATTRIBUTES =================================================

    /** Otevírací závorky zahajující výpis obsahu pole či kolekce. */
    public static final char AP = '{';

    /** Uzavírací závorky ukončující výpis obsahu pole či kolekce. */
    public static final char ZP = '}';

    /** Uvozovky či jiný znak, kterým označujeme začátek vypisovaného řetězce,
     *  aby byly vidět i případné úvodní mezery. */
    public static final char A_ = '«';

    /** Uvozovky či jiný znak, kterým označujeme konec vypisovaného řetězce,
     *  aby byly vidět i případné závěrečné mezery. */
    public static final char _Z = '»';

    /** Separátor oddělující položky v jedno řádkovém výpisu. */
    private static final String SEP = ", ";

    /** Separátor oddělující položky v jedno řádkovém výpisu. */
    private static final int SEP_LENGTH = SEP.length();



//== VARIABLE CLASS ATTRIBUTES =================================================



//##############################################################################
//== STATIC INITIALIZER (CLASS CONSTRUCTOR) ====================================
//== CLASS GETTERS AND SETTERS =================================================
//== OTHER NON-PRIVATE CLASS METHODS ===========================================

    /***************************************************************************
     * Přidá do zadané kolekce všechny zadané hodnoty.
     *
     * @param <E>        Typ prvků (elementů) kolekce
     * @param collection Kolekce, do níž přidáváme zadané prvky
     * @param elements   Hodnoty přidávané do zadané kolekce
     */
    @SuppressWarnings("unchecked")
    public static <E> void addAll(Collection<E> collection, E... elements)
    {
        collection.addAll(Arrays.asList(elements));
    }


    /***************************************************************************
     * Převede zadaný vektor na řetězec znaků přičemž
     * každou z hodnot uzavře do francouzských uvozovek,
     * jednotlivé takto převedené hodnoty oddělí čárkami
     * a celý vektor uzavře do kulatých závorek
     *
     * @param arr   Pole, jehož hodnoty chceme vypsat
     * @return      Požadovaný řetězec
     */
    public static String arr2String(int[] arr)
    {
        StringBuilder sb = new StringBuilder().append(AP);
        for (int o : arr) {
            sb.append(A_).append(o).append(_Z).append(SEP);
        }
        return closedStringBuilder(sb);
    }


    /***************************************************************************
     * Převede zadaný vektor na řetězec znaků přičemž
     * každou z hodnot uzavře do francouzských uvozovek
     * a umístí na samostatný řádek.
     *
     * @param arr   Pole, jehož hodnoty chceme vypsat
     * @return      Požadovaný řetězec
     */
    public static String arr2nlString(Object[] arr)
    {
        if (arr == null) {
            return "null";
        }
        StringBuilder sb = new StringBuilder();
        for (Object o : arr) {
            sb.append(A_).append(o).append(_Z).append('\n');
        }
        return sb.toString();
    }


    /***************************************************************************
     * Převede zadaný vektor na řetězec znaků přičemž
     * každou z hodnot uzavře do francouzských uvozovek
     * jednotlivé takto převedené hodnoty oddělí čárkami
     * a celý vektor uzavře do kulatých závorek
     *
     * @param arr   Pole, jehož hodnoty chceme vypsat
     * @return      Požadovaný řetězec
     */
    public static String arr2String(Object[] arr)
    {
        if (arr == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder().append(AP);
        for (Object o : arr) {
            sb.append(AP).append(o).append(ZP).append(SEP);
        }
        return closedStringBuilder(sb);
    }


    /***************************************************************************
     * Vytvoří nový seznam a naplní jej zadanými hodnotami.
     *
     * @param <E>       Typ prvků (elementů) vytvářeného seznamu
     * @param elements  Hodnoty vkládané do vytvářeného seznamu.
     * @return Požadovaný seznam
     */
    @SuppressWarnings("unchecked")
    public static <E> ArrayList<E> newCollection(E... elements)
    {
        return newArrayList(elements);
    }


    /***************************************************************************
     * Vytvoří nový seznam a naplní jej zadanými hodnotami.
     *
     * @param <E> Typ prvků (elementů) vytvářeného seznamu
     * @param elements Hodnoty vkládané do vytvářeného seznamu.
     * @return Požadovaný seznam
     */
    @SuppressWarnings("unchecked")
    public static <E> List<E> newList(E... elements)
    {
        return newArrayList(elements);
    }


    /***************************************************************************
     * Vytvoří novou instanci třídy {@link ArrayList}
     * a naplní ji zadanými hodnotami.
     *
     * @param <E> Typ prvků (elementů) vytvářeného seznamu
     * @param elements Hodnoty vkládané do vytvářeného seznamu.
     * @return Požadovaný seznam
     */
    @SuppressWarnings("unchecked")
    public static <E> ArrayList<E> newArrayList(E... elements)
    {
        ArrayList<E> list = new ArrayList<>(Arrays.asList(elements));
        return list;
    }


    /***************************************************************************
     * Vytvoří novou množinu a naplní ji zadanými hodnotami.
     *
     * @param <E> Typ prvků (elementů) vytvářené množiny
     * @param elements Hodnoty vkládané do vytvářené množiny.
     * @return Požadovaná množina
     */
    @SuppressWarnings("unchecked")
    public static <E> Set<E> newSet(E... elements)
    {
        return newHashSet(elements);
    }


    /***************************************************************************
     * Vytvoří novou instanci třídy {@link HashSet}
     * a naplní ji zadanými hodnotami.
     *
     * @param <E> Typ prvků (elementů) vytvářené množiny
     * @param elements Hodnoty vkládané do vytvářené množiny.
     * @return Požadovaná množina
     */
    @SuppressWarnings("unchecked")
    public static <E> HashSet<E> newHashSet(E... elements)
    {
        HashSet<E> set = new HashSet<>(Arrays.asList(elements));
        return set;
    }


    /***************************************************************************
     * Vytvoří novou instanci třídy {@link HashSet}
     * a naplní ji zadanými hodnotami.
     *
     * @param <E> Typ prvků (elementů) vytvářené množiny
     * @param elements Hodnoty vkládané do vytvářené množiny.
     * @return Požadovaná množina
     */
    @SuppressWarnings("unchecked")
    public static <E> HashSet<E> newLinkedHashSet(E... elements)
    {
        HashSet<E> set = new LinkedHashSet<>(Arrays.asList(elements));
        return set;
    }


    /***************************************************************************
     * Vytvoří novou instanci třídy {@link HashSet}
     * a naplní ji zadanými hodnotami.
     *
     * @param <E> Typ prvků (elementů) vytvářené množiny
     * @param elements Hodnoty vkládané do vytvářené množiny.
     * @return Požadovaná množina
     */
    @SuppressWarnings("unchecked")
    public static <E extends Enum<E>> EnumSet<E> newEnumSet(E... elements)
    {
        if (elements.length == 0) {
            throw new RuntimeException(
                "\nTato metoda neumí vytvořit prázdnou množinu - použijte " +
                  "java.util.EnumSet.noneOf(Class<E>)");
        }
        EnumSet<E> set = EnumSet.of(elements[0], elements);
        return set;
    }


    /***************************************************************************
     * Vytvoří novou instanci typu {@link Map.Entry}.
     *
     * @param <K>   Typ klíče vytvářeného objektu
     * @param <V>   Typ hodnoty vytvářeného objektu
     * @param key   Klíč vytvářeného objektu
     * @param value Hodnota  vytvářeného objektu
     * @return Vytvořená instance
     */
    @SuppressWarnings("unchecked")
    public static <K, V> Map.Entry<K,V> e(K key, V value)
    {
        return new AbstractMap.SimpleEntry<>(key, value);
    }


    /***************************************************************************
     * Pomocí zadaného továrního objektu vytvoří novou mapu
     * a naplní ji zadanými hodnotami.
     *
     * @param <K>        Typ klíče vytvářeného objektu
     * @param <V>        Typ hodnoty vytvářeného objektu
     * @param mapFactory Tovární objekt vytvářející prázdnou mapu
     *                   s kapacitou zadané velikosti
     * @param elements   Položky vytvářené mapy
     * @return Požadovaná mapa
     */
    @SuppressWarnings("unchecked")
    public static <K, V> Map<K,V> newMap(IntFunction<Map<K,V>> mapFactory,
                                         Map.Entry<K,V>... elements)
    {
        Map<K,V> map = mapFactory.apply(elements.length);
        for (Entry<K, V> entry : elements) {
            map.put(entry.getKey(), entry.getValue());
        }
        return map;
    }


    /***************************************************************************
     * Vytvoří novou mapu typu {@link HashMap} a naplní ji zadanými hodnotami.
     *
     * @param <K>        Typ klíče vytvářeného objektu
     * @param <V>        Typ hodnoty vytvářeného objektu
     * @param elements   Položky vytvářené mapy
     * @return Požadovaná mapa
     */
    @SuppressWarnings("unchecked")
    public static <K, V> HashMap<K,V> newHashMap(Map.Entry<K,V>... elements)
    {
        IntFunction<Map<K,V>> mapFactory = HashMap::new;
        Map<K,V> map = newMap(mapFactory, elements);
        return (HashMap<K, V>)map;
    }


    /***************************************************************************
     * Vytvoří novou mapu typu {@link LinkedHashMap}
     * a naplní ji zadanými hodnotami.
     *
     * @param <K>        Typ klíče vytvářeného objektu
     * @param <V>        Typ hodnoty vytvářeného objektu
     * @param elements   Položky vytvářené mapy
     * @return Požadovaná mapa
     */
    @SuppressWarnings("unchecked")
    public static <K, V> LinkedHashMap<K,V> newLinkedHashMap(
                                               Map.Entry<K,V>... elements)
    {
        IntFunction<Map<K,V>> mapFactory = LinkedHashMap::new;
        Map<K,V> map = newMap(mapFactory, elements);
        return (LinkedHashMap<K, V>)map;
    }


    /***************************************************************************
     * Vrátí informaci o tom, jsou-li zadané dvě pole řetězců shodná
     * neuvažujeme-li velikost písmen, tj. jsou-li obě pole shodně velká a
     * jsou-li příslušně shodné jejich vzájemně si odpovídající položky.
     *
     * @param arr1 První pole
     * @param arr2 Druhý pole
     * @return Požadovaná informace
     */
    public static boolean strArrEqualsIgnoreCase(String[] arr1, String[] arr2)
    {
        if (arr1.length != arr2.length) {
            return false;
        }

        for (int i=0;   i < arr1.length;   i++) {
            if (! arr1[i].equalsIgnoreCase(arr2[i])) {
                return false;
            }
        }
        return true;
    }



//== PRIVATE AND AUXILIARY CLASS METHODS =======================================

    /***************************************************************************
     * Pokud se do bufferu vůbec něco zapsalo, odebere poslední oddělovač;
     * poté přidá uzavírací závorku.
     *
     * @param sb Uzavíraný {@code StringBuilder}
     * @return Vrátí zadaný {@code StringBuilder}
     */
    private static String closedStringBuilder(StringBuilder sb)
    {
        int sbl = sb.length();
        if (sbl > 1) {
            sb.delete(sb.length()-SEP_LENGTH, sb.length());
        }
        sb.append(ZP);
        return sb.toString();
    }



//##############################################################################
//== CONSTANT INSTANCE ATTRIBUTES ==============================================
//== VARIABLE INSTANCE ATTRIBUTES ==============================================



//##############################################################################
//== CONSTRUCTORS AND FACTORY METHODS ==========================================

    /***************************************************************************
     * Soukromý konstruktor zabraňující vytvoření instance.
     */
    private ContainerUtil() {}



//== ABSTRACT METHODS ==========================================================
//== INSTANCE GETTERS AND SETTERS ==============================================
//== OTHER NON-PRIVATE INSTANCE METHODS ========================================
//== PRIVATE AND AUXILIARY INSTANCE METHODS ====================================



//##############################################################################
//== NESTED DATA TYPES =========================================================
}
