package eu.jonahbauer.wizard.common.util;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Modifier;
import java.util.Locale;

public final class SealedClassTypeAdapterFactory<T> implements TypeAdapterFactory {
    private final RuntimeTypeAdapterFactory<T> factory;

    public static <T> SealedClassTypeAdapterFactory<T> of(Class<T> clazz) {
        return new SealedClassTypeAdapterFactory<>(clazz, null);
    }

    public static <T> SealedClassTypeAdapterFactory<T> of(Class<T> clazz, @Nullable String suffix) {
        return new SealedClassTypeAdapterFactory<>(clazz, suffix);
    }

    private SealedClassTypeAdapterFactory(Class<T> clazz, @Nullable String suffix) {
        factory = RuntimeTypeAdapterFactory.of(clazz);
        register(clazz, suffix);
    }

    private void register(Class<? extends T> clazz, String suffix) {
        for (Class<?> subclass : clazz.getPermittedSubclasses()) {
            int modifiers = subclass.getModifiers();
            if (Modifier.isFinal(modifiers) || subclass.isSealed() && !Modifier.isAbstract(modifiers)) {
                String name = subclass.getSimpleName();

                // remove suffix
                if (suffix != null) {
                    if (name.endsWith(suffix)) name = name.substring(0, name.length() - suffix.length());
                }

                // transform camelCast to snake_case
                name = name.replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase(Locale.ROOT);

                factory.registerSubtype(subclass.asSubclass(clazz), name);
            }
            if (subclass.isSealed()) {
                register(subclass.asSubclass(clazz), suffix);
            } else if (!Modifier.isFinal(modifiers)) {
                //subclass is non-sealed
                throw new IllegalArgumentException(
                        "SealedClassTypeAdapterFactory does not support a type hierarchy that contains non-sealed classes. " +
                        "Found non-sealed class " + subclass.getName()
                );
            }
        }

    }

    @Override
    public <S> TypeAdapter<S> create(Gson gson, TypeToken<S> type) {
        return factory.create(gson, type);
    }
}
