/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library 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 library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * Modified by
 * @author  Data Virtuality GmbH
 * @date    $Date$
 *  
 */
// https://issues.jboss.org/browse/TEIID-2168
// https://issues.jboss.org/browse/TEIID-2086
// https://issues.jboss.org/browse/TEIID-2771
// https://issues.jboss.org/browse/TEIID-2444
// https://issues.jboss.org/browse/TEIID-2215
// https://issues.jboss.org/browse/TEIID-3519
// https://issues.jboss.org/browse/TEIID-3978
// https://issues.jboss.org/browse/TEIID-5351
// https://issues.redhat.com/browse/TEIID-5859
// https://issues.redhat.com/browse/TEIID-2439
package org.teiid.runtime;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;

import java.util.Map;
import javax.resource.spi.work.WorkManager;
import javax.transaction.TransactionManager;

import org.infinispan.commons.dataconversion.MediaType;
import org.infinispan.commons.util.FileLookupFactory;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.parsing.ConfigurationBuilderHolder;
import org.infinispan.configuration.parsing.ParserRegistry;
import org.infinispan.manager.DefaultCacheManager;
import org.jgroups.JChannel;
import org.jgroups.fork.ForkChannel;
import org.teiid.cache.CacheFactory;
import org.teiid.cache.infinispan.InfinispanCacheFactory;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.dqp.internal.process.*;
import org.teiid.query.ObjectReplicator;
import org.teiid.replication.jgroups.ChannelFactory;
import org.teiid.replication.jgroups.JGroupsObjectReplicator;
import org.teiid.security.SecurityHelper;
import org.teiid.transport.SocketConfiguration;

import static org.infinispan.configuration.cache.TransactionConfiguration.TRANSACTION_MANAGER_LOOKUP;

public class EmbeddedConfiguration extends DQPConfiguration {

	private final class SimpleChannelFactory implements ChannelFactory {
		private JChannel channel;

		@Override
		public JChannel createChannel(String id) throws Exception {
			synchronized (this) {
				if (channel == null) {
					channel = new JChannel(this.getClass().getClassLoader().getResourceAsStream(getJgroupsConfigFile()));
					channel.connect("teiid-replicator"); //$NON-NLS-1$
				}
			}
			//assumes fork and other necessary protocols are in the main stack
			ForkChannel fc = new ForkChannel(channel, "teiid-replicator-fork", id); //$NON-NLS-1$
			return fc;
		}

		void stop() {
			if (this.channel != null) {
				channel.close();
			}
		}
	}

	private SecurityHelper securityHelper;
	private String securityDomain;
	private TransactionManager transactionManager;
	private ObjectReplicator objectReplicator;
	private WorkManager workManager;
	private boolean useDisk = true;
	private String bufferDirectory;
	private CacheFactory cacheFactory;
	private int maxResultSetCacheStaleness = 60;
	private String infinispanConfigFile = "infinispan-config.xml"; //$NON-NLS-1$
	private String jgroupsConfigFile; // from infinispan-core
	private List<SocketConfiguration> transports;
	private int maxODBCLobSizeAllowed = 5*1024*1024; // 5 MB

	private DefaultCacheManager cacheManager;
	private SimpleChannelFactory channelFactory;

	public EmbeddedConfiguration() {
		DefaultAuthorizationValidator authorizationValidator = new DefaultAuthorizationValidator();
		authorizationValidator.setPolicyDecider(new DataRolePolicyDecider());
		this.setAuthorizationValidator(authorizationValidator);
	}
	
	public SecurityHelper getSecurityHelper() {
		return securityHelper;
	}
	/**
	 * Set the {@link SecurityHelper} that can associate the appropriate SecurityContext
	 * with threads executing Teiid tasks.  Will also set the appropriate user/subject information
	 * on the Teiid contexts. Not required if a {@link WorkManager} is set.
	 * 
	 * @param securityHelper
	 */
	public void setSecurityHelper(SecurityHelper securityHelper) {
		this.securityHelper = securityHelper;
	}
	public String getSecurityDomain() {
		return this.securityDomain;
	}
	public void setSecurityDomain(String securityDomain) {
		this.securityDomain = securityDomain;
	}
	public TransactionManager getTransactionManager() {
		return transactionManager;
	}
	public void setTransactionManager(TransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}

