/*-
 * ~~~~~~licensing~~~~~~
 * test-producers
 * ==========
 * Copyright (C) 2020 - 2022 EmDev LLC
 * ==========
 * 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.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.platform.core.producer.impl;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

import ru.entaxy.platform.base.objects.factory.EntaxyFactory;
import ru.entaxy.platform.core.producer.api.EntaxyProducer;
import ru.entaxy.platform.core.producer.wrapper.AbstractFactoryWrapper;
import ru.entaxy.platform.core.producer.wrapper.DefaultFactoryWrapper;

public abstract class DefaultProducer implements EntaxyProducer {

	private static final Logger log = LoggerFactory.getLogger(DefaultProducer.class);
	
	protected Class<? extends AbstractFactoryWrapper> factoryWrapperClass = DefaultFactoryWrapper.class;
	
	protected Map<EntaxyFactory, AbstractFactoryWrapper> factories = new HashMap<>();

	protected List<String> supportedTypes = new ArrayList<>();
	
	protected List<EntaxyProducerListener> listeners = new ArrayList<>();
	
	public DefaultProducer() {
		if (this.getClass().isAnnotationPresent(EntaxyProducerInfo.class)) {
			EntaxyProducerInfo info = this.getClass().getAnnotation(EntaxyProducerInfo.class);
			this.supportedTypes = Arrays.asList(info.supportedTypes());
			this.factoryWrapperClass = info.factoryWrapperClass();
		}
	}
	
	@Override
	public void addListener(EntaxyProducerListener listener) {
		synchronized (this.listeners) {
			this.listeners.remove(listener);
			this.listeners.add(listener);
		}
	}
	
	@Override
	public void removeListener(EntaxyProducerListener listener) {
		synchronized (this.listeners) {
			this.listeners.remove(listener);
		}
	}
	
	protected AbstractFactoryWrapper doAddFactory(EntaxyFactory factory) {
		return doAddFactory(factory, factoryWrapperClass);
	}
	protected AbstractFactoryWrapper doAddFactory(EntaxyFactory factory, Class<? extends AbstractFactoryWrapper> wrapperClass) {
		if (this.getSupportedTypes().contains(factory.getFactoryType())) {
			
			try {
				AbstractFactoryWrapper wrapper = createWrapper(factory, wrapperClass);
				addFields(wrapper);
				factories.put(factory, wrapper);
				
				synchronized (this.listeners) {
					for (EntaxyProducerListener listener: this.listeners)
						listener.factoryAdded(wrapper.getFactoryId(), wrapper.getFactoryType(), this);
				}
				
				log.debug("Factory added:" + factory.getFactoryId());
				return wrapper;
			} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
					| InvocationTargetException | NoSuchMethodException | SecurityException e) {
				log.error("FactoryWrapper creation failed: ", e);
				return null;
			}
		} else {
			log.debug("Factory addition ignored: [{}:{}] for types [{}]"
					, factory.getFactoryId()
					, factory.getFactoryType()
					, getSupportedTypes().stream().collect(Collectors.joining(",")));
			return null;
		}
	}
	
	protected AbstractFactoryWrapper createWrapper(EntaxyFactory factory, Class<? extends AbstractFactoryWrapper> wrapperClass) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
		return wrapperClass.getConstructor(EntaxyFactory.class).newInstance(factory);
	}
	
	protected void addFields(AbstractFactoryWrapper factoryWrapper) {
		
	}
	
	public void removeFactory(EntaxyFactory factory) {
		if (this.getSupportedTypes().contains(factory.getFactoryType())) {
			factories.remove(factory);
			synchronized (this.listeners) {
				for (EntaxyProducerListener listener: this.listeners)
					listener.factoryRemoved(factory.getFactoryId(), factory.getFactoryType(), this);
			}
			
			log.debug("Factory removed:" + factory.getFactoryId());
		} else {
			log.debug("Factory removal ignored:" + factory.getFactoryId());
		}
	}
	
	@Override
	public String getProducerId() {
		return this.getClass().getName();
	}

	@Override
	public List<EntaxyFactory> getAllFactories() {
		return new ArrayList<>(factories.values());
	}

	@Override
	public List<EntaxyFactory> getFactoriesForType(String factoryType) {
		return factories.values().stream().filter((f)->{return f.getFactoryType().equals(factoryType);})
				.collect(Collectors.toList());
	}

	@Override
	public EntaxyFactory getFactoryById(String factoryId) {
		for (EntaxyFactory f: factories.values())
			if (f.getFactoryId().equals(factoryId))
				return f;
		return null;
	}

	@Override
	public List<String> getSupportedTypes() {
		return this.supportedTypes;
	}
}
