/*
 *  @author Ranides Atterwim <ranides@gmail.com>
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.enums;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.ranides.assira.math.Bitwise;
import net.ranides.assira.reflection.inspect.BeanInspector;
import net.ranides.assira.text.StrBuilder;
import net.ranides.assira.trace.AdvLogger;
import net.ranides.assira.trace.LoggerUtils;

/**
 * 
 * @author ranides
 */
public final class EnumUtils {

    private static final AdvLogger LOGGER = LoggerUtils.getLogger();

    private EnumUtils() {
        // utility class
    }

    /**
     * Pobiera dla wszystkich wartości zadeklarowanych w typie enum atrybut JavaBeans,
     * i tworzy mapę, w której kluczem jest wartość atrybutu, a wartością stała
     * enum. Jeśli uzyskane klucze nie tworzą unikalnego zbioru, to rzucany jest
     * wyjątek {@link EnumDuplicateError}
     * @param property
     * @throws EnumDuplicateKey
     */
    public static <E extends Enum<E>> Map<Object, E> mapBean(Class<E> eenum, String property) {
        List<E> items = Arrays.asList(eenum.getEnumConstants());
        List<?> values = BeanInspector.forClass(eenum).each(items, property);
        Map<Object, E> map = new HashMap<>();

        for(int i=0, n=items.size(); i<n; i++) {
            Object key = values.get(i);
            E current = items.get(i);
            if( map.containsKey(key) ) {
                E prev = map.get(key);
                throw new EnumDuplicateError(property, key, prev, current);
            }
            map.put(key, current);
        }
        return map;
    }

    public static <K, E extends Enum<E> & EnumHash<K>> Map<K, E> mapHash(Class<E> eenum) {
        Map<K, E> map = new HashMap<>();

        for(E current : eenum.getEnumConstants()) {
            K key = current.toEnumKey();
            if( map.containsKey(key) ) {
                E prev = map.get(key);
                throw new EnumDuplicateError("toEnumKey", key, prev, current);
            }
            map.put(key, current);
        }
        return map;
    }

    public static <E extends Enum<E> & EnumInt> Map<Integer, E> mapInt(Class<E> eenum) {
        Map<Integer, E> map = new HashMap<>();

        for(E current : eenum.getEnumConstants()) {
            int key = current.toEnumKey();
            if( map.containsKey(key) ) {
                E prev = map.get(key);
                throw new EnumDuplicateError("toEnumKey", key, prev, current);
            }
            map.put(key, current);
        }
        return map;
    }

    public static <E extends Enum<E> & EnumInt> int encodeInt(Set<E> values) {
        int result = 0;
        for(E item : values) {
            result |= item.toEnumKey();
        }
        return result;
    }

    public static <E extends Enum<E> & EnumInt> EnumSet<E> decodeInt(Class<E> eenum, int flags) {
        EnumSet<E> result = EnumSet.noneOf(eenum);
        for(E item : eenum.getEnumConstants()) {
            if( Bitwise.has(flags, item.toEnumKey()) ) {  result.add(item); }
        }
        return result;
    }

    public static <E extends Enum<E>> String encodeText(Set<E> values) {
        StrBuilder result = new StrBuilder();
        result.list(" ");
        for(E item : values) {
            result.item().append(item.name());
        }
        return result.toString();
    }

    public static <E extends Enum<E>> EnumSet<E> decodeText(Class<E> eenum, String text) {
        EnumSet<E> result = EnumSet.noneOf(eenum);
        for(String item : text.split("[ ,]+")) {
            try {
                result.add(Enum.valueOf(eenum, item));
            } catch(IllegalArgumentException _1) {
                try {
                    result.add(Enum.valueOf(eenum, item));
                } catch(IllegalArgumentException _2) {
                    LOGGER.fwarn("Unknown enum value: %s : %s", eenum.getName(), item);
                }
            }
        }
        return result;
    }

}
