// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.opendata.core.modules;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import org.openstreetmap.josm.data.preferences.sources.ExtendedSourceEntry;
import org.openstreetmap.josm.data.preferences.sources.SourceEntry;
import org.openstreetmap.josm.data.preferences.sources.SourceProvider;
import org.openstreetmap.josm.plugins.opendata.OdPlugin;
import org.openstreetmap.josm.plugins.opendata.core.OdConstants;
import org.openstreetmap.josm.plugins.opendata.core.datasets.AbstractDataSetHandler;
import org.openstreetmap.josm.tools.Logging;

/**
 * A common {@link Module} base implementation
 */
public abstract class AbstractModule implements Module {

    protected final List<Class<? extends AbstractDataSetHandler>> handlers = new ArrayList<>();

    private final List<AbstractDataSetHandler> instantiatedHandlers = new ArrayList<>();

    protected final ModuleInformation info;

    protected AbstractModule(ModuleInformation info) {
        this.info = info;
    }

    @Override
    public ModuleInformation getModuleInformation() {
        return info;
    }

    @Override
    public List<Class<? extends AbstractDataSetHandler>> getHandlers() {
        return handlers;
    }

    @Override
    public String getDisplayedName() {
        return info.name;
    }

    @Override
    public SourceProvider getMapPaintStyleSourceProvider() {
        final List<SourceEntry> sources = new ArrayList<>();
        for (AbstractDataSetHandler handler : getInstantiatedHandlers()) {
            ExtendedSourceEntry src;
            if (handler != null && (src = handler.getMapPaintStyle()) != null) {
                // Copy style sheet to disk to allow JOSM to load it at startup
                // (even making the plugin "early" does not allow it)
                String path = OdPlugin.getInstance().getResourcesDirectory() + File.separator
                        + src.url.replace(OdConstants.PROTO_RSRC, "").replace('/', File.separatorChar);

                int n;
                byte[] buffer = new byte[4096];
                try (InputStream in = getClass().getResourceAsStream(
                        src.url.substring(OdConstants.PROTO_RSRC.length()-1));
                     FileOutputStream out = new FileOutputStream(path)) {
                    Objects.requireNonNull(in);
                    String dir = path.substring(0, path.lastIndexOf(File.separatorChar));
                    if (new File(dir).mkdirs() && Logging.isDebugEnabled()) {
                        Logging.debug("Created directory: "+dir);
                    }
                    while ((n = in.read(buffer)) > 0) {
                        out.write(buffer, 0, n);
                    }
                    // Add source pointing to the local file
                    src.url = OdConstants.PROTO_FILE+path;
                    sources.add(src);
                } catch (IOException e) {
                    Logging.error(e);
                }
            }
        }
        return sources.isEmpty() ? null : () -> sources;
    }

    @Override
    public SourceProvider getPresetSourceProvider() {
        final List<SourceEntry> sources = new ArrayList<>();
        for (AbstractDataSetHandler handler : getInstantiatedHandlers()) {
            if (handler != null && handler.getTaggingPreset() != null) {
                sources.add(handler.getTaggingPreset());
            }
        }
        return sources.isEmpty() ? null : () -> sources;
    }

    @Override
    public final List<AbstractDataSetHandler> getNewlyInstanciatedHandlers() {
        List<AbstractDataSetHandler> result = new ArrayList<>();
        for (Class<? extends AbstractDataSetHandler> handlerClass : handlers) {
            if (handlerClass != null) {
                try {
                    result.add(handlerClass.getConstructor().newInstance());
                } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException t) {
                    Logging.log(Logging.LEVEL_ERROR, "Cannot instantiate "+handlerClass+" because of "+t.getClass().getName(), t);
                }
            }
        }
        return result;
    }

    private List<AbstractDataSetHandler> getInstantiatedHandlers() {
        if (instantiatedHandlers.isEmpty()) {
            instantiatedHandlers.addAll(getNewlyInstanciatedHandlers());
        }
        return instantiatedHandlers;
    }
}
