package org.arquillian.cube.docker.impl.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class ContainerObjectUtil {

    private ContainerObjectUtil() {
        super();
    }

    public static <T extends Annotation> List<T> getAllAnnotations(final Class<?> source,
        final Class<T> annotationClass) {
        return AccessController.doPrivileged((PrivilegedAction<List<T>>) () -> {
            List<T> annotations = new ArrayList<>();

            Class<?> nextSource = source;
            while (nextSource != Object.class) {
                final T[] annotationsByType = nextSource.getAnnotationsByType(annotationClass);
                Collections.addAll(annotations, annotationsByType);
                //If not maybe we need to use the default value but maybe there is some parent class
                //That contains a different value rather than the default ones so we need to continue search.
                nextSource = nextSource.getSuperclass();
            }
            return annotations;
        });
    }

    public static <T> T getTopCubeAttribute(final Class<?> source, final String nameField,
        final Class<? extends Annotation> annotationClass, final T defaultValue) {
        return AccessController.doPrivileged((PrivilegedAction<T>) () -> {
            Method field = null;
            try {
                field = annotationClass.getMethod(nameField);
            } catch (NoSuchMethodException e) {
                throw new IllegalArgumentException(e);
            }
            Class<?> nextSource = source;
            boolean foundAnnotation = false;
            while (nextSource != Object.class) {
                if (nextSource.isAnnotationPresent(annotationClass)) {
                    foundAnnotation = true;
                    final Annotation annotation = nextSource.getAnnotation(annotationClass);
                    final T value = getValue(annotation, field);
                    //If it is not the default value means that we have found a top level definition
                    if (defaultValue.getClass().isArray()) {

                        if (defaultValue.getClass().getComponentType().isPrimitive()) {
                            // Only works with integer primitives or CCE
                            if (!Arrays.equals((int[]) value, (int[]) defaultValue)) {
                                return value;
                            }
                        } else {

                            if (!Arrays.equals((Object[]) value, (Object[]) defaultValue)) {
                                return value;
                            }
                        }
                    } else {
                        if (!value.equals(defaultValue)) {
                            return value;
                        }
                    }
                }
                //If not maybe we need to use the default value but maybe there is some parent class
                //That contains a different value rather than the default ones so we need to continue search.
                nextSource = nextSource.getSuperclass();
            }
            return foundAnnotation ? defaultValue : null;
        });
    }

    @SuppressWarnings("unchecked")
    private static <T> T getValue(final Annotation annotation, Method field) {
        try {
            return (T) field.invoke(annotation);
        } catch (InvocationTargetException | IllegalAccessException e) {
            throw new IllegalArgumentException(e);
        }
    }
}