	public ObjectReplicator getObjectReplicator() {
		if (this.objectReplicator == null && jgroupsConfigFile != null) {
			channelFactory = new SimpleChannelFactory();
			this.objectReplicator = new JGroupsObjectReplicator(channelFactory, Executors.newCachedThreadPool());
		}
		return objectReplicator;
	}

	public void setObjectReplicator(ObjectReplicator objectReplicator) {
		this.objectReplicator = objectReplicator;
	}

	/**
	 * Sets the {@link WorkManager} to be used instead of a {@link ThreadReuseExecutor}.
	 * This means that Teiid will not own the processing threads and will not necessarily be
	 * responsible for security context propagation.
	 * @param workManager
	 */
	public void setWorkManager(WorkManager workManager) {
		this.workManager = workManager;
	}
	public WorkManager getWorkManager() {
		return workManager;
	}
	
	@Override
	public TeiidExecutor getTeiidExecutor() {
		if (workManager == null) {
			return super.getTeiidExecutor();
		}
		//TODO: if concurrency is 1, then use a direct executor
		//the only scheduled task right now just restarts a workitem,
		//so that can be done in the scheduler thread
		return new WorkManagerTeiidExecutor(workManager);
	}
	
	public boolean isUseDisk() {
		return useDisk;
	}
	
	public void setUseDisk(boolean useDisk) {
		this.useDisk = useDisk;
	}
	
	public void setBufferDirectory(String dir) {
		this.bufferDirectory = dir;
	}
	
	public String getBufferDirectory() {
		return this.bufferDirectory;
	}

	public String getInfinispanConfigFile() {
		return infinispanConfigFile;
	}

	public void setInfinispanConfigFile(String infinispanConfigFile) {
		this.infinispanConfigFile = infinispanConfigFile;
	}

	public CacheFactory getCacheFactory() {
		if (this.cacheFactory == null) {
			try {
				InputStream is = FileLookupFactory.newInstance().lookupFileStrict(this.infinispanConfigFile, Thread.currentThread().getContextClassLoader());
				ConfigurationBuilderHolder holder = new ParserRegistry().parse(is, null, MediaType.APPLICATION_XML);
					if (getTransactionManager() != null) {
					setCacheTransactionManager(holder);
					}
				cacheManager = new DefaultCacheManager(holder, true);
				for (String cacheName : cacheManager.getCacheNames()) {
					cacheManager.startCache(cacheName);
				}
				this.cacheFactory = new InfinispanCacheFactory(cacheManager, this.getClass().getClassLoader());
			} catch (IOException e) {
				throw new TeiidRuntimeException(RuntimePlugin.Event.TEIID40100, e);
			}
		}
		return this.cacheFactory;
	}

	private void setCacheTransactionManager(ConfigurationBuilderHolder holder) {
		Map<String, ConfigurationBuilder> namedBuilders = holder.getNamedConfigurationBuilders();
		for (Map.Entry<String, ConfigurationBuilder> entry : namedBuilders.entrySet()) {
			ConfigurationBuilder cb = entry.getValue();
			cb.transaction().attributes().attribute(TRANSACTION_MANAGER_LOOKUP).set(() -> EmbeddedConfiguration.this.getTransactionManager());
 	        }
	}

	public void setCacheFactory(CacheFactory cacheFactory) {
		this.cacheFactory = cacheFactory;
	}
	public int getMaxResultSetCacheStaleness() {
		return maxResultSetCacheStaleness;
	}
	public void setMaxResultSetCacheStaleness(int maxResultSetCacheStaleness) {
		this.maxResultSetCacheStaleness = maxResultSetCacheStaleness;
	}
	public String getJgroupsConfigFile() {
		return jgroupsConfigFile;
	}
 	public void setJgroupsConfigFile(String jgroupsConfigFile) {
		this.jgroupsConfigFile = jgroupsConfigFile;
	}

	protected void stop() {
		if (cacheManager != null) {
			cacheManager.stop();
		}
		if (channelFactory != null) {
			channelFactory.stop();
		}
	}

	public void addTransport(SocketConfiguration configuration) {
		if (this.transports == null) {
			this.transports = new ArrayList<SocketConfiguration>();
		}
		this.transports.add(configuration);
	}

	public List<SocketConfiguration> getTransports(){
		return this.transports;
	}

	public int getMaxODBCLobSizeAllowed() {
		return this.maxODBCLobSizeAllowed;
	}

	public void setMaxODBCLobSizeAllowed(int lobSize) {
		this.maxODBCLobSizeAllowed = lobSize;
	}
}
