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

package io.opentelemetry.javaagent.tooling.muzzle.references;

import static io.opentelemetry.javaagent.tooling.muzzle.references.ReferenceMergeUtil.mergeFields;
import static io.opentelemetry.javaagent.tooling.muzzle.references.ReferenceMergeUtil.mergeFlags;
import static io.opentelemetry.javaagent.tooling.muzzle.references.ReferenceMergeUtil.mergeMethods;
import static io.opentelemetry.javaagent.tooling.muzzle.references.ReferenceMergeUtil.mergeSet;

import java.util.Set;
import javax.annotation.Nullable;

/**
 * Represents a reference to a class used in the instrumentation advice or helper class code (or the
 * helper class itself).
 *
 * <p>This class is used in the auto-generated {@code InstrumentationModule#getMuzzleReferences()}
 * method, it is not meant to be used directly by agent extension developers.
 */
public final class ClassRef {

  private final Set<Source> sources;
  private final Set<Flag> flags;
  private final String className;
  private final String superClassName;
  private final Set<String> interfaceNames;
  private final Set<FieldRef> fields;
  private final Set<MethodRef> methods;

  ClassRef(
      Set<Source> sources,
      Set<Flag> flags,
      String className,
      String superClassName,
      Set<String> interfaceNames,
      Set<FieldRef> fields,
      Set<MethodRef> methods) {
    this.sources = sources;
    this.flags = flags;
    this.className = className;
    this.superClassName = superClassName;
    this.interfaceNames = interfaceNames;
    this.fields = fields;
    this.methods = methods;
  }

  /** Start building a new {@linkplain ClassRef reference}. */
  public static ClassRefBuilder builder(String className) {
    return new ClassRefBuilder(className);
  }

  /** Returns information about code locations where this class was referenced. */
  public Set<Source> getSources() {
    return sources;
  }

  /** Returns modifier flags of this class. */
  public Set<Flag> getFlags() {
    return flags;
  }

  /** Returns the name of this class. */
  public String getClassName() {
    return className;
  }

  /** Returns the name of the super class, if this class extends one; null otherwise. */
  @Nullable
  public String getSuperClassName() {
    return superClassName;
  }

  /** Returns the set of interfaces implemented by this class. */
  public Set<String> getInterfaceNames() {
    return interfaceNames;
  }

  /** Returns the set of references to fields of this class. */
  public Set<FieldRef> getFields() {
    return fields;
  }

  /** Returns the set of references to methods of this class. */
  public Set<MethodRef> getMethods() {
    return methods;
  }

  /**
   * Create a new reference which combines this reference with another reference of the same type.
   *
   * @param anotherReference A reference to the same class.
   * @return a new {@linkplain ClassRef reference} which merges the two references.
   */
  public ClassRef merge(ClassRef anotherReference) {
    if (!anotherReference.getClassName().equals(className)) {
      throw new IllegalStateException("illegal merge " + this + " != " + anotherReference);
    }
    String superName =
        null == this.superClassName ? anotherReference.superClassName : this.superClassName;

    return new ClassRef(
        mergeSet(sources, anotherReference.sources),
        mergeFlags(flags, anotherReference.flags),
        className,
        superName,
        mergeSet(interfaceNames, anotherReference.interfaceNames),
        mergeFields(fields, anotherReference.fields),
        mergeMethods(methods, anotherReference.methods));
  }

  @Override
  public String toString() {
    String extendsPart = superClassName == null ? "" : " extends " + superClassName;
    String implementsPart =
        interfaceNames.isEmpty() ? "" : " implements " + String.join(", ", interfaceNames);
    return getClass().getSimpleName() + ": " + className + extendsPart + implementsPart;
  }
}
