/*
 * Copyright 2017 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.services.cdi.producer;

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

import javax.enterprise.inject.AmbiguousResolutionException;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;

import org.jbpm.runtime.manager.impl.deploy.DeploymentDescriptorManager;
import org.jbpm.runtime.manager.impl.jpa.EntityManagerFactoryManager;
import org.jbpm.services.task.HumanTaskConfigurator;
import org.jbpm.services.task.HumanTaskServiceFactory;
import org.jbpm.services.task.audit.JPATaskLifeCycleEventListener;
import org.jbpm.services.task.impl.command.CommandBasedTaskService;
import org.jbpm.services.task.lifecycle.listeners.BAMTaskEventListener;
import org.kie.api.task.TaskLifeCycleEventListener;
import org.kie.api.task.UserGroupCallback;
import org.kie.internal.runtime.conf.AuditMode;
import org.kie.internal.runtime.conf.DeploymentDescriptor;
import org.kie.internal.task.api.InternalTaskService;
import org.kie.internal.task.api.UserInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * CDI producer for <code>TaskService</code> instances. By default it runs in new mode,
 * meaning new <code>TaskService</code> instance for every injection point.
 * This behavior can be altered by setting <code>org.jbpm.cdi.taskservice.mode</code> system
 * property to one of the values.
 * <ul>
 * <li>none - disables producer to not return TaskService instances</li>
 * <li>singleton - produces only one instance of TaskService that will be shared</li>
 * <li>new - produces new instance for every injection point</li>
 * </ul>
 * This bean accept following injections:
 * <ul>
 * <li>UserGroupCallback</li>
 * <li>UserInfo</li>
 * <li>TaskLifeCycleEventListener</li>
 * </ul>
 * all of these are optional injections and if not available defaults will be used. Underneath it uses
 * <code>HumanTaskConfigurator</code> for <code>TaskService</code> instances creations.
 * @see HumanTaskConfigurator
 */
public class HumanTaskServiceProducer {

    private static final Logger logger = LoggerFactory.getLogger( HumanTaskServiceProducer.class );
    final String mode = System.getProperty( "org.jbpm.cdi.taskservice.mode", "new" );

    @Inject
    private Instance<UserGroupCallback> userGroupCallback;

    @Inject
    private Instance<UserInfo> userInfo;

    @Inject
    @Any
    private Instance<TaskLifeCycleEventListener> taskListeners;
    
    @Inject
    @Any
    private Instance<List<TaskLifeCycleEventListener>> listOfListeners;

    @Inject
    @PersistenceUnit(unitName = "org.jbpm.domain")
    private EntityManagerFactory emf;

    // internal member to ensure only single instance of task service is produced
    private InternalTaskService taskService;

    @Produces
    public CommandBasedTaskService produceTaskService() {
        if ( mode.equalsIgnoreCase( "none" ) ) {
            return null;
        }
        if ( taskService == null ) {
            HumanTaskConfigurator configurator = createHumanTaskConfigurator();
            
            if ( mode.equalsIgnoreCase( "singleton" ) ) {
                this.taskService = (CommandBasedTaskService) configurator.getTaskService();
            } else {
                return (CommandBasedTaskService) configurator.getTaskService();
            }
        }

        return (CommandBasedTaskService) taskService;
    }

    protected HumanTaskConfigurator createHumanTaskConfigurator() {
        HumanTaskConfigurator configurator = HumanTaskServiceFactory.newTaskServiceConfigurator();
        
        configureHumanTaskConfigurator(configurator);
        return configurator;
    }

    protected void configureHumanTaskConfigurator(HumanTaskConfigurator configurator) {
        configurator
                .entityManagerFactory( emf )
                .userGroupCallback( safeGet( userGroupCallback ) )
                .userInfo( safeGet( userInfo ) );
        
        DeploymentDescriptorManager manager = new DeploymentDescriptorManager("org.jbpm.domain");
        DeploymentDescriptor descriptor = manager.getDefaultDescriptor();
        // in case there is descriptor with enabled audit register then by default
        if (!descriptor.getAuditMode().equals(AuditMode.NONE)) {
        	JPATaskLifeCycleEventListener listener = new JPATaskLifeCycleEventListener(false);
        	BAMTaskEventListener bamListener = new BAMTaskEventListener(false);
        	// if the audit persistence unit is different than default for the engine perform proper init
        	if (!"org.jbpm.domain".equals(descriptor.getAuditPersistenceUnit())) {
        		 EntityManagerFactory emf = EntityManagerFactoryManager.get().getOrCreate(descriptor.getAuditPersistenceUnit());
        		 listener = new JPATaskLifeCycleEventListener(emf);
        		 
        		 bamListener = new BAMTaskEventListener(emf);
        	}
        	configurator.listener( listener );
        	configurator.listener( bamListener );
        }
        // next proceed with registration of further listeners as cdi injections
        try {
            for ( TaskLifeCycleEventListener listener : taskListeners ) {
                configurator.listener( listener );
                logger.debug( "Registering listener {}", listener );
            }
        } catch ( Exception e ) {
            logger.debug( "Cannot add listeners to task service due to {}", e.getMessage() );
        }
    }

    protected <T> T safeGet( Instance<T> instance ) {
        try {
            T object = instance.get();
            logger.debug( "About to set object {} on task service", object );
            return object;
        } catch ( AmbiguousResolutionException e ) {
            // special handling in case cdi discovered multiple injections
            // that are actually same instances - e.g. weld on tomcat
            HashSet<T> available = new HashSet<T>();

            for ( T object : instance ) {
                available.add( object );
            }

            if ( available.size() == 1 ) {
                return available.iterator().next();
            } else {
                throw e;
            }
        } catch ( Throwable e ) {
            logger.debug( "Cannot get value of of instance {} due to {}", instance, e.getMessage() );
        }

        return null;
    }

}
