package com.lezhnin.yadi.api;

import static com.lezhnin.yadi.api.ServiceReference.fromTypes;
import static com.lezhnin.yadi.api.ServiceReference.toTypes;
import static java.util.Objects.requireNonNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.function.Supplier;
import javax.annotation.Nonnull;

public interface Dependency {

    @Nonnull
    ServiceReference<?>[] getReferences();

    interface ImmediateDependency<T> extends Dependency {

        @Nonnull
        Supplier<T> getSupplier();

        static <T> ImmediateDependency<T> immediate(@Nonnull final Supplier<T> supplier) {
            return new ImmediateDependency<T>() {
                @Nonnull
                @Override
                public ServiceReference<?>[] getReferences() {
                    return new ServiceReference<?>[0];
                }

                @Nonnull
                @Override
                public Supplier<T> getSupplier() {
                    return requireNonNull(supplier);
                }
            };
        }
    }

    interface MethodDependency extends Dependency {

        @Nonnull
        Method getMethod();

        static MethodDependency method(@Nonnull final Method method) {
            return method(
                    requireNonNull(method).getDeclaringClass(),
                    method.getName(),
                    fromTypes(method.getParameterTypes())
            );
        }

        static Method findMethod(@Nonnull final Class<?> from, @Nonnull final String name, @Nonnull final Class<?>... parameters) {
            try {
                return requireNonNull(from).getMethod(requireNonNull(name), requireNonNull(parameters));
            } catch (final NoSuchMethodException e) {
                throw new MethodNotFoundException(from, name, parameters, e);
            }
        }

        static MethodDependency method(@Nonnull final Class<?> from,
                                       @Nonnull final String name,
                                       @Nonnull final ServiceReference<?>... parameters) {
            return new MethodDependency() {
                @Nonnull
                @Override
                public Method getMethod() {
                    return MethodDependency.findMethod(requireNonNull(from), requireNonNull(name), toTypes(parameters));
                }

                @Nonnull
                @Override
                public ServiceReference<?>[] getReferences() {
                    return requireNonNull(parameters);
                }
            };
        }
    }

    interface ConstructorDependency<T> extends Dependency {

        Constructor<T> getConstructor();

        static <T> ConstructorDependency<T> constructor(@Nonnull final Constructor<T> constructor) {
            return constructor(
                    requireNonNull(constructor).getDeclaringClass(),
                    fromTypes(constructor.getParameterTypes())
            );
        }

        static <T> Constructor<T> findConstructor(@Nonnull final Class<T> from, @Nonnull final Class<?>... parameters) {
            try {
                return requireNonNull(from).getConstructor(requireNonNull(parameters));
            } catch (final NoSuchMethodException e) {
                throw new MethodNotFoundException(from, "constructor", parameters, e);
            }
        }

        static <T> ConstructorDependency<T> constructor(@Nonnull final Class<T> from, @Nonnull final ServiceReference<?>... parameters) {
            return new ConstructorDependency<T>() {
                @Nonnull
                @Override
                public Constructor<T> getConstructor() {
                    return ConstructorDependency.findConstructor(requireNonNull(from), toTypes(parameters));
                }

                @Nonnull
                @Override
                public ServiceReference<?>[] getReferences() {
                    return requireNonNull(parameters);
                }
            };
        }
    }
}

@@ -1200,32 +1200,41 @@
 ependency method
+FromClass
 (@Nonnull final 
@@ -1275,16 +1275,25 @@
 n method
+FromClass
 (%0A      
@@ -1500,25 +1500,49 @@
 d findMethod
-(
+InClass(%0A                
 @Nonnull fin
@@ -1554,24 +1554,40 @@
 ass%3C?%3E from,
+%0A               
  @Nonnull fi
@@ -1602,16 +1602,32 @@
 ng name,
+%0A               
  @Nonnul
@@ -1648,32 +1648,41 @@
 ?%3E... parameters
+%0A        
 ) %7B%0A            
@@ -1987,17 +1987,43 @@
 y method
-(
+FromClass(%0A                
 @Nonnull
@@ -2064,82 +2064,1259 @@
     
-                       @Nonnull final String name,%0A                       
+@Nonnull final String name,%0A                @Nonnull final ServiceReference%3C?%3E... parameters%0A        ) %7B%0A            return new MethodDependency() %7B%0A                @Nonnull%0A                @Override%0A                public Method getMethod() %7B%0A                    return MethodDependency.findMethodInClass(requireNonNull(from), requireNonNull(name), toTypes(parameters));%0A                %7D%0A%0A                @Nonnull%0A                @Override%0A                public ServiceReference%3C?%3E%5B%5D getReferences() %7B%0A                    return requireNonNull(parameters);%0A                %7D%0A            %7D;%0A        %7D%0A    %7D%0A%0A    interface InstanceMethodDependency%3CT%3E extends MethodDependency %7B%0A%0A        @Nonnull%0A        T getInstance();%0A%0A        static %3CT%3E InstanceMethodDependency%3CT%3E methodFromInstance(%0A                @Nonnull final T instance,%0A                @Nonnull final Method method%0A        ) %7B%0A            return methodFromInstance(%0A                    requireNonNull(instance),%0A                    method.getName(),%0A                    fromTypes(method.getParameterTypes())%0A            );%0A        %7D%0A%0A        static %3CT%3E InstanceMethodDependency%3CT%3E methodFromInstance(%0A                @Nonnull final T instance,%0A                @Nonnull final String name,%0A
     
@@ -3367,32 +3367,41 @@
 ?%3E... parameters
+%0A        
 ) %7B%0A            
@@ -3403,32 +3403,40 @@
      return new 
+Instance
 MethodDependency
@@ -3427,37 +3427,204 @@
 MethodDependency
+%3CT%3E
 () %7B%0A
+                @Nonnull%0A                @Override%0A                public T getInstance() %7B%0A                    return requireNonNull(instance);%0A                %7D%0A%0A
                 
@@ -3752,24 +3752,31 @@
 y.findMethod
+InClass
 (requireNonN
@@ -3771,36 +3771,51 @@
 (requireNonNull(
-from
+instance).getClass(
 ), requireNonNul
@@ -4519,32 +4519,49 @@
 findConstructor(
+%0A                
 @Nonnull final C
@@ -4565,32 +4565,48 @@
 l Class%3CT%3E from,
+%0A               
  @Nonnull final 
@@ -4619,32 +4619,41 @@
 ?%3E... parameters
+%0A        
 ) %7B%0A            
@@ -4960,32 +4960,49 @@
 %3CT%3E constructor(
+%0A                
 @Nonnull final C
@@ -5014,16 +5014,32 @@
 T%3E from,
+%0A               
  @Nonnul
@@ -5079,16 +5079,25 @@
 rameters
+%0A        
 ) %7B%0A    

add InstanceMethodDependency