/*
 * 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.gravitino.maintenance.optimizer.common.util;

import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import java.util.List;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import org.apache.gravitino.maintenance.optimizer.api.common.Provider;
import org.apache.gravitino.maintenance.optimizer.api.monitor.MetricsProvider;
import org.apache.gravitino.maintenance.optimizer.api.monitor.MonitorCallback;
import org.apache.gravitino.maintenance.optimizer.api.monitor.TableJobRelationProvider;
import org.apache.gravitino.maintenance.optimizer.api.recommender.JobSubmitter;
import org.apache.gravitino.maintenance.optimizer.api.recommender.StatisticsProvider;
import org.apache.gravitino.maintenance.optimizer.api.recommender.StrategyProvider;
import org.apache.gravitino.maintenance.optimizer.api.recommender.TableMetadataProvider;
import org.apache.gravitino.maintenance.optimizer.api.updater.MetricsUpdater;
import org.apache.gravitino.maintenance.optimizer.api.updater.StatisticsUpdater;

/** Helper for loading optimizer providers via {@link ServiceLoader}. */
public class ProviderUtils {

  public static <T extends Provider> T createProviderInstance(
      Class<T> providerClass, String provider) {
    ServiceLoader<Provider> loader = ServiceLoader.load(Provider.class);
    List<Class<? extends T>> providers =
        Streams.stream(loader.iterator())
            .filter(p -> provider.equalsIgnoreCase(p.name()))
            .filter(providerClass::isInstance)
            .map(p -> (Class<? extends T>) p.getClass())
            .collect(Collectors.toList());

    String providerClassName = Provider.class.getName();

    if (providers.isEmpty()) {
      throw new IllegalArgumentException(
          "No "
              + providerClass.getSimpleName()
              + " found for provider name '"
              + provider
              + "'. Ensure the provider is registered in META-INF/services/"
              + providerClassName
              + " and the implementation is on the classpath, and that the provider's name()"
              + " method returns the expected value.");
    } else if (providers.size() > 1) {
      throw new IllegalArgumentException(
          "Multiple "
              + providerClass.getSimpleName()
              + " implementations found for provider name '"
              + provider
              + "'. Ensure only one implementation with this name exists in META-INF/services/"
              + providerClassName
              + ".");
    } else {
      Class<? extends T> providerClz = Iterables.getOnlyElement(providers);
      try {
        return providerClz.getDeclaredConstructor().newInstance();
      } catch (Exception e) {
        throw new RuntimeException("Failed to instantiate provider: " + providerClz.getName(), e);
      }
    }
  }

  public static StrategyProvider createStrategyProviderInstance(String provider) {
    return createProviderInstance(StrategyProvider.class, provider);
  }

  public static StatisticsProvider createStatisticsProviderInstance(String provider) {
    return createProviderInstance(StatisticsProvider.class, provider);
  }

  public static TableMetadataProvider createTableMetadataProviderInstance(String provider) {
    return createProviderInstance(TableMetadataProvider.class, provider);
  }

  public static JobSubmitter createJobSubmitterInstance(String provider) {
    return createProviderInstance(JobSubmitter.class, provider);
  }

  public static StatisticsUpdater createStatisticsUpdaterInstance(String provider) {
    return createProviderInstance(StatisticsUpdater.class, provider);
  }

  public static MetricsUpdater createMetricsUpdaterInstance(String provider) {
    return createProviderInstance(MetricsUpdater.class, provider);
  }

  public static MetricsProvider createMetricsProviderInstance(String provider) {
    return createProviderInstance(MetricsProvider.class, provider);
  }

  public static TableJobRelationProvider createTableJobRelationProviderInstance(String provider) {
    return createProviderInstance(TableJobRelationProvider.class, provider);
  }

  public static MonitorCallback createMonitorCallbackInstance(String provider) {
    return createProviderInstance(MonitorCallback.class, provider);
  }
}
