/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.quarkus.component.xchange.deployment;

import java.lang.reflect.Modifier;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import io.quarkus.bootstrap.model.ApplicationModel;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.IndexDependencyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
import io.quarkus.maven.dependency.ResolvedDependency;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.knowm.xchange.BaseExchange;
import org.knowm.xchange.dto.Order;

class XchangeProcessor {

    private static final String FEATURE = "camel-xchange";

    @BuildStep
    FeatureBuildItem feature() {
        return new FeatureBuildItem(FEATURE);
    }

    @BuildStep
    ExtensionSslNativeSupportBuildItem activateSslNativeSupport() {
        return new ExtensionSslNativeSupportBuildItem(FEATURE);
    }

    @BuildStep
    void indexDependenciesAndResources(
            BuildProducer<IndexDependencyBuildItem> indexedDependency,
            BuildProducer<NativeImageResourceBuildItem> nativeImageResource,
            CurateOutcomeBuildItem curateOutcome) {

        ApplicationModel applicationModel = curateOutcome.getApplicationModel();
        for (ResolvedDependency dependency : applicationModel.getDependencies()) {
            if (dependency.getGroupId().equals("org.knowm.xchange")) {
                // Index any org.knowm.xchange dependencies present on the classpath as they contain the APIs for interacting with each crypto exchange
                String artifactId = dependency.getArtifactId();
                indexedDependency.produce(new IndexDependencyBuildItem(dependency.getGroupId(), artifactId));

                // Include crypto exchange metadata resources
                String[] split = artifactId.split("-");
                if (split.length > 1) {
                    String cryptoExchange = split[split.length - 1];
                    nativeImageResource.produce(new NativeImageResourceBuildItem(cryptoExchange + ".json"));
                }
            }
        }

        indexedDependency.produce(new IndexDependencyBuildItem("jakarta.ws.rs", "jakarta.ws.rs-api"));
    }

    @BuildStep
    void registerForReflection(BuildProducer<ReflectiveClassBuildItem> reflectiveClass, CombinedIndexBuildItem combinedIndex) {
        // The xchange component dynamically instantiates Exchange classes so they must be registered for reflection
        IndexView index = combinedIndex.getIndex();
        String[] xchangeClasses = index.getAllKnownSubclasses(DotName.createSimple(BaseExchange.class.getName()))
                .stream()
                .map(classInfo -> classInfo.name().toString())
                .toArray(String[]::new);
        reflectiveClass.produce(ReflectiveClassBuildItem.builder(xchangeClasses).build());

        // DTO classes need to be serialized / deserialized
        final Pattern pattern = Pattern.compile("^org\\.knowm\\.xchange.*dto.*");
        String[] dtoClasses = index.getKnownClasses()
                .stream()
                .map(classInfo -> classInfo.name().toString())
                .filter(className -> pattern.matcher(className).matches())
                .toArray(String[]::new);
        reflectiveClass.produce(ReflectiveClassBuildItem.builder(dtoClasses).methods().fields().build());

        // rescu REST framework needs reflective access to the value method on some JAX-RS annotations
        String[] jaxrsAnnotations = index.getKnownClasses()
                .stream()
                .filter(ClassInfo::isAnnotation)
                .filter(classInfo -> classInfo.firstMethod("value") != null)
                .map(classInfo -> classInfo.name().toString())
                .filter(className -> className.startsWith("jakarta.ws.rs"))
                .toArray(String[]::new);
        reflectiveClass.produce(ReflectiveClassBuildItem.builder(jaxrsAnnotations).methods().build());

        // reflective instantiation of xchange converters
        String[] converterClasses = index.getAllKnownSubclasses("com.fasterxml.jackson.databind.util.StdConverter")
                .stream()
                .map(classInfo -> classInfo.name().toString())
                .filter(className -> className.startsWith("org.knowm.xchange"))
                .toArray(String[]::new);
        reflectiveClass.produce(ReflectiveClassBuildItem.builder(converterClasses).build());
    }

    @BuildStep
    void registerProxyClasses(BuildProducer<NativeImageProxyDefinitionBuildItem> nativeImageProxy,
            CombinedIndexBuildItem combinedIndexBuildItem) {
        // Some xchange libraries use JAX-RS proxies to interact with the exchange APIs so we need to register them
        IndexView index = combinedIndexBuildItem.getIndex();
        index.getAnnotations(DotName.createSimple("jakarta.ws.rs.Path"))
                .stream()
                .map(AnnotationInstance::target)
                .filter(target -> target.kind().equals(AnnotationTarget.Kind.CLASS))
                .map(AnnotationTarget::asClass)
                .filter(classInfo -> Modifier.isInterface(classInfo.flags()))
                .map(classInfo -> classInfo.name().toString())
                .filter(className -> className.startsWith("org.knowm.xchange"))
                .map(NativeImageProxyDefinitionBuildItem::new)
                .forEach(nativeImageProxy::produce);
    }

    @BuildStep
    void runtimeInitializedClasses(BuildProducer<RuntimeInitializedClassBuildItem> runtimeInitializedClasses) {
        Stream.of(Order.class.getName())
                .map(RuntimeInitializedClassBuildItem::new)
                .forEach(runtimeInitializedClasses::produce);
    }

    @BuildStep
    void registerResourceBundles(BuildProducer<NativeImageResourceBundleBuildItem> producer) {
        producer.produce(new NativeImageResourceBundleBuildItem("sun.util.resources.CurrencyNames", "java.base"));
    }

}
