/*
 * 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.Map;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import org.jbpm.process.core.timer.GlobalSchedulerService;
import org.kie.api.KieBase;
import org.kie.api.builder.ReleaseId;
import org.kie.api.io.Resource;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.EnvironmentName;
import org.kie.api.runtime.manager.RegisterableItemsFactory;
import org.kie.api.runtime.manager.RuntimeEnvironment;
import org.kie.api.runtime.manager.RuntimeEnvironmentBuilder;
import org.kie.api.runtime.manager.RuntimeEnvironmentBuilderFactory;
import org.kie.api.task.TaskService;
import org.kie.api.task.UserGroupCallback;
import org.kie.internal.task.api.UserInfo;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.transaction.PlatformTransactionManager;

/**
 * Factory responsible for producing instances of RuntimeEnvironment that are consumed by <code>RuntimeManager</code>
 * upon creation. It allows to create following types of <code>RuntimeEnvironment</code> (that mainly means what is
 * configured by default):
 * <ul>
 *     <li>DEFAULT - default (most common) configuration for RuntimeManager</li>
 *     <li>EMPTY - completely empty environment to be manually populated</li>
 *     <li>DEFAULT_IN_MEMORY - same as DEFAULT but without persistence of the runtime engine</li>
 *     <li>DEFAULT_KJAR - same as DEFAULT but knowledge asset are taken from KJAR identified by releaseid or GAV</li>
 *     <li>DEFAULT_KJAR_CL - build directly from classpath that consists kmodule.xml descriptor</li>
 * </ul>
 *
 * Mandatory properties depends on the type selected but knowledge information must be given for all types. That means
 * that one of the following is provided:
 * <ul>
 *     <li>knowledgeBase</li>
 *     <li>assets</li>
 *     <li>releaseId</li>
 *     <li>groupId, artifactId, version</li>
 * </ul>
 * Next for DEFAULT, DEFAULT_KJAR, DEFAULT_KJAR_CL persistence needs to be configured:
 * <ul>
 *     <li>entity manager factory</li>
 *     <li>transaction manager</li>
 * </ul>
 * Transaction Manager must be Spring transaction manager as based on its presence entire
 * persistence and transaction support is configured. <br/>
 * Optionally <code>entityManager</code> can be provided to be used instead of always
 * creating new one from EntityManagerFactory - e.g. when using shared entity manager from Spring.
 * <br/>
 * All other properties are optional and are meant to override the default given by type of the environment selected.
 * @see RuntimeManagerFactoryBean
 */
public class RuntimeEnvironmentFactoryBean implements FactoryBean, InitializingBean {

    public static final String TYPE_DEFAULT = "DEFAULT";
    public static final String TYPE_EMPTY = "EMPTY";
    public static final String TYPE_DEFAULT_IN_MEMORY = "DEFAULT_IN_MEMORY";
    public static final String TYPE_DEFAULT_KJAR = "DEFAULT_KJAR";
    public static final String TYPE_DEFAULT_KJAR_CL = "DEFAULT_KJAR_CL";

    private String type = TYPE_DEFAULT;
    // persistence
    private EntityManagerFactory entityManagerFactory;
    private EntityManager entityManager;
    // transaction
    private PlatformTransactionManager transactionManager;

    // knowledge base and assets
    private KieBase knowledgeBase;
    private Map<Resource, ResourceType> assets;

    // kjar
    private ReleaseId releaseId;
    private String groupId;
    private String artifactId;
    private String version;
    private String kbaseName;
    private String ksessionName;

    // environment entries
    private Map<String, Object> environmentEntries;

    // configuration
    private Map<String, String> configuration;

    // human task
    private UserGroupCallback userGroupCallback;
    private UserInfo userInfo;
    private TaskService taskService;

    private RegisterableItemsFactory registerableItemsFactory;

    // misc
    private ClassLoader classLoader;
    private GlobalSchedulerService schedulerService;
    private boolean pessimisticLocking = false;

