/*
 * 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$
 *  
 */

package org.teiid.runtime;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;

import org.teiid.adminapi.Model;
import org.teiid.adminapi.Model.Type;
import org.teiid.adminapi.VDB.Status;
import org.teiid.adminapi.impl.ModelMetaData;
import org.teiid.adminapi.impl.SourceMappingMetadata;
import org.teiid.adminapi.impl.VDBMetaData;
import org.teiid.core.util.StringUtil;
import org.teiid.deployers.VDBRepository;
import org.teiid.deployers.VirtualDatabaseException;
import org.teiid.dqp.internal.datamgr.ConnectorManager;
import org.teiid.dqp.internal.datamgr.ConnectorManagerRepository;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.metadata.Datatype;
import org.teiid.metadata.MetadataFactory;
import org.teiid.metadata.MetadataRepository;
import org.teiid.metadata.MetadataStore;
import org.teiid.query.metadata.ChainingMetadataRepository;
import org.teiid.query.metadata.DDLMetadataRepository;
import org.teiid.query.metadata.NativeMetadataRepository;
import org.teiid.query.metadata.DirectQueryMetadataRepository;
import org.teiid.query.parser.QueryParser;
import org.teiid.translator.TranslatorException;

public abstract class AbstractVDBDeployer {
	
	// EXTENSION
	// ISSUE: DVCORE-2441
	// FIXED BY: Roman Pedchenko
	// DATE: 2014.10.26
	// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2235, 2661, 2178, 2297
	// private Map<String, MetadataRepository<?, ?>> repositories = new ConcurrentSkipListMap<String, MetadataRepository<?, ?>>(String.CASE_INSENSITIVE_ORDER);
	protected ConcurrentSkipListMap<String, MetadataRepository<?, ?>> repositories = new ConcurrentSkipListMap<String, MetadataRepository<?, ?>>(StringUtil.CASE_INSENSITIVE_ORDER);
	// END EXTENSION
	
	public AbstractVDBDeployer() {
		repositories.put("ddl", new DDLMetadataRepository()); //$NON-NLS-1$
		repositories.put("native", new NativeMetadataRepository()); //$NON-NLS-1$
	}
	
	public void addMetadataRepository(String name, MetadataRepository<?, ?> metadataRepository) {
		this.repositories.put(name, metadataRepository);
	}
	
	protected void assignMetadataRepositories(VDBMetaData deployment, MetadataRepository<?, ?> defaultRepo) throws VirtualDatabaseException {
		for (ModelMetaData model:deployment.getModelMetaDatas().values()) {
			if (model.isSource() && model.getSourceNames().isEmpty()) {
	    		throw new VirtualDatabaseException(RuntimePlugin.Event.TEIID40093, RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40093, model.getName(), deployment.getName(), deployment.getVersion()));
	    	}
			if (model.getModelType() == Type.FUNCTION || model.getModelType() == Type.OTHER) {
				continue;
			}
			MetadataRepository<?, ?> repo = getMetadataRepository(deployment, model, defaultRepo);
			model.addAttchment(MetadataRepository.class, repo);
		}
	}
	
	private MetadataRepository<?, ?> getMetadataRepository(VDBMetaData vdb, ModelMetaData model, MetadataRepository<?, ?> defaultRepo) throws VirtualDatabaseException {
		if (model.getSchemaSourceType() == null) {
			if (!vdb.isDynamic()) {
				return defaultRepo;
			}
			
			if (model.isSource()) {
				// EXTENSION
				// ISSUE: DVCORE-2441
				// FIXED BY: Roman Pedchenko
				// DATE: 2014.10.26
				// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2235, 2661, 2178, 2297
				return new ChainingMetadataRepository(Arrays.asList(new NativeMetadataRepository(), new DirectQueryMetadataRepository()));
				// END EXTENSION
			}
			throw new VirtualDatabaseException(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40094, model.getName(), vdb.getName(), vdb.getVersion(), null));
		}
		
