package org.gcube.application.geoportal.service.engine.providers;

import lombok.extern.slf4j.Slf4j;
import org.gcube.application.cms.caches.AbstractScopedMap;
import org.gcube.application.cms.plugins.*;
import org.gcube.application.cms.plugins.faults.InitializationException;
import org.gcube.application.cms.plugins.faults.PluginExecutionException;
import org.gcube.application.cms.plugins.faults.ShutDownException;
import org.gcube.application.cms.plugins.reports.InitializationReport;
import org.gcube.application.geoportal.common.model.rest.ConfigurationException;

import java.util.HashMap;
import java.util.Map;

@Slf4j
public class PluginManager extends AbstractScopedMap<PluginManager.PluginMap> implements PluginManagerInterface {


    public static class PluginMap extends HashMap<String,Plugin>{
    }

    PluginMap implementations=new PluginMap();


    public PluginManager(){
        super("Plugin Cache");
        // reflections

        implementations.putAll(PluginsReflections.load());
        log.info("Loaded {} plugins",implementations.keySet().size());




        // Init plugins
        implementations.forEach((id,p)->{
            log.info("Registering plugin Manager ");
            if(p instanceof LifecycleManager) {
                log.debug("Registering plugin Manager to {} ",id);
                ((LifecycleManager) p).setPluginManager(this);
            }

            if(p instanceof InitializablePlugin){
                log.info("INIT Plugin {}",id);
                try {
                    InitializablePlugin ip=(InitializablePlugin) p;
                    logReport(ip,ip.init());
                }catch (InitializationException e){
                    log.error("Failed to initialize "+id,e);
                }catch(Throwable t){
                    log.error("Unable to initialize "+id,t);
                }
            }
        });
    }


    @Override
    protected PluginMap retrieveObject(String context) throws ConfigurationException {
        log.warn("PLUGIN INITIALIZTIONS IN CONTEXT {} ",context);
        // Init plugins
        implementations.forEach((id,p)->{
            if(p instanceof InitializablePlugin){
                log.info("INIT Plugin {} in context {} ",id, context);
                try {
                    InitializablePlugin ip=(InitializablePlugin) p;
                    logReport(ip,ip.initInContext());
                }catch (InitializationException e){
                    log.error("Failed to initialize "+id,e);
                }catch(Throwable t){
                    log.error("Unable to initialize "+id,t);
                }
            }
        });
        return implementations;
    }

    @Override
    protected void dispose(PluginMap toDispose) {
        // ShutDown plugins
        implementations.forEach((id,p)->{
            if(p instanceof InitializablePlugin){
                log.info("Shutting down Plugin {}",id);
                try {
                    InitializablePlugin ip=(InitializablePlugin) p;
                    ip.shutdown();
                }catch (ShutDownException e){
                    log.error("Failed to shutdown "+id,e);
                }catch(Throwable t){
                    log.error("Unable to shutdown "+id,t);
                }
            }
        });
    }

    @Override
    public void init() {

    }

    @Override
    public Plugin getById(String pluginID) throws ConfigurationException {
        Plugin toReturn = getObject().get(pluginID);
        if(toReturn == null ) throw new ConfigurationException("Plugin "+pluginID+" not found ");
        return toReturn;
    }

    private static final void logReport(Plugin p, InitializationReport report){
        if(report==null)
            log.warn("WARNING : Initialization Report by {} IS null ",p.getDescriptor().getId());
        else
            try {
                report.validate();
                switch (report.getStatus()) {
                    case ERROR: {
                        log.error("Plugin [{}] STATUS : {}, INFO {} ", p.getDescriptor().getId(), report.getStatus(), report.getMessages());
                        break;
                    }
                    case WARNING: {
                        log.warn("Plugin [{}] STATUS : {}, INFO {} ", p.getDescriptor().getId(), report.getStatus(), report.getMessages());
                        break;
                    }
                    default: {
                        log.info("Plugin [{}] STATUS : {}, INFO {} ", p.getDescriptor().getId(), report.getStatus(), report.getMessages());
                    }
                }
            }catch (PluginExecutionException e) {
                log.warn("Invalid report provided by {} ",p.getDescriptor().getId(),e);
            }
    }


    @Override
    public Map<String, Plugin> getByType(String type) throws ConfigurationException {
        HashMap<String,Plugin> toReturn = new HashMap<>();
        getObject().forEach((s, plugin) -> {
            if (plugin!=null&&plugin.getDescriptor().getType().equals(type))
                toReturn.put(s,plugin);
        });
        return toReturn;
    }
}
