/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.as.ejb3.component.entity.interceptors;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Iterator;
import java.util.Set;

import org.jboss.as.ee.component.Component;
import org.jboss.as.ee.component.ComponentInstance;
import org.jboss.as.ee.component.ComponentView;
import org.jboss.as.ee.component.interceptors.InvocationType;
import org.jboss.as.ejb3.component.entity.EntityBeanComponent;
import org.jboss.as.ejb3.component.entity.EntityBeanComponentInstance;
import org.jboss.invocation.Interceptor;
import org.jboss.invocation.InterceptorContext;
import org.jboss.invocation.InterceptorFactory;
import org.jboss.invocation.InterceptorFactoryContext;
import org.jboss.msc.value.InjectedValue;

import static org.jboss.as.ejb3.EjbMessages.MESSAGES;

/**
 * Interceptor that hooks up finder methods for BMP entity beans
 * <p/>
 * This is a view level interceptor that should be attached to finder methods on the home interface.
 *
 * @author Stuart Douglas
 */
public class EntityBeanHomeFinderInterceptorFactory implements InterceptorFactory {

    protected enum ReturnType {
        COLLECTION,
        ENUMERATION,
        SINGLE
    }

    private final Method finderMethod;
    private final ReturnType returnType;
    private final InjectedValue<ComponentView> viewToCreate = new InjectedValue<ComponentView>();

    public EntityBeanHomeFinderInterceptorFactory(final Method finderMethod) {
        this.finderMethod = finderMethod;
        if (Collection.class.isAssignableFrom(finderMethod.getReturnType())) {
            returnType = ReturnType.COLLECTION;
        } else if (Enumeration.class.isAssignableFrom(finderMethod.getReturnType())) {
            returnType = ReturnType.ENUMERATION;
        } else {
            returnType = ReturnType.SINGLE;
        }
    }

    @Override
    public Interceptor create(final InterceptorFactoryContext context) {

        final EntityBeanComponent component = (EntityBeanComponent) context.getContextData().get(Component.class);

        return new Interceptor() {
            @Override
            public Object processInvocation(final InterceptorContext context) throws Exception {

                //grab a bean from the pool to invoke the finder method on
                final EntityBeanComponentInstance instance = component.acquireUnAssociatedInstance();
                final Object result;
                final InvocationType invocationType = context.getPrivateData(InvocationType.class);
                try {
                    context.putPrivateData(InvocationType.class, InvocationType.FINDER_METHOD);
                    result = invokeFind(context, instance);
                    return prepareResults(context, result, component);
                } finally {
                    component.releaseEntityBeanInstance(instance);
                    context.putPrivateData(InvocationType.class, invocationType);
                }
            }

        };
    }

    protected Object prepareResults(final InterceptorContext context, final Object result, final EntityBeanComponent component) throws Exception {
        switch (returnType) {
            case COLLECTION: {
                Collection keys = (Collection) result;
                final Set<Object> results = new LinkedHashSet<Object>();
                if (keys != null) {
                    for (Object key : keys) {
                        results.add(getLocalObject(key));
                    }
                }
                return results;
            }
            case ENUMERATION: {
                Enumeration keys = (Enumeration) result;
                final Set<Object> results = new LinkedHashSet<Object>();
                if (keys != null) {
                    while (keys.hasMoreElements()) {
                        Object key = keys.nextElement();
                        results.add(getLocalObject(key));
                    }
                }
                final Iterator<Object> iterator = results.iterator();
                return new Enumeration<Object>() {

                    @Override
                    public boolean hasMoreElements() {
                        return iterator.hasNext();
                    }

                    @Override
                    public Object nextElement() {
                        return iterator.next();
                    }
                };
            }
            default: {
                if (result == null) {
                    throw MESSAGES.couldNotFindEntity(finderMethod, Arrays.toString(context.getParameters()));
                }
                return getLocalObject(result);
            }
        }
    }

    protected Object invokeFind(final InterceptorContext context, final EntityBeanComponentInstance instance) throws Exception {
        Method oldMethod = context.getMethod();
        try {
            context.putPrivateData(ComponentInstance.class, instance);
            context.setMethod(finderMethod);
            context.setTarget(instance.getInstance());
            return instance.getInterceptor(finderMethod).processInvocation(context);
        } finally {
            context.setMethod(oldMethod);
            context.setTarget(null);
            context.putPrivateData(ComponentInstance.class, null);
        }
    }

    protected Object getLocalObject(final Object result) throws Exception {
        final HashMap<Object, Object> create = new HashMap<Object, Object>();
        create.put(EntityBeanComponent.PRIMARY_KEY_CONTEXT_KEY, result);
        return viewToCreate.getValue().createInstance(create).getInstance();
    }

    public InjectedValue<ComponentView> getViewToCreate() {
        return viewToCreate;
    }

    public ReturnType getReturnType() {
        return returnType;
    }
}