// EXTENSION
// ISSUE: DVCORE-2441
// FIXED BY: Roman Pedchenko
// DATE: 2014.10.26
// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2235, 2661, 2178, 2297
//     	MetadataRepository<?, ?> first = null;
//     	MetadataRepository<?, ?> current = null;
//     	MetadataRepository<?, ?> previous = null;
// END EXTENSION
		// EXTENSION
		// ISSUE: DVCORE-2440
		// FIXED BY: Roman Pedchenko
		// DATE: 2014.10.18
		// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2176,https://issues.jboss.org/browse/TEIID-2177
		// StringTokenizer st = new StringTokenizer(model.getSchemaSourceType(), ","); //$NON-NLS-1$
		String schemaTypes = model.getSchemaSourceType();
		StringTokenizer st = new StringTokenizer(schemaTypes, ","); //$NON-NLS-1$
		// END EXTENSION
		List<MetadataRepository<?, ?>> repos = new ArrayList<MetadataRepository<?,?>>(st.countTokens());
		while (st.hasMoreTokens()) {
			String repoType = st.nextToken().trim();
			// EXTENSION
			// ISSUE: DVCORE-2441
			// FIXED BY: Roman Pedchenko
			// DATE: 2014.10.26
			// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2235, 2661, 2178, 2297
			// current = getMetadataRepository(repoType);
			MetadataRepository<?, ?> current = getMetadataRepository(repoType);
			// END EXTENSION
			if (current == null) {
				throw new VirtualDatabaseException(RuntimePlugin.Util.gs(RuntimePlugin.Event.TEIID40094, model.getName(), vdb.getName(), vdb.getVersion(), repoType));
			}
			// EXTENSION
			// ISSUE: DVCORE-2441
			// FIXED BY: Roman Pedchenko
			// DATE: 2014.10.26
			// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2235, 2661, 2178, 2297
			// if (first == null) {
			// 	first = current;
			// }
			// 
			// if (previous != null) {
			// 	previous.setNext(current);
			// }
			// previous = current;
			// current = null;
			repos.add(current);
			// END EXTENSION
		}
		// EXTENSION
		// ISSUE: DVCORE-2440
		// FIXED BY: Roman Pedchenko
		// DATE: 2014.10.18
		// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2176,https://issues.jboss.org/browse/TEIID-2177
		if (model.getModelType() == ModelMetaData.Type.PHYSICAL) {
			// EXTENSION
			// ISSUE: DVCORE-2441
			// FIXED BY: Roman Pedchenko
			// DATE: 2014.10.26
			// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2235, 2661, 2178, 2297
			// previous.setNext(new DirectQueryMetadataRepository());
			repos.add(new DirectQueryMetadataRepository());
			// END EXTENSION
		}
		// END EXTENSION
		// EXTENSION
		// ISSUE: DVCORE-2441
		// FIXED BY: Roman Pedchenko
		// DATE: 2014.10.26
		// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2235, 2661, 2178, 2297
		// return first;
		if (repos.size() == 1) {
			return repos.get(0);
		}
		return new ChainingMetadataRepository(repos);
		// END EXTENSION
	}
	
    protected ConnectorManager getConnectorManager(final ModelMetaData model, final ConnectorManagerRepository cmr) {
    	if (model.isSource()) {
	    	List<SourceMappingMetadata> mappings = model.getSourceMappings();
			for (SourceMappingMetadata mapping:mappings) {
				return cmr.getConnectorManager(mapping.getName());
			}
    	}
		return null;
    }
    
	protected void loadMetadata(VDBMetaData vdb, ConnectorManagerRepository cmr,
			MetadataStore store) throws TranslatorException {
		// EXTENSION
		// ISSUE: DVCORE-14,DVCORE-2233
		// FIXED BY: Salvatore Raunich,Roman Pedchenko
		// DATE: 20.11.2012,2014.07.31
		// if the VDB is empty, jump to finishDeployment() to set the VDB as ACTIVE
		if (vdb.getModelMetaDatas().values().isEmpty()) {
			final VDBMetaData vdb1 = vdb;
			Runnable job = new Runnable() {
				@Override
				public void run() {
					getVDBRepository().finishDeployment(vdb1.getName(), vdb1.getVersion());
				}
			};
			Executor executor = getExecutor();
			if (executor == null) {
				job.run();
			}
			else {
	    		executor.execute(job);
			}
			return;
		}		
		// END EXTENSION
		// load metadata from the models
		AtomicInteger loadCount = new AtomicInteger();
		for (ModelMetaData model: vdb.getModelMetaDatas().values()) {
			if (model.getModelType() == Model.Type.PHYSICAL || model.getModelType() == Model.Type.VIRTUAL) {
				loadCount.incrementAndGet();
			}
		}
		for (ModelMetaData model: vdb.getModelMetaDatas().values()) {
			MetadataRepository metadataRepository = model.getAttachment(MetadataRepository.class);
			if (model.getModelType() == Model.Type.PHYSICAL || model.getModelType() == Model.Type.VIRTUAL) {
				loadMetadata(vdb, model, cmr, metadataRepository, store, loadCount);
				LogManager.logTrace(LogConstants.CTX_RUNTIME, "Model ", model.getName(), "in VDB ", vdb.getName(), " was being loaded from its repository"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			}
			else {
				LogManager.logTrace(LogConstants.CTX_RUNTIME, "Model ", model.getName(), "in VDB ", vdb.getName(), " skipped being loaded because of its type ", model.getModelType()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			}
		}
	}
	
	protected abstract VDBRepository getVDBRepository();

	protected abstract void loadMetadata(VDBMetaData vdb, ModelMetaData model,
			ConnectorManagerRepository cmr,
			MetadataRepository metadataRepository, MetadataStore store,
			AtomicInteger loadCount) throws TranslatorException;
	
	protected void metadataLoaded(final VDBMetaData vdb,
			final ModelMetaData model,
			final MetadataStore vdbMetadataStore,
			final AtomicInteger loadCount, MetadataFactory factory, boolean success) {
		if (success) {
			// merge into VDB metadata
			factory.mergeInto(vdbMetadataStore);
		
			//TODO: this is not quite correct, the source may be missing
			model.clearRuntimeMessages();
		} else {
			// EXTENSION
			// ISSUE: DVCORE-14 
			// FIXED BY: Salvatore Raunich
			// DATE: 20.11.2012
			// don't set the VDB status as FAILED but leave it as ACTIVE
//			vdb.setStatus(Status.FAILED);
			//TODO: abort the other loads			
			// END EXTENSION
		}
		
		if (loadCount.decrementAndGet() == 0 || vdb.getStatus() == Status.FAILED) {
			getVDBRepository().finishDeployment(vdb.getName(), vdb.getVersion());
		}
	}
	// EXTENSION
	// ISSUE: DVCORE-2441
	// FIXED BY: Roman Pedchenko
	// DATE: 2014.10.23
	// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-247, 2166, 2151, 2234, 2581, 2571, 2369, 1386, 2226, 2233, 2224, 2225, 2257
	
	protected MetadataFactory createMetadataFactory(VDBMetaData vdb,
			ModelMetaData model) {
		Map<String, Datatype> datatypes = this.getVDBRepository().getRuntimeTypeMap();
		// EXTENSION
		// ISSUE: DVCORE-2441
		// FIXED BY: Roman Pedchenko
		// DATE: 2014.10.26
		// ADOPTED FROM: https://issues.jboss.org/browse/TEIID-2235, 2661, 2178, 2297
		// MetadataFactory factory = new MetadataFactory(vdb.getName(), vdb.getVersion(), model.getName(), datatypes, model.getProperties(), model.getSchemaText());
		MetadataFactory factory = new MetadataFactory(vdb.getName(), vdb.getVersion(), datatypes, model);
		// END EXTENSION
		factory.setBuiltinDataTypes(this.getVDBRepository().getSystemStore().getDatatypes());
		factory.getSchema().setPhysical(model.isSource());
		factory.setParser(new QueryParser()); //for thread safety each factory gets it's own instance.
		return factory;
	}
	// END EXTENSION

	/**
	 * @throws VirtualDatabaseException  
	 */
	protected MetadataRepository<?, ?> getMetadataRepository(String repoType) throws VirtualDatabaseException {
		return repositories.get(repoType);
	}

	// EXTENSION
	// ISSUE: DVCORE-2233
	// FIXED BY: Roman Pedchenko
	// DATE: 2014.07.31
	public Executor getExecutor() {
		return null;
	}
	// END EXTENSION
}
