package org.pac4j.jax.rs.features.jersey;

import java.util.Optional;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.Context;

import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver;
import org.glassfish.jersey.server.model.Parameter;
import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider;
import org.pac4j.core.config.Config;
import org.pac4j.core.context.J2EContext;
import org.pac4j.core.profile.CommonProfile;
import org.pac4j.core.profile.ProfileManager;
import org.pac4j.jax.rs.annotations.Pac4JProfile;

/**
 * {@link Pac4JProfile &#64;Profile} injection value factory provider.
 * 
 * Register a new {@link Binder} in order to enable this.
 * 
 * @author Victor Noel - Linagora
 *
 */
@Singleton
public class Pac4JProfileValueFactoryProvider extends AbstractValueFactoryProvider {

    private final Config config;

    @Inject
    protected Pac4JProfileValueFactoryProvider(MultivaluedParameterExtractorProvider mpep, ServiceLocator locator,
            ConfigProvider configProvider) {
        super(mpep, locator, Parameter.Source.UNKNOWN);
        this.config = configProvider.config;
    }

    @Override
    protected Factory<?> createValueFactory(Parameter parameter) {
        assert parameter != null;
        if (!parameter.isAnnotationPresent(Pac4JProfile.class)) {
            return null;
        } else if (!CommonProfile.class.isAssignableFrom(parameter.getRawType())) {
            throw new IllegalStateException(
                    "Cannot inject a Pac4J profile into a parameter of type " + parameter.getRawType().getName());
        } else {
            return new ProfileValueFactory(config, parameter);
        }
    }

    @Singleton
    static class ProfileInjectionResolver extends ParamInjectionResolver<Pac4JProfile> {
        ProfileInjectionResolver() {
            super(Pac4JProfileValueFactoryProvider.class);
        }
    }

    @Singleton
    static class ConfigProvider {

        private final Config config;

        ConfigProvider(Config config) {
            this.config = config;
        }
    }

    public static class Binder extends AbstractBinder {

        private final Config config;

        public Binder(Config config) {
            this.config = config;
        }

        @Override
        protected void configure() {
            bind(new ConfigProvider(config)).to(ConfigProvider.class);
            bind(Pac4JProfileValueFactoryProvider.class).to(ValueFactoryProvider.class).in(Singleton.class);
            bind(ProfileInjectionResolver.class).to(new TypeLiteral<InjectionResolver<Pac4JProfile>>() {
            }).in(Singleton.class);
        }
    }

    static class ProfileValueFactory implements Factory<CommonProfile> {

        @Context
        private HttpServletRequest request;

        private final Config config;

        private final Parameter parameter;

        public ProfileValueFactory(Config config, Parameter parameter) {
            this.config = config;
            this.parameter = parameter;
        }

        @Override
        public CommonProfile provide() {
            // we don't need the response for this
            final J2EContext context = new J2EContext(request, null, config.getSessionStore());
            final Optional<CommonProfile> profile = new ProfileManager<>(context).get(true);
            if (profile.isPresent()) {
                final CommonProfile p = profile.get();
                if (parameter.getRawType().isInstance(p)) {
                    return p;
                } else {
                    throw new IllegalStateException("Cannot inject a Pac4J profile of type "
                            + p.getClass().getName() + " into a parameter of type " + parameter.getRawType().getName());
                }
            } else {
                throw new IllegalStateException(
                        "Cannot inject a Pac4J profile into an unauthenticated request");
            }
        }

        @Override
        public void dispose(CommonProfile instance) {
            // nothing
        }
    }
}

@@ -1022,16 +1022,73 @@
 Profile;
+%0Aimport org.slf4j.Logger;%0Aimport org.slf4j.LoggerFactory;
 %0A%0A/**%0A *
@@ -3259,24 +3259,119 @@
 Profile%3E %7B%0A%0A
+        private static final Logger LOG = LoggerFactory.getLogger(ProfileValueFactory.class);%0A%0A
         @Con
@@ -4192,38 +4192,80 @@
     
-throw new IllegalStateExceptio
+// this is most certainly a programmer error%0A                    LOG.war
 n(%22C
@@ -4301,17 +4301,48 @@
 of type 
-%22
+%7B%7D into a parameter of type %7B%7D%22,
 %0A       
@@ -4361,18 +4361,16 @@
         
- +
  p.getCl
@@ -4388,41 +4388,9 @@
 me()
- + %22 into a parameter of type %22 +
+,
  par
@@ -4413,32 +4413,65 @@
 e().getName());%0A
+                    return null;%0A
                 
@@ -4513,49 +4513,73 @@
     
-throw new IllegalStateException(%0A        
+// there could be reason for that (after a callback for example)%0A
     
@@ -4582,32 +4582,42 @@
                 
+LOG.debug(
 %22Cannot inject a
@@ -4666,16 +4666,45 @@
 uest%22);%0A
+                return null;%0A
         

Allow for null profile during injection