/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.instrumentation.restlet.v2_0.internal;

import static java.lang.invoke.MethodType.methodType;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nullable;
import org.restlet.Message;
import org.restlet.data.Form;
import org.restlet.util.Series;

/**
 * This class is internal and is hence not for public use. Its APIs are unstable and can change at
 * any time.
 */
public final class MessageAttributesAccessor {

  private static final MethodHandle GET_ATTRIBUTES;
  private static final MethodHandle SET_VALUE;
  private static final Class<?> HEADER_CLASS;
  private static final MethodHandle NEW_SERIES;

  static {
    MethodHandle getAttributes = null;
    MethodHandle setValue = null;
    Class<?> headerClass = null;
    MethodHandle newSeries = null;

    MethodHandles.Lookup lookup = MethodHandles.lookup();
    try {
      getAttributes = lookup.findVirtual(Message.class, "getAttributes", methodType(Map.class));
    } catch (NoSuchMethodException | IllegalAccessException e) {
      // changed the return type to ConcurrentMap in version 2.1
      try {
        getAttributes =
            lookup.findVirtual(Message.class, "getAttributes", methodType(ConcurrentMap.class));
      } catch (NoSuchMethodException | IllegalAccessException ex) {
        // ignored
      }
    }

    Class<?> setValueReturnType = null;
    try {
      // changed the generic bound to NamedValue in version 2.1; earlier than that it's Parameter
      setValueReturnType = Class.forName("org.restlet.util.NamedValue");
    } catch (ClassNotFoundException e) {
      try {
        setValueReturnType = Class.forName("org.restlet.data.Parameter");
      } catch (ClassNotFoundException ex) {
        // ignored
      }
    }
    if (setValueReturnType != null) {
      try {
        setValue =
            lookup.findVirtual(
                Series.class, "set", methodType(setValueReturnType, String.class, String.class));
      } catch (NoSuchMethodException | IllegalAccessException e) {
        // ignored
      }
    }

    try {
      // restlet 2.3+
      headerClass = Class.forName("org.restlet.data.Header");
    } catch (ClassNotFoundException e) {
      try {
        // restlet 2.1-2.2
        headerClass = Class.forName("org.restlet.engine.header.Header");
      } catch (ClassNotFoundException ex) {
        // restlet 2.0 does not have Header
      }
    }
    if (headerClass != null) {
      // restlet 2.1+ Series has different constructor
      try {
        newSeries = lookup.findConstructor(Series.class, methodType(void.class, Class.class));
      } catch (NoSuchMethodException | IllegalAccessException e) {
        // ignored
      }
    }

    GET_ATTRIBUTES = getAttributes;
    SET_VALUE = setValue;
    HEADER_CLASS = headerClass;
    NEW_SERIES = newSeries;
  }

  @SuppressWarnings("unchecked")
  @Nullable
  public static Map<String, Object> getAttributes(Message message) {
    if (GET_ATTRIBUTES == null) {
      return null;
    }
    try {
      return (Map<String, Object>) GET_ATTRIBUTES.invoke(message);
    } catch (Throwable e) {
      return null;
    }
  }

  @Nullable
  public static Series<?> createHeaderSeries() {
    if (HEADER_CLASS == null) {
      return new Form();
    }
    if (NEW_SERIES == null) {
      // should never really happen
      return null;
    }
    try {
      return (Series<?>) NEW_SERIES.invoke(HEADER_CLASS);
    } catch (Throwable e) {
      return null;
    }
  }

  public static void setSeriesValue(Series<?> series, String name, String value) {
    if (SET_VALUE == null) {
      return;
    }
    try {
      SET_VALUE.invoke(series, name, value);
    } catch (Throwable ignored) {
      // just do nothing
    }
  }

  private MessageAttributesAccessor() {}
}
