package org.gcube.application.cms.implementations;


import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.gcube.application.cms.caches.Engine;
import org.gcube.application.geoportal.common.model.rest.ConfigurationException;
import org.gcube.common.storagehub.client.dsl.StorageHubClient;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class ImplementationProvider {

	private static ImplementationProvider instance=null;
	
	@Synchronized
	public static ImplementationProvider get() {
		if(instance==null) {
			instance=new ImplementationProvider();
		}
		return instance;
	}


	public <T> T getProvidedObjectByClass(Class<T> clazz) throws ConfigurationException {
		return (T) implementationsRegistry.get(clazz).getObject();
	}

	public <T> Engine<T> getEngineByManagedClass(Class<T> clazz) throws ConfigurationException {
		return implementationsRegistry.get(clazz);
	}

	private ConcurrentHashMap<Class,Engine> implementationsRegistry=new ConcurrentHashMap<>();

	public <T> void setEngine(Engine<T> engine, Class<T> clazz){
		implementationsRegistry.put(clazz,engine);
	}

	private ImplementationProvider(){
		//Defaults
		setEngine(new DefaultISProvider(),ISInterface.class);
		setEngine(new StorageHubProvider(), StorageHubClient.class);
	}
	public Map<Class,String> getManagerList(){
		HashMap<Class,String> toReturn=new HashMap<>();
		implementationsRegistry.forEach(
				(aClass, engine) -> {toReturn.put(engine.getClass(),aClass.getCanonicalName());}
		);
		return toReturn;
	}

	private boolean isInit=false;

	@Synchronized
	public void initEngines(){
		if(!isInit) {
			log.info("INITIALIZING ENGINES. Size : {} ", implementationsRegistry.size());
			HashSet<Class> failed = new HashSet<>();
			implementationsRegistry.forEach((aClass, engine) -> {
				log.info("Init : {} -> {}", engine.getClass().toGenericString(), aClass.getCanonicalName());
				try {
					engine.init();
				} catch (Throwable t) {
					failed.add(engine.getClass());
					log.error("Unable to start engine {} ", engine.getClass(), t);
				}
			});
			if (failed.isEmpty())
				log.info("INIT OK");
			else {
				log.warn("!!!!! Following  Engines FAILED INIT :");
				failed.forEach(aClass -> log.warn(String.valueOf(aClass)));
			}
			isInit = true;
		}else log.info("Received Init request but Engines already started");
	}

	public void shutdownEngines(){
		log.info("SHUTTING DOWN ENGINES. Size : {} ",implementationsRegistry.size());
		HashSet<Class> failed= new HashSet<>();
		implementationsRegistry.forEach((aClass, engine) -> {
			log.info("ShotDown : {} -> {}",engine.getClass().toGenericString(),aClass.getCanonicalName());
			try{
				engine.shutdown();
			}catch (Throwable t){
				failed.add(engine.getClass());
				log.error("Unable to shutdown engine {} ",engine.getClass(),t);
			}
		});
		if(failed.isEmpty())
			log.info("SHUTDOWN OK");
		else {
			log.warn("!!!!! Following  Engines FAILED SHUTDOWN :");
			failed.forEach(aClass->log.warn(String.valueOf(aClass)));
		}
	}
}
