/*
 * Copyright 2011-2019 B2i Healthcare Pte Ltd, http://b2i.sg
 * 
 * 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 com.b2international.snowowl.core.repository;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.b2international.commons.extension.ClassPathScanner;
import com.b2international.index.mapping.Mappings;
import com.b2international.index.revision.Hooks;
import com.b2international.index.revision.RevisionIndex;
import com.b2international.snowowl.core.Repository;
import com.b2international.snowowl.core.RepositoryInfo.Health;
import com.b2international.snowowl.core.domain.IComponent;
import com.b2international.snowowl.core.merge.ComponentRevisionConflictProcessor;
import com.b2international.snowowl.core.setup.Environment;
import com.b2international.snowowl.core.terminology.Terminology;
import com.b2international.snowowl.core.terminology.TerminologyComponent;
import com.b2international.snowowl.datastore.CodeSystemEntry;
import com.b2international.snowowl.datastore.CodeSystemVersionEntry;
import com.b2international.snowowl.datastore.request.IndexReadRequest;
import com.b2international.snowowl.datastore.review.ConceptChanges;
import com.b2international.snowowl.datastore.review.Review;
import com.b2international.snowowl.datastore.version.VersioningRequestBuilder;

/**
 * @since 4.5
 */
public final class RepositoryBuilder {
	
	private final String repositoryId;
	private final DefaultRepositoryManager manager;
	private final Logger log;
	
	private int mergeMaxResults;
	private TerminologyRepositoryInitializer initializer;
	private Hooks.PreCommitHook hook;
	private VersioningRequestBuilder versioningRequestBuilder;
	private ComponentDeletionPolicy deletionPolicy;
	
	private final Mappings mappings = new Mappings(
		Review.class, 
		ConceptChanges.class, 
		CodeSystemEntry.class, 
		CodeSystemVersionEntry.class
	);
	private final TerminologyComponents terminologyComponents;
	private ComponentRevisionConflictProcessor componentRevisionConflictProcessor;

	RepositoryBuilder(DefaultRepositoryManager manager, String repositoryId) {
		this.manager = manager;
		this.repositoryId = repositoryId;
		this.log = LoggerFactory.getLogger("repository."+repositoryId);
		this.terminologyComponents = new TerminologyComponents(this.log);
	}
	
	public Logger log() {
		return log;
	}

	public RepositoryBuilder setMergeMaxResults(int mergeMaxResults) {
		this.mergeMaxResults = mergeMaxResults;
		return this;
	}
	
	public RepositoryBuilder withInitializer(TerminologyRepositoryInitializer initializer) {
		this.initializer = initializer;
		return this;
	}
	
	public RepositoryBuilder addTerminologyComponents(List<Class<? extends IComponent>> terminologyComponents) {
		return addTerminologyComponents(terminologyComponents.stream().map(Terminology::getAnnotation).collect(Collectors.toMap(tc -> tc.docType(), tc -> tc)));
	}
	
	public RepositoryBuilder addTerminologyComponents(Map<Class<?>, TerminologyComponent> terminologyComponents) {
		for (Entry<Class<?>, TerminologyComponent> terminologyComponent : terminologyComponents.entrySet()) {
			final Class<?> docType = terminologyComponent.getKey();
			this.terminologyComponents.add(terminologyComponent.getValue());
			this.mappings.putMapping(docType);
		}
		return this;
	}
	
	public RepositoryBuilder addMappings(Collection<Class<?>> mappings) {
		mappings.forEach(this.mappings::putMapping);
		return this;
	}
	
	public RepositoryBuilder withPreCommitHook(Hooks.PreCommitHook hook) {
		this.hook = hook;
		return this;
	}
	
	public RepositoryBuilder withVersioningRequestBuilder(VersioningRequestBuilder versioningRequestBuilder) {
		this.versioningRequestBuilder = versioningRequestBuilder;
		return this;
	}
	
	public RepositoryBuilder withComponentDeletionPolicy(ComponentDeletionPolicy deletionPolicy) {
		this.deletionPolicy = deletionPolicy;
		return this;
	}

	public RepositoryBuilder withComponentRevisionConflictProcessor(ComponentRevisionConflictProcessor componentRevisionConflictProcessor) {
		this.componentRevisionConflictProcessor = componentRevisionConflictProcessor;
		return this;
	}
	
	public Repository build(Environment env) {
		// get all repository configuration plugins and apply them to customize the repository
		List<TerminologyRepositoryConfigurer> repositoryConfigurers = ClassPathScanner.INSTANCE.getComponentsByInterface(TerminologyRepositoryConfigurer.class)
			.stream()
			.filter(configurer -> repositoryId.equals(configurer.getRepositoryId()))
			.collect(Collectors.toList());
		
		repositoryConfigurers
			.forEach(configurer -> {
				configurer.getAdditionalMappings().forEach(mappings::putMapping);
			});
		
		final TerminologyRepository repository = new TerminologyRepository(repositoryId, mergeMaxResults, env, mappings, log);
		repository.bind(VersioningRequestBuilder.class, versioningRequestBuilder);
		repository.bind(ComponentDeletionPolicy.class, deletionPolicy);
		repository.bind(ComponentRevisionConflictProcessor.class, componentRevisionConflictProcessor);
		repository.bind(TerminologyComponents.class, terminologyComponents);
		repository.activate();
		repository.service(RevisionIndex.class).hooks().addHook(hook);
		manager.put(repositoryId, repository);
		
		// execute initialization steps
		repository.waitForHealth(Health.GREEN, 3 * 60L /*wait 3 minutes for GREEN repository status*/);
		new IndexReadRequest<Void>((context) -> {
			initializer.initialize(context);
			return null;
		}).execute(manager.getContext(repositoryId));
		
		return repository;
	}

}

@@ -671,24 +671,90 @@
 epository;%0A%0A
+import static com.google.common.base.Preconditions.checkNotNull;%0A%0A
 import java.
@@ -3431,19 +3431,42 @@
 %7B%0A%09%09
-return addT
+for (Class%3C? extends IComponent%3E t
 ermi
@@ -3480,18 +3480,19 @@
 omponent
-s(
+ : 
 terminol
@@ -3508,22 +3508,41 @@
 ents
-.stream().map(
+) %7B%0A%09%09%09TerminologyComponent tc = 
 Term
@@ -3548,18 +3548,17 @@
 minology
-::
+.
 getAnnot
@@ -3566,66 +3566,244 @@
 tion
-).collect(Collectors.toMap(tc -%3E tc.docType(), tc -%3E tc)))
+(terminologyComponent);%0A%09%09%09checkNotNull(tc.docType(), %22Document must be specified for terminology component: %25s%22, terminologyComponent);%0A%09%09%09this.terminologyComponents.add(tc);%0A%09%09%09this.mappings.putMapping(tc.docType());%0A%09%09%7D%0A%09%09return this
 ;%0A%09%7D

fix terminology component registration in RepositoryBuilder