package ru.kirillius.pf.sdn.core;

import ru.kirillius.utils.logging.SystemLogger;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Manages lifecycle of dynamically configurable components, keeping them in sync with application settings.
 */
public final class ComponentHandlerService extends AppService {
    private final static String CTX = ComponentHandlerService.class.getSimpleName();

    private final List<Component<?>> loadedComponents = new ArrayList<>();

    /**
     * Constructs the service and captures the hosting context.
     */
    public ComponentHandlerService(Context context) {
        super(context);
    }



    /**
     * Unloads all currently managed components.
     */
    @Override
    public synchronized void close() throws IOException {
        unloadComponents(loadedComponents.toArray(Component[]::new));
    }

    /**
     * Retrieves a loaded component instance by class.
     */
    public synchronized Component<?> getComponentInstance(Class<? extends Component<?>> pluginClass) {
        return loadedComponents.stream().filter(plugin -> plugin.getClass().equals(pluginClass)).findFirst().orElse(null);
    }

    /**
     * Reloads the specified component classes by unloading then loading them again.
     */
    @SafeVarargs
    public final synchronized void reloadComponents(Class<? extends Component<?>>... classes) {
        Arrays.stream(classes)
                .forEach(componentClass -> {
                            loadedComponents.stream()
                                    .filter(component -> componentClass.equals(component.getClass()))
                                    .findFirst().ifPresent(this::unloadComponents);
                            loadComponents(componentClass);
                        }
                );

    }

    /**
     * Loads the specified component classes if not already present.
     */
    @SafeVarargs
    public final synchronized void loadComponents(Class<? extends Component<?>>... componentClasses) {
        for (var componentClass : componentClasses) {
            if (loadedComponents.stream().map(Component::getClass).anyMatch(componentClass::equals)) {
                SystemLogger.warning("Unable to load component " + componentClass.getSimpleName() + " because it is loaded already", CTX);
                continue;
            }
            SystemLogger.message("Loading component: " + componentClass.getSimpleName(), CTX);
            var plugin = Component.loadPlugin(componentClass, context);
            loadedComponents.add(plugin);
        }
    }

    /**
     * Unloads the given component instances and closes their resources.
     */
    public final synchronized void unloadComponents(Component<?>... components) {
        for (var component : components) {
            SystemLogger.message("Unloading component: " + component.getClass().getSimpleName(), CTX);
            try {
                component.close();
            } catch (IOException e) {
                SystemLogger.error("Error on component unload", CTX, e);
            } finally {
                loadedComponents.remove(component);
            }
        }
    }

    /**
     * Aligns loaded components with the set defined in configuration.
     */
    public final synchronized void syncComponentsWithConfig() {
        var config = context.getConfig();
        var enabledPlugins = config.getEnabledComponents();

        (List.copyOf(loadedComponents)).forEach(plugin -> {
            if (!enabledPlugins.contains(plugin.getClass())) {
                unloadComponents(plugin);
            }
        });
        var loadedClasses = loadedComponents.stream().map(plugin -> plugin.getClass()).toList();
        enabledPlugins.forEach(pluginClass -> {
            if (loadedClasses.contains(pluginClass)) {
                return;
            }
            loadComponents(pluginClass);
        });
    }

}