    @Override
    public Object getObject() throws Exception {
        RuntimeEnvironmentBuilder builder = null;

        RuntimeEnvironmentBuilderFactory factory = RuntimeEnvironmentBuilder.Factory.get();

        if (type.equalsIgnoreCase(TYPE_EMPTY)) {
            builder = factory.newEmptyBuilder();
        } else if (type.equalsIgnoreCase(TYPE_DEFAULT_IN_MEMORY)) {
            builder = factory.newDefaultInMemoryBuilder();
        } else if (type.equalsIgnoreCase(TYPE_DEFAULT)) {
            builder = factory.newDefaultBuilder();
        } else if (type.equalsIgnoreCase(TYPE_DEFAULT_KJAR)) {
            if (releaseId != null) {
                builder = factory.newDefaultBuilder(releaseId, kbaseName, ksessionName);
            } else {
                builder = factory.newDefaultBuilder(groupId, artifactId, version, kbaseName, ksessionName);
            }
        } else if (type.equalsIgnoreCase(TYPE_DEFAULT_KJAR_CL)) {
            builder = factory.newClasspathKmoduleDefaultBuilder(kbaseName, ksessionName);
        } else {
            throw new IllegalArgumentException("Unknown type of environment");
        }

        // apply all known properties
        builder
        .entityManagerFactory(entityManagerFactory)
        .knowledgeBase(knowledgeBase)
        .classLoader(classLoader)
        .schedulerService(schedulerService)
        .userGroupCallback(userGroupCallback)
        .registerableItemsFactory(registerableItemsFactory);

        // common environment entries
        builder.addEnvironmentEntry("org.kie.api.task.TaskService", taskService);
        builder.addEnvironmentEntry(EnvironmentName.TRANSACTION_MANAGER, transactionManager);
        builder.addEnvironmentEntry(EnvironmentName.TASK_USER_GROUP_CALLBACK, userGroupCallback);
        builder.addEnvironmentEntry(EnvironmentName.TASK_USER_INFO, userInfo);
        if (entityManager != null) {
            builder.addEnvironmentEntry(EnvironmentName.APP_SCOPED_ENTITY_MANAGER, entityManager)
            .addEnvironmentEntry(EnvironmentName.CMD_SCOPED_ENTITY_MANAGER, entityManager)
            .addEnvironmentEntry("IS_JTA_TRANSACTION", false)
            .addEnvironmentEntry("IS_SHARED_ENTITY_MANAGER", true);
        }
        if( pessimisticLocking ) { 
            builder.addEnvironmentEntry(EnvironmentName.USE_PESSIMISTIC_LOCKING, true);
        }

        // apply configuration if any
        if (configuration != null) {
            for (Map.Entry<String, String> entry : configuration.entrySet()) {
                builder.addConfiguration(entry.getKey(), entry.getValue());
            }
        }

        // apply environment entries if any
        if (environmentEntries != null) {
            for (Map.Entry<String, Object> entry : environmentEntries.entrySet()) {
                builder.addEnvironmentEntry(entry.getKey(), entry.getValue());
            }
        }

        // apply assets if kbase was not given
        if (knowledgeBase == null && assets != null) {
            for (Map.Entry<Resource, ResourceType> entry : assets.entrySet()) {
                builder.addAsset(entry.getKey(), entry.getValue());
            }
        }

        return builder.get();
    }

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

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

    @Override
    public void afterPropertiesSet() throws Exception {
        // check it always
        checkKnowledge();
        // check persistence and transaction for all except empty and in memory
        if (!type.equalsIgnoreCase(TYPE_DEFAULT_IN_MEMORY) && !type.equalsIgnoreCase(TYPE_EMPTY)) {
            checkPersistence();
        }
        // for kjar based ensure that either release id or GAV is given
        if (type.equalsIgnoreCase(TYPE_DEFAULT_KJAR) || type.equalsIgnoreCase(TYPE_DEFAULT_KJAR_CL)) {
            checkKjar();
        }
    }

