/*
 * Copyright 2013 Red Hat, Inc. and/or its affiliates.
 *
 * 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.kie.spring.factorybeans;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.drools.persistence.api.TransactionManagerFactory;
import org.jbpm.runtime.manager.impl.SimpleRuntimeEnvironment;
import org.kie.api.runtime.EnvironmentName;
import org.kie.api.runtime.manager.RuntimeEnvironment;
import org.kie.api.runtime.manager.RuntimeManager;
import org.kie.api.runtime.manager.RuntimeManagerFactory;
import org.kie.internal.runtime.conf.ObjectModelResolver;
import org.kie.internal.runtime.conf.ObjectModelResolverProvider;
import org.kie.internal.runtime.manager.RuntimeManagerRegistry;
import org.kie.spring.persistence.KieSpringTransactionManagerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;

/**
 * FactoryBean responsible to create instances of <code>RuntimeManager</code> of given type based on provided
 * runtimeEnvironment (which is a mandatory property).
 * Supported types:
 * <ul>
 *     <li>SINGLETON</li>
 *     <li>PER_REQUEST</li>
 *     <li>PER_PROCESS_INSTANCE</li>
 * </ul>
 * where default is SINGLETON when no type is specified. <br/>
 * Every runtime manager must be uniquely identified thus <code>identifier</code> is a mandatory property.
 * <br/>
 * All instances created by this factory are cached to be able to properly dispose them using destroy method (close()).
 */
public class RuntimeManagerFactoryBean implements FactoryBean, InitializingBean, ApplicationContextAware {

    private ApplicationContext applicationContext;

    private Set<RuntimeManager> runtimeManagerSet = new HashSet<RuntimeManager>();

    private RuntimeEnvironment runtimeEnvironment;
    private String identifier;
    private String type = "SINGLETON";

    private RuntimeManagerFactory factory = RuntimeManagerFactory.Factory.get();

    @Override
    public Object getObject() throws Exception {
        if(RuntimeManagerRegistry.get().isRegistered(getIdentifier())) {
            // get the runtime manager from the registry
            return RuntimeManagerRegistry.get().getManager(getIdentifier());
        }
        else {
            TransactionManagerFactory transactionManagerFactory = TransactionManagerFactory.get();
            if (transactionManagerFactory instanceof KieSpringTransactionManagerFactory) {
                ((KieSpringTransactionManagerFactory) transactionManagerFactory).setGlobalTransactionManager(applicationContext.getBean(AbstractPlatformTransactionManager.class));
            }
            
            RuntimeManager manager = null;
            if ("PER_REQUEST".equalsIgnoreCase(type)) {
                disallowSharedTaskService(runtimeEnvironment);
                manager = factory.newPerRequestRuntimeManager(runtimeEnvironment, identifier);
            } else if ("PER_PROCESS_INSTANCE".equalsIgnoreCase(type)) {
                disallowSharedTaskService(runtimeEnvironment);
                manager = factory.newPerProcessInstanceRuntimeManager(runtimeEnvironment, identifier);
            } else {
                manager = factory.newSingletonRuntimeManager(runtimeEnvironment, identifier);
            }
            

            runtimeManagerSet.add(manager);
            return manager;
        }
    }

    @Override
    public Class<?> getObjectType() {
        return RuntimeManager.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (runtimeEnvironment == null && identifier == null) {
            throw new IllegalArgumentException("RuntimeEnvironment and identifier needs to be set");
        }

    }

    public RuntimeEnvironment getRuntimeEnvironment() {
        return runtimeEnvironment;
    }

    public void setRuntimeEnvironment(RuntimeEnvironment runtimeEnvironment) {
        this.runtimeEnvironment = runtimeEnvironment;
    }

    public String getIdentifier() {
        return identifier;
    }

    public void setIdentifier(String identifier) {
        this.identifier = identifier;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public void close() {
        for (RuntimeManager manager : runtimeManagerSet) {
            manager.close();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        
        // init any spring based ObjectModelResolvers
        List<ObjectModelResolver> resolvers = ObjectModelResolverProvider.getResolvers();
        if (resolvers != null) {
            for (ObjectModelResolver resolver : resolvers) {
                if (resolver instanceof ApplicationContextAware) {
                    ((ApplicationContextAware) resolver).setApplicationContext(applicationContext);
                }
            }
        }
    }

    protected void disallowSharedTaskService(RuntimeEnvironment environment) {

        if (((SimpleRuntimeEnvironment)environment).getEnvironmentTemplate().get("org.kie.api.task.TaskService") != null) {
            throw new IllegalStateException("Per process instance and per request runtime manager do not support shared task service");
        }
    }
}
