/*
 * Copyright (c) 2009, Niclas Hedhman. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.qi4j.api.service.qualifier;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.qi4j.api.service.ServiceReference;
import org.qi4j.functional.Specification;

/**
 * Filter services based on Meta Info being declared on the Service.
 * <p>
 * Meta Info of any type can be set on the service during assembly, e.g.;
 * </p>
 * <pre><code>
 * module.addService( MyService.class ).setMetaInfo( new MyCustomInfo(someData) );
 * </code></pre>
 * <p>
 * and then at an injection point you can do this:
 * </p>
 * <pre><code>
 * &#64;Service &#64;HasMetaInfo(MyCustomInfo.class) MyService service;
 * </code></pre>
 * <p>
 * to get only a service that has a MyCustomInfo instance set as meta info.
 * </p>
 */
@Retention( RetentionPolicy.RUNTIME )
@Qualifier( HasMetaInfo.HasMetaInfoQualifier.class )
@Documented
public @interface HasMetaInfo
{
    /**
     * The Class(es) needed to have been defined in the Service meta info for a qualifier to evaluate true.
     *
     * @return One or more classes that should be defined in the service's meta info for the service to be considered
     *         qualified. If more than one class is defined, the {@code anded()} parameter will define if they must be
     *         AND'ed or OR'ed together.
     */
    Class[] value();

    /**
     * True if the Classes defined in the value() field should be AND'ed instead of OR'ed.
     *
     * @return If true, all the Class types defined in {@code value()} must be defined for the service for it to be
     *         qualified. If false, if any of the Class types defined in {@code value()} is defined for the service
     *         the service is qualified.
     */
    boolean anded() default false;

    /**
     * HasMetaInfo Annotation Qualifier.
     * See {@link HasMetaInfo}.
     */
    public static class HasMetaInfoQualifier
        implements AnnotationQualifier<HasMetaInfo>
    {
        @Override
        public <T> Specification<ServiceReference<?>> qualifier( final HasMetaInfo hasMetaInfo )
        {
            return new Specification<ServiceReference<?>>()
            {
                @Override
                @SuppressWarnings( {"raw", "unchecked"} )
                public boolean satisfiedBy( ServiceReference<?> service )
                {
                    for( Class metaInfoType : hasMetaInfo.value() )
                    {
                        Object metaInfo = service.metaInfo( metaInfoType );
                        if( hasMetaInfo.anded() )
                        {
                            if( metaInfo == null )
                            {
                                return false;
                            }
                        }
                        else
                        {
                            if( metaInfo != null )
                            {
                                return true;
                            }
                        }
                    }
                    return false;
                }
            };
        }
    }
}
