/*-
 * ~~~~~~licensing~~~~~~
 * test-producers
 * ==========
 * Copyright (C) 2020 - 2025 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/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.regex.Pattern;
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.api.EntaxyProducerUtils;
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<String, AbstractFactoryWrapper> factories = new HashMap<>();

    protected List<String> supportedTypesOrigin = new ArrayList<>();

    protected List<String> supportedTypes = new ArrayList<>();
    protected List<String> supportedTypesPatterns = new ArrayList<>();

    protected List<EntaxyProducerListener> listeners = new ArrayList<>();

    public DefaultProducer() {
        if (this.getClass().isAnnotationPresent(EntaxyProducerInfo.class)) {
            EntaxyProducerInfo info = this.getClass().getAnnotation(EntaxyProducerInfo.class);
            setSupportedTypes(Arrays.asList(info.supportedTypes()));
            this.factoryWrapperClass = info.factoryWrapperClass();
        }
    }

    protected void setSupportedTypes(List<String> typesToSet) {
        this.supportedTypesOrigin = typesToSet;
        for (String s : typesToSet) {
            if (EntaxyProducerUtils.isPattern(s)) {
                this.supportedTypesPatterns.add(EntaxyProducerUtils.toRegexPattern(s));
            } else {
                this.supportedTypes.add(s);
            }
        }
    }

    @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 (isTypeSupported(factory.getType())) {

            try {
                AbstractFactoryWrapper wrapper = createWrapper(factory, wrapperClass);
                addFields(wrapper);
                factories.put(factory.getId(), wrapper);

                synchronized (this.listeners) {
                    for (EntaxyProducerListener listener : this.listeners)
                        listener.factoryAdded(wrapper.getId(), wrapper.getType(), this);
                }

                log.debug("Factory added:" + factory.getId());
                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.getId(), factory.getType(),
                    getSupportedTypes().stream().collect(Collectors.joining(",")));
            return null;
        }
    }

    protected boolean isTypeSupported(String type) {
        if (this.supportedTypes.contains(type))
            return true;
        for (String s : supportedTypesPatterns)
            if (Pattern.matches(s, type))
                return true;
        return false;
    }

    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 (isTypeSupported(factory.getType())) {
            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.supportedTypesOrigin;
    }
}
