/*
 * 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.jbpm.executor;


import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.EntityManagerFactory;

import org.jbpm.executor.impl.AvailableJobsExecutor;
import org.jbpm.executor.impl.ClassCacheManager;
import org.jbpm.executor.impl.ExecutorImpl;
import org.jbpm.executor.impl.ExecutorRunnable;
import org.jbpm.executor.impl.ExecutorServiceImpl;
import org.jbpm.executor.impl.event.ExecutorEventSupport;
import org.jbpm.executor.impl.jpa.ExecutorQueryServiceImpl;
import org.jbpm.executor.impl.jpa.ExecutorRequestAdminServiceImpl;
import org.jbpm.executor.impl.jpa.JPAExecutorStoreService;
import org.jbpm.executor.impl.mem.InMemoryExecutorAdminServiceImpl;
import org.jbpm.executor.impl.mem.InMemoryExecutorQueryServiceImpl;
import org.jbpm.executor.impl.mem.InMemoryExecutorStoreService;
import org.jbpm.shared.services.impl.TransactionalCommandService;
import org.kie.api.executor.Executor;
import org.kie.api.executor.ExecutorAdminService;
import org.kie.api.executor.ExecutorQueryService;
import org.kie.api.executor.ExecutorService;
import org.kie.api.executor.ExecutorStoreService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Creates singleton instance of <code>ExecutorService</code> that shall be used outside of CDI 
 * environment.
 */
public class ExecutorServiceFactory {

	private final static String mode = System.getProperty( "org.jbpm.cdi.executor.mode", "singleton" );
	
	private final static String availableJobsExecutorName = System.getProperty(
	    "org.jbpm.cdi.executor.jndi", "java:module/AvailableJobsExecutor" );

	private static final Logger logger = LoggerFactory.getLogger(ExecutorServiceFactory.class);
   
	private static ExecutorService serviceInstance;
    
    public static synchronized ExecutorService newExecutorService(EntityManagerFactory emf){
    	if ( mode.equalsIgnoreCase( "singleton" ) ) {
            if (serviceInstance == null) {
            	serviceInstance = configure(emf);
            }
            return serviceInstance;
        } else {
            return configure(emf);
        }        
    }
    
    public static synchronized ExecutorService newExecutorService(){
    	if ( mode.equalsIgnoreCase( "singleton" ) ) {
            if (serviceInstance == null) {
            	serviceInstance = configure();
            }
            return serviceInstance;
        } else {
            return configure();
        }        
    }
    
    public static synchronized void resetExecutorService(ExecutorService executorService) {
    	if (executorService.equals(serviceInstance)) {
    		serviceInstance = null;
    	}
    }

    private static ExecutorService configure(EntityManagerFactory emf) {
        ExecutorEventSupport eventSupport = new ExecutorEventSupport();
        // create instances of executor services

    	ExecutorQueryService queryService = new ExecutorQueryServiceImpl(true);
    	Executor executor = new ExecutorImpl();
    	ExecutorAdminService adminService = new ExecutorRequestAdminServiceImpl();

    	// create executor for persistence handling
        TransactionalCommandService commandService = new TransactionalCommandService(emf);
        
        ExecutorStoreService storeService = new JPAExecutorStoreService(true);
        ((JPAExecutorStoreService)storeService).setCommandService(commandService);
        ((JPAExecutorStoreService)storeService).setEmf(emf);
        ((JPAExecutorStoreService)storeService).setEventSupport(eventSupport);
        
        ((ExecutorImpl) executor).setExecutorStoreService(storeService);
        ((ExecutorImpl) executor).setEventSupport(eventSupport);
        
        // set executor on all instances that requires it
        ((ExecutorQueryServiceImpl) queryService).setCommandService(commandService);        
        ((ExecutorRequestAdminServiceImpl) adminService).setCommandService(commandService);
        
        
        // configure services
        ExecutorService service = new ExecutorServiceImpl(executor);
    	((ExecutorServiceImpl)service).setQueryService(queryService);
    	((ExecutorServiceImpl)service).setExecutor(executor);
        ((ExecutorServiceImpl)service).setAdminService(adminService);
        ((ExecutorServiceImpl)service).setEventSupport(eventSupport);

        return service;
    }
    