    protected void checkPersistence() {
        if (entityManagerFactory == null && entityManager == null) {
            throw new IllegalArgumentException("Entity Manager or EntityManagerFactory must be provided");
        }
        if (transactionManager == null) {
            throw new IllegalArgumentException("TransactionManager must be provided");
        }
    }

    protected void checkKnowledge() {
        if (knowledgeBase == null && assets == null && releaseId == null && groupId == null ) {
            throw new IllegalArgumentException("Knowledge is not provided, set one of knowledgeBase, assets, releaseId or GAV");
        }
    }

    protected void checkKjar() {
        if (releaseId == null && groupId == null && artifactId == null && version == null) {
            throw new IllegalArgumentException("For Kjar environment either ReleaseId or GAV must be provided");
        }
    }

    public String getType() {
        return type;
    }

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

    public EntityManagerFactory getEntityManagerFactory() {
        return entityManagerFactory;
    }

    public void setEntityManagerFactory(EntityManagerFactory entityManagerFactory) {
        this.entityManagerFactory = entityManagerFactory;
    }

    public EntityManager getEntityManager() {
        return entityManager;
    }

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public PlatformTransactionManager getTransactionManager() {
        return transactionManager;
    }

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public KieBase getKnowledgeBase() {
        return knowledgeBase;
    }

    public void setKnowledgeBase(KieBase knowledgeBase) {
        this.knowledgeBase = knowledgeBase;
    }

    public Map<Resource, ResourceType> getAssets() {
        return assets;
    }

    public void setAssets(Map<Resource, ResourceType> assets) {
        this.assets = assets;
    }

    public ReleaseId getReleaseId() {
        return releaseId;
    }

    public void setReleaseId(ReleaseId releaseId) {
        this.releaseId = releaseId;
    }

    public String getGroupId() {
        return groupId;
    }

    public void setGroupId(String groupId) {
        this.groupId = groupId;
    }

    public String getArtifactId() {
        return artifactId;
    }

    public void setArtifactId(String artifactId) {
        this.artifactId = artifactId;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    public String getKbaseName() {
        return kbaseName;
    }

    public void setKbaseName(String kbaseName) {
        this.kbaseName = kbaseName;
    }

    public String getKsessionName() {
        return ksessionName;
    }

    public void setKsessionName(String ksessionName) {
        this.ksessionName = ksessionName;
    }

    public Map<String, Object> getEnvironmentEntries() {
        return environmentEntries;
    }

    public void setEnvironmentEntries(Map<String, Object> environmentEntries) {
        this.environmentEntries = environmentEntries;
    }

    public Map<String, String> getConfiguration() {
        return configuration;
    }

    public void setConfiguration(Map<String, String> configuration) {
        this.configuration = configuration;
    }

    public UserGroupCallback getUserGroupCallback() {
        return userGroupCallback;
    }

    public void setUserGroupCallback(UserGroupCallback userGroupCallback) {
        this.userGroupCallback = userGroupCallback;
    }

    public UserInfo getUserInfo() {
        return userInfo;
    }

    public void setUserInfo(UserInfo userInfo) {
        this.userInfo = userInfo;
    }

    public TaskService getTaskService() {
        return taskService;
    }

    public void setTaskService(TaskService taskService) {
        this.taskService = taskService;
    }

    public RegisterableItemsFactory getRegisterableItemsFactory() {
        return registerableItemsFactory;
    }

    public void setRegisterableItemsFactory(RegisterableItemsFactory registerableItemsFactory) {
        this.registerableItemsFactory = registerableItemsFactory;
    }

    public ClassLoader getClassLoader() {
        return classLoader;
    }

    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    public GlobalSchedulerService getSchedulerService() {
        return schedulerService;
    }

    public void setSchedulerService(GlobalSchedulerService schedulerService) {
        this.schedulerService = schedulerService;
    }

    public boolean getPessimisticLocking() {
        return pessimisticLocking;
    }

    public void setPessimisticLocking(boolean pessimisticLocking ) {
        this.pessimisticLocking = pessimisticLocking;
    }
    
}
