
package appeng.items.parts;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import net.minecraft.util.ResourceLocation;

import appeng.api.parts.IPartModel;
import appeng.core.AELog;

/**
 * Helps with the reflection magic needed to gather all models for AE2 cable bus
 * parts.
 */
class PartModelsHelper {

    static List<ResourceLocation> createModels(Class<?> clazz) {
        List<ResourceLocation> locations = new ArrayList<>();

        // Check all static fields for used models
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (field.getAnnotation(PartModels.class) == null) {
                continue;
            }

            if (!Modifier.isStatic(field.getModifiers())) {
                AELog.error("The @PartModels annotation can only be used on static fields or methods. Was seen on: "
                        + field);
                continue;
            }

            Object value;
            try {
                field.setAccessible(true);
                value = field.get(null);
            } catch (IllegalAccessException e) {
                AELog.error(e, "Cannot access field annotated with @PartModels: " + field);
                continue;
            }

            convertAndAddLocation(field, value, locations);
        }

        // Check all static methods for the annotation
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.getAnnotation(PartModels.class) == null) {
                continue;
            }

            if (!Modifier.isStatic(method.getModifiers())) {
                AELog.error("The @PartModels annotation can only be used on static fields or methods. Was seen on: "
                        + method);
                continue;
            }

            // Check for parameter count
            if (method.getParameters().length != 0) {
                AELog.error(
                        "The @PartModels annotation can only be used on static methods without parameters. Was seen on: "
                                + method);
                continue;
            }

            // Make sure we can handle the return type
            Class<?> returnType = method.getReturnType();
            if (!ResourceLocation.class.isAssignableFrom(returnType)
                    && !Collection.class.isAssignableFrom(returnType)) {
                AELog.error(
                        "The @PartModels annotation can only be used on static methods that return a ResourceLocation or Collection of "
                                + "ResourceLocations. Was seen on: " + method);
                continue;
            }

            Object value = null;
            try {
                method.setAccessible(true);
                value = method.invoke(null);
            } catch (IllegalAccessException | InvocationTargetException e) {
                AELog.error(e, "Failed to invoke the @PartModels annotated method " + method);
                continue;
            }

            convertAndAddLocation(method, value, locations);
        }

        if (clazz.getSuperclass() != null) {
            locations.addAll(createModels(clazz.getSuperclass()));
        }

        return locations;
    }

    private static void convertAndAddLocation(Object source, Object value, List<ResourceLocation> locations) {
        if (value == null) {
            return;
        }

        if (value instanceof ResourceLocation) {
            locations.add((ResourceLocation) value);
        } else if (value instanceof IPartModel) {
            locations.addAll(((IPartModel) value).getModels());
        } else if (value instanceof Collection) {
            // Check that each object is an IPartModel
            Collection values = (Collection) value;
            for (Object candidate : values) {
                if (!(candidate instanceof IPartModel)) {
                    AELog.error("List of locations obtained from {} contains a non resource location: {}", source,
                            candidate);
                    continue;
                }

                locations.addAll(((IPartModel) candidate).getModels());
            }
        }
    }

}
