package com.codeborne.selenide.impl;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * We assume this API will change in next releases.
 * Be aware if you are going to use it.
 *
 * @since Selenide 5.15.0
 */
public class Plugins {
  private static final Logger logger = LoggerFactory.getLogger(Plugins.class);
  private static final Map<Class<?>, Object> cache = new ConcurrentHashMap<>();

  @SuppressWarnings("unchecked")
  public static <T> T inject(Class<T> klass) {
    return (T) cache.computeIfAbsent(klass, Plugins::loadPlugin);
  }

  private static <T> T loadPlugin(Class<T> klass) {
    Iterator<T> loader = ServiceLoader.load(klass).iterator();
    if (!loader.hasNext()) {
      T defaultPlugin = getDefaultPlugin(klass);
      logger.debug("Using default implementation of {}: {}", klass.getName(), defaultPlugin.getClass().getName());
      return defaultPlugin;
    }
    T implementation = loader.next();
    logger.info("Using implementation of {}: {}", klass.getName(), implementation.getClass().getName());
    return implementation;
  }

  private static <T> T getDefaultPlugin(Class<T> klass) {
    String resource = "/META-INF/defaultservices/" + klass.getName();
    URL file = Plugins.class.getResource(resource);
    if (file == null) {
      throw new IllegalStateException("Resource not found in classpath: " + resource);
    }

    String className = readFile(file).trim();
    try {
      return instantiate(className);
    }
    catch (Exception e) {
      throw new IllegalStateException("Failed to initialize default plugin " + className + " from " + file, e);
    }
  }

  @Nonnull
  private static <T> T instantiate(String className) throws Exception {
    @SuppressWarnings("unchecked")
    Constructor<T> constructor = (Constructor<T>) Class.forName(className).getDeclaredConstructor();
    constructor.setAccessible(true);
    return constructor.newInstance();
  }

  private static String readFile(URL file) {
    try {
      return IOUtils.toString(file, UTF_8);
    }
    catch (IOException e) {
      throw new IllegalStateException("Failed to read " + file, e);
    }
  }
}

@@ -785,32 +785,45 @@
   public static 
+synchronized 
 %3CT%3E T inject(Cla
@@ -841,22 +841,26 @@
 ) %7B%0A    
-return
+T plugin =
  (T) cac
@@ -866,40 +866,61 @@
 che.
-computeIfAbsent(klass, P
+get(klass);%0A    if (plugin == null) %7B%0A      p
 lugin
-s::
+ = 
 load
@@ -925,17 +925,80 @@
 adPlugin
-)
+(klass);%0A      cache.put(klass, plugin);%0A    %7D%0A    return plugin
 ;%0A  %7D%0A%0A 

fix concurrent update bug in Plugins