package net.ranides.assira.generic;

import net.ranides.assira.collection.arrays.ArrayAllocator;
import net.ranides.assira.collection.arrays.ArrayUtils;
import net.ranides.assira.collection.maps.CacheMap;
import net.ranides.assira.functional.special.AnyFunction;
import net.ranides.assira.reflection.IAttribute;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.IField;

/**
 *
 * @param <E>
 */
public final class EnumFactory<E extends Enum<E>> {

    private static final String INCOMPATIBLE_JDK = "Incompatible JDK Version: can not create instances of enum at runtime";

    @SuppressWarnings("unchecked")
    private static final CacheMap<Class, EnumFactory> CACHE = CacheMap.getInstance(EnumFactory::new);

    private final AnyFunction<E> constructor;

    private final IField values;

    private EnumFactory(Class<E> type) {
        IClass<E> typeinfo = IClass.typeinfo(type);

        try {
            constructor = typeinfo.constructors()
                    .require(IAttribute.ANY)
                    .first()
                    .orElseThrow(() -> new UnsupportedOperationException(INCOMPATIBLE_JDK))
                    .accessible()
                    .accessor();

            values = typeinfo.field("$VALUES")
                    .orElseThrow(() -> new UnsupportedOperationException(INCOMPATIBLE_JDK))
                    .rewritable();
        } catch(RuntimeException cause) {
            throw new UnsupportedOperationException(INCOMPATIBLE_JDK, cause);
        }
    }

    @SuppressWarnings("unchecked")
    public static <E extends Enum<E>> EnumFactory<E> forType(Class<E> type) {
        return EnumFactory.CACHE.get(type);
    }

    public E newValue(String name, int ordinal) {
        return constructor.call(name, ordinal);
    }

    public E newValue(String name, int ordinal, Object... args) {
        return constructor.call(ArrayUtils.concat(new Object[]{name, ordinal}, args));
    }

    public EnumFactory<E> registerNewValue(String name, int ordinal) {
        return register(newValue(name, ordinal));
    }

    public EnumFactory<E> registerNewValue(String name, int ordinal, Object... args) {
        return register(newValue(name, ordinal, args));
    }

    public EnumFactory<E> register(E value) {
        Object[] oldValues = (Object[])values.get(null);
        Object[] newValues = ArrayAllocator.ensureCapacity(oldValues,value.ordinal()+1);
        if(oldValues != newValues) {
            values.set(null, newValues);
        }
        newValues[value.ordinal()] = value;
        return this;
    }

}
