@Deprecated
public enum CBVersion {
    /** Minecraft 1.12 */
    v1_12_R1(0, "v1_12_R1"),

    /** Minecraft 1.13 */
    v1_13_R1(1, "v1_13_R1"),

    /** Minecraft 1.13.1 */
    v1_13_R2(2, "v1_13_R2"),

    UNKNOWN(-1, ""),
    ;

    private final int id;
    private final String name;

    CBVersion(int id, String name) {
        this.id = id;
        this.name = name;
    }

    /**
     * Gets given CraftBukkit version id (used in Mixin environment)
     *
     * @return CraftBukkit version id
     */
    @Contract(pure = true)
    public int getId() {
        return id;
    }

    /**
     * Gets given CraftBukkit version name
     *
     * @return CraftBukkit version name
     */
    @NotNull
    @Contract(pure = true)
    public String getName() {
        return name;
    }
} public interface Orion {
    /**
     * Registers access transformer to be applied on server start.
     *
     * @param atUrl Resource {@link URL} of access transformer file (in other words {@link Class#getResource(String)})
     */
    void registerAT(@NotNull URL atUrl);

    /**
     * Registers mixin config.
     *
     * Note: Mixin configuration name format is checked on register.
     *
     * @param mixinConfigName Mixin configuration name in format: <pre>mixins.MOD_ID.WHATEVER.json</pre>
     */
    void registerMixinConfig(@NotNull String mixinConfigName);

    /**
     * Gets list of registered Maven repositories
     *
     * @return {@link List} of Maven repository {@link URL}s
     */
    @NotNull
    List<URL> getRegisteredMavenRepositories();

    /**
     * Registers Maven repository where libraries can be downloaded
     *
     * @param url Maven repository url
     * @see Orion#registerLibrary(String)
     */
    void registerMavenRepository(@NotNull URL url);

    /**
     * Gets list of registered mod libraries
     *
     * @return {@link List} of mod libraries
     * @see Orion#registerLibrary(String)
     */
    @NotNull
    List<String> getRegisteredLibraries();

    /**
     * Add new library for downloading from Maven repository.
     *
     * @param dependencyString Dependency string, like <pre>groupId:artifactId:version</pre>
     */
    void registerLibrary(@NotNull String dependencyString);

    /**
     * Gets unmodifiable list of loaded mods
     *
     * @return Unmodifiable list of loaded mods
     */
    @NotNull
    List<ModInfo> getMods();

    /**
     * Gets mod by its id
     *
     * @since 0.0.4-SNAPSHOT
     * @param modId Mod id
     * @return Instance of {@link ModInfo} or null, if mod wasn't found
     */
    @Nullable
    ModInfo getMod(@NotNull String modId);

    /**
     * Gets unmodifiable list of registered Mixin configurations
     *
     * @return Unmodifiable list of registered Mixin configurations
     */
    @NotNull
    List<String> getMixinConfigurations();

    /**
     * Register new {@link OrionTransformer} class
     *
     * @since 0.0.3-SNAPSHOT
     * @param transformer New transformer
     */
    void registerTransformer(@NotNull Class<? extends OrionTransformer> transformer);

    /**
     * Unregisters registered {@link OrionTransformer} class
     *
     * @since 0.0.3-SNAPSHOT
     * @param transformer Registered transformer. Check if transformer is registered with {@link #getRegisteredTransformers()}
     */
    void unregisterTransformer(@NotNull Class<? extends OrionTransformer> transformer);

    /**
     * Gets list of registered Orion bytecode transformers
     *
     * @since 0.0.3-SNAPSHOT
     * @return {@link List} of registered {@link OrionTransformer}s
     */
    @NotNull
    Set<Class<? extends OrionTransformer>> getRegisteredTransformers();

    /**
     * Gets mod asset manager
     *
     * @since 0.0.4-SNAPSHOT
     * @return Mod asset manager
     */
    @NotNull
    AssetManager.ForMod getAssetManager();
} public final class OrionAPI {
    /** {@link eu.mikroskeem.orion.api.Orion} instance */
    private static Orion instance;

    /**
     * Gets {@link Orion} instance
     *
     * @return {@link Orion} instance
     */
    @NotNull
    @Contract(pure = true)
    public static Orion getInstance() {
        return instance;
    }

    /**
     * Sets {@link Orion} instance. Can be done only once, as this class is singleton container
     *
     * @param instance {@link Orion}
     */
    @Contract("null -> fail")
    public static void setInstance(@NotNull Orion instance) {
        Objects.requireNonNull(instance, "Can not set Orion instance to null!");
        if(OrionAPI.instance != null) throw new IllegalStateException("Orion instance is already set!");
        OrionAPI.instance = instance;
    }
} public interface AssetManager {
    /**
     * Tries to get an asset from mod jar
     *
     * @param assetPath Asset path, like {@code config.cfg} or {@code lang/en_US.properties}
     * @return Instance of {@link InputStream} streaming asset contents or null, if asset couldn't be found
     */
    @Nullable
    InputStream getAsset(@NotNull String assetPath);

    /**
     * Copies asset from mod jar to path
     *
     * Does not overwrite destination file if already present
     *
     * @param destination Destination path
     * @param assetPath Asset path
     * @throws UncheckedIOException If asset copying fails either because of filesystem or resource finding error
     */
    void copyAsset(@NotNull Path destination, @NotNull String assetPath) throws UncheckedIOException;

    /**
     * Copies asset from mod jar to path
     *
     * @param destination Destination path
     * @param assetPath Asset path
     * @param overwriteExisting Whether to overwrite destination file if present
     * @throws UncheckedIOException If asset copying fails either because of filesystem or resource finding error
     */
    void copyAsset(@NotNull Path destination, @NotNull String assetPath, boolean overwriteExisting) throws UncheckedIOException;

    /**
     * Mod specific asset manager picker
     */
    interface ForMod {
        /**
         * Gets asset manager for mod specified with {@code modInfo}
         *
         * @param modInfo Instance of {@link ModInfo}
         * @return Mod specific {@link AssetManager}
         */
        @NotNull
        AssetManager forMod(@NotNull ModInfo modInfo);

        /**
         * Gets asset manager for mod specified with {@code modInfo}
         *
         * @param modId Mod id
         * @return Mod specific {@link AssetManager} or null, if no such mod exists
         */
        @Nullable
        AssetManager forMod(@NotNull String modId);
    }
} public interface OrionTransformer {
    /**
     * Transforms class
     *
     * @param source             Class bytecode. Might not be present
     * @param className          Class name
     * @param remappedClassName  Remapped class name. Might equal to {@code className}, if not remapped.
     * @return Transformed class or null
     */
    @Nullable
    byte[] transformClass(@Nullable byte[] source, @NotNull String className, @NotNull String remappedClassName);
} public class ObjectConfigurationLoader<T> {
    @NotNull private final String baseNodeName;
    @NotNull private final Class<T> configClass;
    @Nullable private final String header;

    /** Configuration file absolute location */
    @NotNull private final Path configurationPath;

    /** Configuration loader instance */
    @NotNull private final ConfigurationLoader<CommentedConfigurationNode> loader;

    /** Object mapper instance for {@code T} */
    @NotNull private final ObjectMapper<T>.BoundInstance mapper;

    /** Configuration lodaer base node */
    @Nullable private CommentedConfigurationNode baseNode;

    /** Configuration instance */
    @Nullable private T configuration;

    /** This configuration loader's default options */
    private ConfigurationOptions getDefaultOptions() {
        ConfigurationOptions options = ConfigurationOptions.defaults();
        if(header != null)
            options = options.setHeader(header);
        return options;
    }

    public ObjectConfigurationLoader(@NotNull Path configurationFile, @NotNull Class<T> configClass,
                                     @Nullable String baseNodeName, @Nullable String header) {
        this.configurationPath = configurationFile.toAbsolutePath();
        this.configClass = configClass;
        this.header = header;

        // Validate configuration file
        if(Files.isDirectory(configurationPath))
            throw new IllegalStateException("Path " + configurationPath.toString() + " is a directory!");

        try {
            if(Files.notExists(configurationPath))
                Files.createDirectories(configurationPath.getParent());
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }

        // Validate configuration class
        if(!Modifier.isPublic(configClass.getModifiers()))
            throw new IllegalStateException("configClass must be public!");

        if(Arrays.stream(configClass.getAnnotations()).noneMatch(a -> a instanceof ConfigSerializable))
            throw new IllegalStateException("configClass must have @ConfigSerializable annotation!");

        // Check if annotation defining base node name is present, and use annotation name from there
        ConfigurationBaseNode baseNodeAnnotation = configClass.getAnnotation(ConfigurationBaseNode.class);
        if(baseNodeAnnotation != null)
            baseNodeName = baseNodeAnnotation.value();

        this.baseNodeName = baseNodeName != null && !baseNodeName.isEmpty() ? baseNodeName : configClass.getName()
                .substring(configClass.getName().lastIndexOf('.') + 1)
                .toLowerCase(Locale.ENGLISH);

        // Validate base node name
        if(this.baseNodeName.isEmpty())
            throw new IllegalStateException("baseNodeName must not be empty!");

        // Build configuration loader
        loader = HoconConfigurationLoader.builder()
                .setDefaultOptions(getDefaultOptions())
                .setHeaderMode(HeaderMode.PRESERVE)
                .setPath(configurationPath)
                .build();

        // Build object mapper
        try {
            mapper = ObjectMapper.forClass(configClass).bindToNew();
        } catch (ObjectMappingException e) {
            SneakyThrow.throwException(e);
            throw null;
        }
    }

    public ObjectConfigurationLoader(@NotNull Path configurationFile, @NotNull Class<T> configClass) {
        this(configurationFile, configClass, null, null);
    }

    /**
     * Loads configuration
     */
    public void load() {
        try {
            baseNode = loader.load();
            configuration = mapper.populate(Objects.requireNonNull(baseNode).getNode(baseNodeName));
        } catch (ObjectMappingException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Saves configuration
     */
    public void save() {
        try {
            mapper.serialize(Objects.requireNonNull(baseNode).getNode(baseNodeName));
            loader.save(baseNode);
        } catch (ObjectMappingException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Gets configuration class instance
     *
     * @return Instance of {@code T}
     */
    @Contract(pure = true)
    public T getConfiguration() {
        return Objects.requireNonNull(configuration);
    }

    /**
     * Gets wrapped configuation loader instance
     *
     * @return Wrapped configuration loader instance
     */
    @NotNull
    public ConfigurationLoader<CommentedConfigurationNode> getLoader() {
        return loader;
    }
} public final class ModLoadEvent {} public interface ModInfo {
    /**
     * Gets mod id
     *
     * @return Mod id
     */
    @NotNull
    String getId();

    /**
     * Gets mod main class name
     *
     * @return Mod main class name
     */
    @NotNull
    String getClassName();

    /**
     * Gets mod dependencies
     *
     * @return Mod dependency list
     */
    @NotNull
    List<String> getDependencies();

    /**
     * Gets mod configuration class
     *
     * @return Mod configuration class
     */
    @NotNull
    Class<?> getConfigClass();
} public final class OrionMixinErrorHandler implements IMixinErrorHandler {
    private static final Logger logger = LogManager.getLogger("Orion Mixin Error Handler");

    @Override
    public ErrorAction onPrepareError(IMixinConfig config, Throwable th, IMixinInfo mixin, ErrorAction action) {
        return ErrorAction.ERROR;
    }

    @Override
    public ErrorAction onApplyError(String targetClassName, Throwable th, IMixinInfo mixin, ErrorAction action) {
        return ErrorAction.ERROR;
    }
} public final class OrionTokenProvider implements IEnvironmentTokenProvider {
    @Override
    public int getPriority() {
        return 0;
    }

    @Override
    public Integer getToken(String token, MixinEnvironment env) {
        if(token.equals("CB_VERSION"))
            return OrionCore.INSTANCE.getCBVersion().getId();

        return null;
    }
} public final class TypeLiteralGenerator {
    private final static AtomicInteger generatedCounter = new AtomicInteger(0);
    private final static Map<SignatureBuilder, TypeLiteral<?>> generated = new HashMap<>();
    private final static Type thisType = Type.getType(TypeLiteralGenerator.class);
    private final static Type typeLiteralType = Type.getType(TypeLiteral.class);

    @NotNull
    @SuppressWarnings("unchecked")
    public static <T> TypeLiteral<T> get(@NotNull SignatureBuilder signatureBuilder) {
        return (TypeLiteral<T>) generated.computeIfAbsent(signatureBuilder, c -> generate(of(TypeLiteral.class, signatureBuilder).build()));
    }

    @NotNull
    private static TypeLiteral<?> generate(@NotNull String signature) {
        // Figure out new inner class name
        String className = TypeLiteralGenerator.class.getName() + "$g" + generatedCounter.incrementAndGet();

        // Start writing an inner class
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);

        cw.visit(Opcodes.V1_8,
                Opcodes.ACC_FINAL | Opcodes.ACC_SUPER,
                ClassTools.unqualifyName(className),
                signature,
                typeLiteralType.getInternalName(),
                null);

        cw.visitOuterClass(thisType.getInternalName(), null, null);
        cw.visitInnerClass(ClassTools.unqualifyName(className), null, null, Opcodes.ACC_STATIC);
        ClassTools.generateSimpleSuperConstructor(cw, TypeLiteral.class);
        cw.visitEnd();

        // Load TypeLiteral extending class
        byte[] classData = cw.toByteArray();

        ClassLoader loader = BlackboardKey.<LauncherService>get(BlackboardKey.LAUNCHER_SERVICE).getClassLoader();
        @SuppressWarnings("unchecked")
        Class<? extends TypeLiteral<?>> definedLiteral = (Class<? extends TypeLiteral<?>>)
                Objects.requireNonNull(ClassLoaderTools.defineClass(loader, className, classData), "Failed to define TypeLiteral class: " + className);

        return Objects.requireNonNull(Reflect.wrapClass(definedLiteral).construct().getClassInstance(), "Class instance was null!");
    }

    public final static class SignatureBuilder {
        private final String base;
        private final List<SignatureBuilder> parameters;

        private SignatureBuilder(@NotNull String base, @NotNull List<SignatureBuilder> parameters) {
            this.base = base;
            this.parameters = parameters;
        }

        @NotNull
        public String build() {
            StringBuilder builder = new StringBuilder();
            builder.append('L')
                   .append(base);
            if(parameters.size() > 0) {
                builder.append('<');
                for(SignatureBuilder subBuilder : parameters)
                    builder.append(subBuilder.build());
                builder.append('>');
            }
            builder.append(';');
            return builder.toString();
        }

        @Override
        public boolean equals(Object o) {
            if(this == o) return true;
            if(!(o instanceof SignatureBuilder)) return false;
            SignatureBuilder that = (SignatureBuilder) o;
            return Objects.equals(base, that.base) &&
                    Objects.equals(parameters, that.parameters);
        }

        @Override
        public int hashCode() {
            return Objects.hash(base, parameters);
        }

        @NotNull
        public static SignatureBuilder of(@NotNull Class<?> base, @NotNull SignatureBuilder... parameters) {
            return of(Type.getType(base), parameters);
        }

        @NotNull
        public static SignatureBuilder of(@NotNull Type base, @NotNull SignatureBuilder... parameters) {
            return new SignatureBuilder(base.getInternalName(), Arrays.asList(parameters));
        }
    }
} public abstract class AbstractLauncherService implements LauncherService {
    private Map<String, Object> fallback$blackboard;

    @NotNull
    @Override
    public Map<String, Object> getBlackBoard() {
        if(fallback$blackboard == null) fallback$blackboard = new HashMap<>();
        return fallback$blackboard;
    }

    @Override
    public abstract void registerTransformer(@NotNull Class<? extends OrionTransformer> transformer);

    @NotNull
    @Override
    public abstract Set<String> getClassLoaderExclusions();
} public enum BlackboardKey {
    LAUNCHER_SERVICE("orion.launch.service", AbstractLauncherService.class, false),

    ORIGINAL_ARGUMENTS("orion.launch.arguments", List.class),
    LAUNCH_TARGET("orion.launch.target", String.class),

    LIBRARIES_PATH("orion.path.libraries", Path.class, false),
    MODS_PATH("orion.path.mods", Path.class, false),
    MOD_CONFIGS_PATH("orion.path.modconfigs", Path.class, false),

    AT_URLS("orion.at.urls", List.class, ArrayList<URL>::new)
    ;

    private final String key;
    private final Class<?> type;
    private final boolean mutable;
    @Nullable private final Supplier<?> initializer;

    BlackboardKey(@NotNull String key, @NotNull Class<?> type) {
        this.key = key;
        this.type = type;
        this.mutable = true;
        this.initializer = null;
    }

    BlackboardKey(@NotNull String key, @NotNull Class<?> type, boolean mutable) {
        this.key = key;
        this.type = type;
        this.mutable = mutable;
        this.initializer = null;
    }

    BlackboardKey(@NotNull String key, @NotNull Class<?> type, @Nullable Supplier<?> initializer) {
        this.key = key;
        this.type = type;
        this.mutable = true;
        this.initializer = initializer;
    }

    private static Map<String, Object> blackboard = new HashMap<>();

    public static void setBlackboard(@NotNull Map<String, Object> blackboard) {
        if(!BlackboardKey.blackboard.isEmpty()) {
            blackboard.putAll(BlackboardKey.blackboard);
        }
        BlackboardKey.blackboard = blackboard;
    }

    @NotNull
    @SuppressWarnings("unchecked")
    public static <T> T get(@NotNull BlackboardKey key) {
        if(key.initializer != null)
            return (T) getOr(key, key.initializer);
        return actualGet(key);
    }

    @NotNull
    public static <T> T getOr(@NotNull BlackboardKey key, @NotNull Supplier<T> def) {
        try {
            return actualGet(key);
        } catch (NullPointerException e) {
            T value = requireNonNull(def.get(), "Supplier should not return null!");
            set(key, value);
            return value;
        }
    }

    public static <T> void set(@NotNull BlackboardKey key, @NotNull T value) {
        if(blackboard.containsKey(key.key) && !key.mutable)
            throw new IllegalArgumentException("Key " + key.key + " is not mutable");
        blackboard.put(key.key, requireNonNull(key.type.cast(value), "Value is null"));
    }

    public static void unset(@NotNull BlackboardKey key) {
        if(!key.mutable) throw new IllegalArgumentException("Key " + key.key + " is not mutable");
        blackboard.remove(key.key);
    }

    @SuppressWarnings("unchecked")
    private static <T> T actualGet(@NotNull BlackboardKey key) {
        Object value = requireNonNull(blackboard.get(key.key), "No value in blackboard with key " + key.key);
        return (T) key.type.cast(value);
    }
} public interface LauncherService {
    @NotNull
    Map<String, Object> getBlackBoard();

    void registerTransformer(@NotNull Class<? extends OrionTransformer> transformer);

    @NotNull
    Set<String> getClassLoaderExclusions();

    @NotNull
    ClassLoader getClassLoader();

    void addURLToClassLoader(@NotNull URL url);
} public final class ModClassVisitor extends ClassVisitor {
    private final static Type ORIONMOD_ANNOTATION = Type.getType(OrionMod.class);

    private ModClassVisitor() {
        super(Opcodes.ASM5);
    }

    /** Mod annotation visitor instance */
    @Nullable private ModAnnotationVisitor modAnnotationVisitor;

    /** Visitable class name */
    @Nullable private String className;

    /**
     * Gets mod info from class bytes
     *
     * @param classData Class bytes
     * @return Instance of {@link ModInfo}, or null if not present
     */
    @Nullable
    public static ModInfo getModInfo(byte[] classData) {
        ClassReader classReader = new ClassReader(classData);
        ModClassVisitor modClassVisitor = new ModClassVisitor();
        classReader.accept(modClassVisitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
        return modClassVisitor.modAnnotationVisitor != null &&  modClassVisitor.modAnnotationVisitor.gotId ?
                modClassVisitor.modAnnotationVisitor.modInfo : null;
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        this.className = name;
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        return (visible && desc.equals(ORIONMOD_ANNOTATION.getDescriptor())) ?
                modAnnotationVisitor = new ModAnnotationVisitor(className) : super.visitAnnotation(desc, visible);
    }

    static class ModAnnotationVisitor extends AnnotationVisitor {
        private final List<String> dependencies = new ArrayList<>();
        private State currentState = State.DEFAULT;
        private OrionModInfo modInfo;
        private boolean gotId = false;

        private enum State {
            DEFAULT, DEPENDENCIES
        }

        ModAnnotationVisitor(String className) {
            super(Opcodes.ASM5);
            modInfo = new OrionModInfo();
            modInfo.setDependencies(dependencies);
            modInfo.setConfigClass(DummyConfiguration.class.getName());
            modInfo.setClassName(className.replace('/', '.'));
        }

        private void check(State state) {
            if(currentState != state)
                    throw new IllegalStateException("Expected state " + state + ", but is " + currentState);
        }

        @Override
        public void visit(String name, Object value) {
            if(currentState == State.DEPENDENCIES) {
                dependencies.add((String)value);
                return;
            }

            check(State.DEFAULT);
            Objects.requireNonNull(name, "Name is null");
            switch (name) {
                case "id":
                    gotId = true;
                    modInfo.setId((String)value);
                    break;
                case "configurationClass":
                    Type configClass = (Type) value;
                    modInfo.setConfigClass(configClass.getClassName());
                    break;
                default:
                    super.visit(name, value);
            }
        }

        @Override
        public AnnotationVisitor visitArray(String name) {
            Objects.requireNonNull(name, "Name is null");
            switch (name) {
                case "dependencies":
                    this.currentState = State.DEPENDENCIES;
                    return this;
                default:
                    return super.visitArray(name);
            }
        }

        @Override
        public void visitEnd() {
            if(this.currentState != State.DEFAULT) {
                this.currentState = State.DEFAULT;
                return;
            }

            if(!gotId)
                throw new IllegalStateException("Mod annotation doesn't have required element 'id'");
        }
    }
} public final class ModContainer<T> {
    private final ClassWrapper<T> modClass;
    private final ModInfo modInfo;
    private final Injector injector;
    private final EventBus eventBus;

    public ModContainer(@NotNull ClassWrapper<T> modClass, @NotNull ModInfo modInfo,
                        @NotNull Injector injector, @NotNull EventBus eventBus) {
        this.modClass = modClass;
        this.modInfo = modInfo;
        this.injector = injector;
        this.eventBus = eventBus;
    }

    /**
     * Constructs mod's main class
     */
    public void construct() {
        if(modClass.getClassInstance() != null) throw new IllegalStateException("Mod is already initialized!");
        T instance = injector.getInstance(modClass.getWrappedClass());
        modClass.setClassInstance(instance);
        eventBus.register(instance);
        eventBus.post(new ModConstructEvent());
    }

    /**
     * Gets mod info
     *
     * @return instance of {@link ModInfo}
     */
    @NotNull
    @Contract(pure = true)
    public ModInfo getModInfo() {
        return modInfo;
    }

    /**
     * Gets mod event bus
     *
     * @return {@link EventBus} of mod
     */
    @NotNull
    @Contract(pure = true)
    public EventBus getEventBus() {
        return eventBus;
    }
} @NotNull
public static SignatureBuilder of(@NotNull Class<?> base, @NotNull SignatureBuilder... parameters) {
    return of(Type.getType(base), parameters);
}
import com.google.common.eventbus.EventBus;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import eu.mikroskeem.orion.api.CBVersion;
import eu.mikroskeem.orion.api.Orion;
import eu.mikroskeem.orion.api.OrionAPI;
import eu.mikroskeem.orion.api.asset.AssetManager;
import eu.mikroskeem.orion.api.bytecode.OrionTransformer;
import eu.mikroskeem.orion.api.configuration.ObjectConfigurationLoader;
import eu.mikroskeem.orion.api.events.ModLoadEvent;
import eu.mikroskeem.orion.api.mod.ModInfo;
import eu.mikroskeem.orion.core.extensions.OrionMixinErrorHandler;
import eu.mikroskeem.orion.core.extensions.OrionTokenProvider;
import eu.mikroskeem.orion.core.guice.TypeLiteralGenerator;
import eu.mikroskeem.orion.core.launcher.AbstractLauncherService;
import eu.mikroskeem.orion.core.launcher.BlackboardKey;
import eu.mikroskeem.orion.core.launcher.LauncherService;
import eu.mikroskeem.orion.core.mod.ModClassVisitor;
import eu.mikroskeem.orion.core.mod.ModContainer;
import eu.mikroskeem.picomaven.Dependency;
import eu.mikroskeem.picomaven.DownloaderCallbacks;
import eu.mikroskeem.picomaven.PicoMaven;
import eu.mikroskeem.shuriken.common.SneakyThrow;
import eu.mikroskeem.shuriken.common.ToURL;
import eu.mikroskeem.shuriken.common.data.Pair;
import eu.mikroskeem.shuriken.common.streams.ByteArrays;
import eu.mikroskeem.shuriken.reflect.ClassWrapper;
import eu.mikroskeem.shuriken.reflect.Reflect;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.loader.ConfigurationLoader;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.launch.MixinBootstrap;
import org.spongepowered.asm.mixin.MixinEnvironment;
import org.spongepowered.asm.mixin.Mixins;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static eu.mikroskeem.orion.core.guice.TypeLiteralGenerator.SignatureBuilder.of;
/*
 * This file is part of project Orion, licensed under the MIT License (MIT).
 *
 * Copyright (c) 2017-2020 Mark Vainomaa <mikroskeem@mikroskeem.eu>
 * Copyright (c) Contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package eu.mikroskeem.orion.core;





/**
 * The mighty Orion Core
 *
 * @author Mark Vainomaa
 */
public final class OrionCore {
    /* Constants */
    private static final TypeLiteral<ConfigurationLoader<CommentedConfigurationNode>> COMMENTED_CONFIGURATION_NODE_LOADER =
            new TypeLiteral<ConfigurationLoader<CommentedConfigurationNode>>() {};

    /* Logger */
    private static final Logger logger = LogManager.getLogger("OrionCore");

    /** Static instance */
    public final static OrionCore INSTANCE = new OrionCore();

    private final AbstractLauncherService launcherService;
    private OrionAPIImpl orionAPI;
    private Injector baseInjector;

    final List<ModContainer<?>> mods = new ArrayList<>();
    final List<String> mixinConfigurations = new ArrayList<>();
    final List<URI> modMavenRepositories = new ArrayList<>();
    final List<Dependency> modLibraries = new ArrayList<>();
    final Set<Class<? extends OrionTransformer>> transformers = new HashSet<>();

    /**
     * Private constructor to set up Mixin loader
     */
    private OrionCore() {
        launcherService = BlackboardKey.get(BlackboardKey.LAUNCHER_SERVICE);

        logger.debug("Setting up SpongeMixin library...");
        MixinBootstrap.init();

        logger.debug("Setting Mixin Environment side to SERVER");
        MixinEnvironment.getDefaultEnvironment().setSide(MixinEnvironment.Side.SERVER);

        logger.debug("Setting up Mixin error handler");
        Mixins.registerErrorHandlerClass(OrionMixinErrorHandler.class.getName());

        logger.debug("Setting up Mixin token provider");
        MixinEnvironment.getDefaultEnvironment().registerTokenProviderClass(OrionTokenProvider.class.getName());

        logger.debug("Mixin library initialization finished!");
    }

    /**
     * Gets detected CraftBukkit version
     *
     * @return Detected {@link CBVersion}
     */
    @NotNull
    @Contract(pure = true)
    public CBVersion getCBVersion() {
        return CBVersion.UNKNOWN;
    }

    /**
     * Sets up Orion transformers
     */
    public void setupTransformers() {
        /* Access transformer */
        launcherService.registerTransformer(OrionAccessTransformer.class);

        /* Mod provided transformers */
        transformers.forEach(launcherService::registerTransformer);
    }

    /**
     * Sets up Orion core mixins and transformers
     */
    public void setupCore() {
        logger.debug("Adding class load exclusions");

        /* Remove invalid exclusion */
        launcherService.getClassLoaderExclusions().remove("org.apache.logging.");

        launcherService.getClassLoaderExclusions().addAll(Arrays.asList(
            /* API */
            "eu.mikroskeem.orion.api",

            /* Don't try to transform the access transformer */
            "eu.mikroskeem.orion.at",

            /* Library packages */
            "ninja.leaping.configurate",
            "javax.inject",
            "joptsimple",
            "gnu.trove",
            "it.unimi.dsi.fastutil",
            "org.apache.logging.log4j",
            "org.yaml.snakeyaml",
            "com.google.inject",
            "com.google.common",
            "com.google.gson",
            "javax.annotation",
            "org.apache.commons",

            /* Note: magical lines to fix logging */
            "net.minecrell.terminalconsole",
            "com.sun.jna",
            "org.fusesource.jansi",
            "org.jline"
        ));

        logger.debug("Setting up OrionAPI singleton");
        orionAPI = new OrionAPIImpl(this);
        OrionAPI.setInstance(orionAPI);

        /* Add Maven Central to repository list */
        try {
            orionAPI.registerMavenRepository(new URL("https://repo.maven.apache.org/maven2"));
        } catch (Exception e) {
            SneakyThrow.throwException(e);
        }

        logger.debug("Setting up dependency injector...");
        baseInjector = Guice.createInjector(b -> b.bind(Orion.class).toInstance(orionAPI));
    }

    /**
     * Sets up Orion mods
     *
     * @param launcherService Minecraft server launcher service
     * @param modsDirectory Directory where mods should reside
     * @throws IOException If {@link Files#list(Path)} fails
     */
    @Contract("null, null -> fail")
    public void setupMods(LauncherService launcherService, Path modsDirectory) throws IOException {