package org.qi4j.library.framework.validation;

import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import org.qi4j.api.annotation.AppliesTo;
import org.qi4j.api.annotation.AppliesToFilter;
import org.qi4j.api.annotation.scope.AssertionFor;
import org.qi4j.api.annotation.scope.Invocation;
import org.qi4j.api.annotation.scope.ThisAs;

/**
 * TODO
 */
@AppliesTo( AbstractAnnotationValidatableAssertion.AppliesTo.class )
public abstract class AbstractAnnotationValidatableAssertion<R extends Annotation, T>
    implements InvocationHandler
{
    private Class annotationType;

    public static class AppliesTo
        implements AppliesToFilter
    {
        public boolean appliesTo( Method method, Class mixin, Class compositeType, Class modifierClass )
        {
            Class annotationClass = (Class) ( (ParameterizedType) modifierClass.getGenericSuperclass() ).getActualTypeArguments()[ 0 ];
            Annotation[][] annotations = method.getParameterAnnotations();
            for( Annotation[] annotation : annotations )
            {
                for( Annotation annotation1 : annotation )
                {
                    if( annotationClass.isInstance( annotation1 ) )
                    {
                        return true;
                    }
                }
            }
            return false;
        }
    }

    @ThisAs ValidationMessages messages;
    @AssertionFor InvocationHandler next;

    Method method;
    String message;
    String resourceBundle;

    List<Method> annotationMethods = new ArrayList<Method>();

    public void setMethod( @Invocation Method method )
    {
        annotationType = (Class) ( (ParameterizedType) this.getClass().getGenericSuperclass() ).getActualTypeArguments()[ 0 ];

        this.method = method;

        if( method.getName().startsWith( "set" ) )
        {
            message = Introspector.decapitalize( method.getName().substring( 3 ) );
        }
        else
        {
            message = method.getName();
        }

        String name = annotationType.getSimpleName();
        StringBuffer messageBuf = new StringBuffer();
        for( int i = 0; i < name.length(); i++ )
        {
            char ch = name.charAt( i );
            if( Character.isUpperCase( ch ) )
            {
                messageBuf.append( '.' );
                messageBuf.append( Character.toLowerCase( ch ) );
            }
            else
            {
                messageBuf.append( ch );
            }
        }

        message += messageBuf.toString();

        resourceBundle = method.getDeclaringClass().getName();
        try
        {
            ResourceBundle bundle = ResourceBundle.getBundle( resourceBundle );
            bundle.getString( message );
        }
        catch( MissingResourceException e )
        {
            resourceBundle = method.getDeclaringClass().getPackage() + ".package";
            try
            {
                ResourceBundle bundle = ResourceBundle.getBundle( resourceBundle );
                bundle.getString( message );
            }
            catch( MissingResourceException e1 )
            {
                // No bundle
                resourceBundle = null;
            }
        }

        Method[] params = annotationType.getMethods();
        for( Method param : params )
        {
            if( !param.getDeclaringClass().equals( Annotation.class ) )
            {
                annotationMethods.add( param );
            }
        }
    }

    public Object invoke( Object object, Method method, Object[] objects ) throws Throwable
    {
        Annotation[][] annotations = method.getParameterAnnotations();
        int i = 0;
        for( Annotation[] annotation : annotations )
        {
            for( Annotation annotation1 : annotation )
            {
                if( annotationType.isInstance( annotation1 ) )
                {
                    T arg = (T) objects[ i ];
                    boolean valid = isValid( (R) annotation1, arg );
                    if( !valid )
                    {
                        Object[] paramValues = new Object[annotationMethods.size() + 1];
                        paramValues[ 0 ] = objects[ i ];
                        for( int j = 0; j < annotationMethods.size(); j++ )
                        {
                            Object paramValue = annotationMethods.get( j ).invoke( annotation1 );
                            paramValues[ i + 1 ] = paramValue;
                        }
                        messages.addValidationMessage( newMessage( paramValues ) );
                    }
                    else
                    {
                        messages.removeValidationMessage( message );
                    }
                }
            }
            i++;
        }

        return next.invoke( object, method, objects );
    }

    protected abstract boolean isValid( R annotation, T argument );

    private ValidationMessage newMessage( Object[] params )
    {
        return new ValidationMessage( message, resourceBundle, ValidationMessage.Severity.ERROR, params );
    }
}

@@ -4234,16 +4234,101 @@
  valid =
+ false;%0A                    try%0A                    %7B%0A                        valid =
  isValid
@@ -4369,24 +4369,28 @@
             
+    
 if( !valid )
@@ -4402,32 +4402,36 @@
                 
+    
 %7B%0A              
@@ -4436,24 +4436,28 @@
             
+ 
+   
 Object%5B%5D par
@@ -4525,32 +4525,36 @@
                 
+    
 paramValues%5B 0 %5D
@@ -4586,32 +4586,36 @@
                 
+  
+  
 for( int j = 0; 
@@ -4666,34 +4666,41 @@
                 
-%7B%0A
+    %7B%0A   
                 
@@ -4707,24 +4707,25 @@
             
+ 
 Object param
@@ -4802,32 +4802,36 @@
                 
+    
 paramValues%5B i +
@@ -4865,32 +4865,36 @@
                 
+    
 %7D%0A              
@@ -4895,32 +4895,36 @@
                 
+ 
+   
 messages.addVali
@@ -4979,34 +4979,40 @@
                 
-%7D%0A
+    %7D%0A  
                 
@@ -5007,32 +5007,34 @@
                 
+  
 else%0A           
@@ -5034,34 +5034,42 @@
                 
-%7B%0A
+    %7B%0A    
                 
@@ -5113,32 +5113,188 @@
 age( message );%0A
+                        %7D%0A                    %7D%0A                    catch( NullPointerException e )%0A                    %7B%0A                        // Ignore%0A
                 

Handle NPE properly