    private static ExecutorService configure() {
    	// create instances of executor services
        ExecutorEventSupport eventSupport = new ExecutorEventSupport();
    	ExecutorQueryService queryService = new InMemoryExecutorQueryServiceImpl(true);
    	Executor executor = new ExecutorImpl();
    	ExecutorAdminService adminService = new InMemoryExecutorAdminServiceImpl(true);

    	InMemoryExecutorStoreService storeService = new InMemoryExecutorStoreService(true);
    	((InMemoryExecutorStoreService)storeService).setEventSupport(eventSupport);
        
        ((ExecutorImpl) executor).setExecutorStoreService(storeService);
        ((ExecutorImpl) executor).setEventSupport(eventSupport);
        
        // set executor on all instances that requires it
        ((InMemoryExecutorQueryServiceImpl) queryService).setStoreService(storeService);        
        ((InMemoryExecutorAdminServiceImpl) adminService).setStoreService(storeService);
        
        
        // configure services
        ExecutorService service = new ExecutorServiceImpl(executor);
    	((ExecutorServiceImpl)service).setQueryService(queryService);
    	((ExecutorServiceImpl)service).setExecutor(executor);
        ((ExecutorServiceImpl)service).setAdminService(adminService);
        ((ExecutorServiceImpl)service).setEventSupport(eventSupport);        
         

        return service;
    }

    public static ExecutorRunnable buildRunable(EntityManagerFactory emf, ExecutorEventSupport eventSupport) {
        ExecutorRunnable runnable = new ExecutorRunnable();
        AvailableJobsExecutor jobExecutor = null;

        try {
            jobExecutor = InitialContext.doLookup(availableJobsExecutorName);
        } catch (Exception e) {
            jobExecutor = buildJobExecutor(emf, eventSupport);
        }
        runnable.setAvailableJobsExecutor(jobExecutor);
        return runnable;
    }


    private static AvailableJobsExecutor buildJobExecutor(EntityManagerFactory emf, ExecutorEventSupport eventSupport) {
        AvailableJobsExecutor jobExecutor;
        jobExecutor = new AvailableJobsExecutor();
        ClassCacheManager classCacheManager = new ClassCacheManager();
        ExecutorQueryService queryService = new ExecutorQueryServiceImpl(true);

        TransactionalCommandService cmdService = new TransactionalCommandService(emf);
        ExecutorStoreService storeService = new JPAExecutorStoreService(true);
        ((JPAExecutorStoreService) storeService).setCommandService(cmdService);
        ((JPAExecutorStoreService) storeService).setEmf(emf);

        ((ExecutorQueryServiceImpl) queryService).setCommandService(cmdService);
        jobExecutor.setClassCacheManager(classCacheManager);
        jobExecutor.setQueryService(queryService);
        jobExecutor.setExecutorStoreService(storeService);
        jobExecutor.setEventSupport(eventSupport);
        // provide bean manager instance as context data as it might not be available to
        // be looked up from JNDI in non managed threads
        try {
            Object beanManager = InitialContext.doLookup("java:comp/BeanManager");
            jobExecutor.addContextData("BeanManager", beanManager);
        } catch (NamingException ex) {
            logger.debug("CDI beans cannot be used in executor commands, because no CDI manager has been found in JNDI.");
        }
        return jobExecutor;
    }
    
    public static ExecutorRunnable buildRunable(ExecutorEventSupport eventSupport) {
    	ExecutorRunnable runnable = new ExecutorRunnable();
    	AvailableJobsExecutor jobExecutor = null;
    	try {
    		jobExecutor = InitialContext.doLookup(availableJobsExecutorName);
    	} catch (Exception e) {
    		jobExecutor = new AvailableJobsExecutor();
	    	ClassCacheManager classCacheManager = new ClassCacheManager();

	    	InMemoryExecutorStoreService storeService = new InMemoryExecutorStoreService(true);
	    	InMemoryExecutorQueryServiceImpl queryService = new InMemoryExecutorQueryServiceImpl(true);
	        queryService.setStoreService(storeService);

	        jobExecutor.setClassCacheManager(classCacheManager);
	        jobExecutor.setQueryService(queryService);
	        jobExecutor.setExecutorStoreService(storeService);
	        jobExecutor.setEventSupport(eventSupport);
	        // provide bean manager instance as context data as it might not be available to
	        // be looked up from JNDI in non managed threads
	        try {
				Object beanManager = InitialContext.doLookup("java:comp/BeanManager");
				jobExecutor.addContextData("BeanManager", beanManager);
			} catch (NamingException ex) {
				logger.debug("CDI beans cannot be used in executor commands, because no CDI manager has been found in JNDI.");
			}
    	}
        runnable.setAvailableJobsExecutor(jobExecutor);
        return runnable;
    }
}
