package net.ranides.assira.reflection.walker;

import lombok.experimental.UtilityClass;
import net.ranides.assira.collection.lists.ReversedList;
import net.ranides.assira.generic.TypeToken;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.reflection.walker.WalkerContexts.ObjectContext;

import java.util.*;
import java.util.function.Function;

/**
 * This is internal class containing utility methods
 */
@UtilityClass
class WalkerUtils {

    public static final Undefined UNDEFINED = new Undefined();

    public static final IClass<?> LIST = new TypeToken<List<?>>() {};
    public static final IClass<?> COLLECTION = new TypeToken<Collection<?>>() {};
    public static final IClass<?> MAP = new TypeToken<Map<?,?>>() {};

    public static final String PATH_TYPE = "/";
    public static final String PATH_NAME = "/";
    public static final String PATH_ID = ".";

    public static IClass<?> lower(IClass<?> a, IClass<?> b) {
        return a.isSubclass(b) ? a : b;
    }

    public static IClass<?> asList(IClass<?> declared) {
        return asType(declared, LIST);
    }

    private static IClass<?> asType(IClass<?> actual, IClass<?> expected) {
        return actual.interfaces().require(c -> c.raw().equals(expected.raw())).first().orElse(expected);
    }

    public static boolean isList(IClass<?> actual) {
        return actual.isSubclass(LIST);
    }

    public static boolean isList(IClass<?> declared, IClass<?> actual) {
        return isList(declared) || isList(actual);
    }

    public static boolean isCollection(IClass<?> actual) {
        return actual.isSubclass(COLLECTION);
    }

    public static boolean isCollection(IClass<?> declared, IClass<?> actual) {
        return isCollection(declared) || isCollection(actual);
    }

    public static boolean isMap(IClass<?> actual) {
        return actual.isSubclass(MAP);
    }

    public static boolean isMap(IClass<?> declared, IClass<?> actual) {
        return isMap(declared) || isMap(actual);
    }

    public static boolean isArray(IClass<?> actual) {
        return actual.isArray();
    }

    public static boolean isArray(IClass<?> declared, IClass<?> actual) {
        return isArray(declared) || isArray(actual);
    }

    public static String typePath(ObjectContext<?> ctx) {
        return ctx.isRoot() ? ctx.typeName() : ctx.parent().typePath() + PATH_TYPE + ctx.typeName();
    }

    public static List<IClass<?>> typeList(ObjectContext<?> ctx) {
        return list(ctx, ObjectContext::type);
    }

    public static String namePath(ObjectContext<?> ctx) {
        return ctx.isRoot() ? ctx.name() : ctx.parent().namePath() + PATH_NAME + ctx.name();
    }

    public static List<String> nameList(ObjectContext<?> ctx) {
        return list(ctx, ObjectContext::name);
    }

    public static String idPath(ObjectContext<?> ctx) {
        return ctx.isRoot() ? String.valueOf(ctx.id()) : ctx.parent().idPath() + PATH_ID + ctx.id();
    }

    private static <T> List<T> list(ObjectContext<?> ctx, Function<ObjectContext<?>, T> func) {
        List<T> names = new ArrayList<>();

        while (!ctx.isRoot()) {
            names.add(func.apply(ctx));
            ctx = ctx.parent();
        }

        names.add(func.apply(ctx));
        return new ReversedList<>(names);
    }

    /**
     * Internal class used as sentinel value for innacessible fields
     */
    public static class Undefined {
        @Override
        public String toString() {
            return "<undefined>";
        }
    }

}
