/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package de.steamwar.linkage;

import de.steamwar.linkage.api.Disable;
import de.steamwar.linkage.api.Enable;
import lombok.NonNull;
import lombok.experimental.StandardException;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public abstract class AbstractLinker<T> {

    protected final T plugin;
    private Map<Class<?>, Object> instances = new HashMap<>();

    protected AbstractLinker(@NonNull T plugin) {
        this.plugin = plugin;
        instances.put(plugin.getClass(), plugin);
    }

    @StandardException
    public static class LinkException extends Exception {
    }

    public final void link() throws LinkException {
        List<Class<?>> classes;
        try {
            classes = new BufferedReader(new InputStreamReader(plugin.getClass().getResourceAsStream("/META-INF/annotations/de.steamwar.linkage.Linked")))
                    .lines()
                    .map(s -> {
                        try {
                            return Class.forName(s, false, plugin.getClass().getClassLoader());
                        } catch (NoClassDefFoundError error) {
                            return null;
                        } catch (ClassNotFoundException e) {
                            throw new SecurityException(e.getMessage(), e);
                        }
                    })
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
        } catch (SecurityException e) {
            Throwable cause = e.getCause();
            throw new LinkException(cause.getMessage(), cause);
        }

        try {
            classes.forEach(clazz -> {
                EventMode eventMode = clazz.getAnnotation(EventMode.class);
                if (!eventModeCheck(clazz, eventMode)) return;
                PluginCheck[] pluginChecks = clazz.getAnnotationsByType(PluginCheck.class);
                for (PluginCheck pluginCheck : pluginChecks) {
                    if (!pluginCheck(clazz, pluginCheck)) return;
                }

                Object any;
                try {
                    any = clazz.newInstance();
                } catch (InstantiationException | IllegalAccessException e) {
                    throw new SecurityException(e.getMessage(), e);
                }
                instances.put(clazz, any);

                if (any instanceof Enable) {
                    ((Enable) any).enable();
                }
                linkObject(any);
            });
        } catch (SecurityException e) {
            Throwable cause = e.getCause();
            throw new LinkException(cause.getMessage(), cause);
        }

        try {
            instances.forEach((clazz, o) -> {
                for (Field field : clazz.getDeclaredFields()) {
                    if (field.getAnnotation(LinkedInstance.class) != null) {
                        try {
                            field.setAccessible(true);
                            if (Modifier.isStatic(field.getModifiers())) {
                                field.set(null, instances.get(field.getType()));
                            } else {
                                field.set(o, instances.get(field.getType()));
                            }
                        } catch (IllegalAccessException e) {
                            throw new SecurityException(e.getMessage(), e);
                        }
                    }
                }
            });
        } catch (SecurityException e) {
            Throwable cause = e.getCause();
            throw new LinkException(cause.getMessage(), cause);
        }
    }

    public final void unlink() {
        instances.forEach((aClass, any) -> {
            unlinkObject(any);
            if (any instanceof Disable) {
                ((Disable) any).disable();
            }
        });
    }

    public final void addLinkableInstance(@NonNull Object instance) {
        instances.put(instance.getClass(), instance);
    }

    /**
     * @return {@code true} if the clazz passes the checks {@code false} otherwise
     */
    protected boolean pluginCheck(@NonNull Class<?> clazz, PluginCheck pluginCheck) {
        return true;
    }

    /**
     * @return {@code true} if the clazz passes the checks {@code false} otherwise
     */
    protected boolean eventModeCheck(@NonNull Class<?> clazz, EventMode eventMode) {
        return true;
    }

    /**
     * There is no need in calling {@link Enable#enable()} by this method.
     */
    protected void linkObject(Object any) {
    }

    /**
     * There is no need in calling {@link Disable#disable()} ()} by this method.
     */
    protected void unlinkObject(Object any) {
    }
}
