package jetbrains.mps.smodel.search;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import org.jetbrains.mps.openapi.language.SAbstractLink;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import java.util.List;
import jetbrains.mps.smodel.behaviour.BHReflection;
import jetbrains.mps.core.aspects.behaviour.SMethodIdV2;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import java.util.Objects;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SPropertyOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import org.jetbrains.mps.openapi.language.SConcept;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import org.jetbrains.mps.openapi.language.SProperty;

/**
 * Unless we decide what's the story around specialized links, we need to keep code that tells most specific link declaration for a given concept and base link
 * This code used to be part of ConceptAndSuperConceptsCache, which is quite outdated to keep going.
 * Note, this code doesn't check if supplied link in fact belongs to the hierarchy of the concept
 */
@GeneratedClass(node = "r:22db907b-8239-4180-8797-e91cea0b9573(jetbrains.mps.smodel.search)/7813081600365639852", model = "r:22db907b-8239-4180-8797-e91cea0b9573(jetbrains.mps.smodel.search)")
public final class LinkDeclarationLookup {
  private final SNode myConcept;

  public LinkDeclarationLookup(@NotNull SNode concept) {
    myConcept = concept;
  }

  public LinkDeclarationLookup(@NotNull SAbstractConcept concept) {
    myConcept = (SNode) concept.getDeclarationNode();
    // XXX what if it's not our ACD that served as concept declaration node, shall I respect this scenario here?
    //    I do not respect it right away as I merely refactoring existing code that did exactly that, cast getDeclarationNode().
  }

  /**
   * Technically, NotNull provided the link is from the concept AND we can access concept's declaration node, node<ACD>
   */
  public SNode getMostSpecificLinkDeclarationFor(SAbstractLink link) {
    SNode override = findOverride(link);
    if (override != null) {
      return override;
    }
    final String linkName = link.getName();
    // with findOverride() we have checked links with specializedLink!=null, only plain LD left to check
    return ListSequence.fromList(((List<SNode>) BHReflection.invoke0(myConcept, CONCEPTS.AbstractConceptDeclaration$KA, SMethodIdV2.create("getLinkDeclarations", 1213877394480L, 0x44a456bea0df1cf0L)))).findFirst((it) -> (SLinkOperations.getTarget(it, LINKS.specializedLink$7ZCN) == null) && Objects.equals(SPropertyOperations.getString(it, PROPS.name$MnvL), linkName));
  }

  /**
   * 
   * @return concept of a link target, or its most specific override within the hierarchy of initial concept
   */
  @NotNull
  public SAbstractConcept getMostSpecificLinkTarget(SAbstractLink link) {
    SNode override = findOverride(link);
    if (override != null) {
      return SNodeOperations.asSConcept(SLinkOperations.getTarget(override, LINKS.target$m40F));
    }
    return link.getTargetConcept();
  }


  /**
   * 
   * Three concepts, each with a link declaration. C3 extends C2, C2 extends C1. (C1.ld1), (C2.ld2) and (C3.ld3)
   * Case 1, transitive: 
   *   ld3 specializes ld2, ld2 specialize ld1. 
   *   C3.getLinkDeclarations() = { ld3 }, LinkDeclarationLookup(C3).getMostSpecificLinkDeclarationFor(ld1) == ld3
   * Case 2, two overrides for same base link: 
   *   ld3 specializes ld1, ld2 specializes ld1.
   *   C3.getLinkDeclarations() = { ld3, ld2 } (order!). LinkDeclarationLookup(C3).getMostSpecificLinkDeclarationFor(ld1) == ld3
   */
  private SNode findOverride(SAbstractLink link) {
    // Two scenarios in mind: given (C1.r1), (C2.r2) and (C3.r3), C3 extends C2 extends C1; first scenario is transitive, r2 specializes r1, r3 specializes r2; 
    // second when both r2 and r3 specialize r1. For C3, there'd be 1 link declaration in the first scenario, namely {r3}, while for the second case it would be {r3,r2}

    // SAbstractLink is always the "base"/persistence one; the one that could be specialized in a subconcept
    // getLinkDeclarations gives most specific LD instance if there are few specialization, that's why the first one to match is fine (assuming no unrelated links with the same name)
    final String linkName = link.getName();
    for (SNode ld : ListSequence.fromList(((List<SNode>) BHReflection.invoke0(myConcept, CONCEPTS.AbstractConceptDeclaration$KA, SMethodIdV2.create("getLinkDeclarations", 1213877394480L, 0x44a456bea0df1cf0L)))).where((it) -> SLinkOperations.getTarget(it, LINKS.specializedLink$7ZCN) != null)) {
      // specializedLink may point to another LD with specializedLink
      SNode sl = SLinkOperations.getTarget(ld, LINKS.specializedLink$7ZCN);
      while (SLinkOperations.getTarget(sl, LINKS.specializedLink$7ZCN) != null) {
        sl = SLinkOperations.getTarget(sl, LINKS.specializedLink$7ZCN);
      }
      if (Objects.equals(SPropertyOperations.getString(sl, PROPS.name$MnvL), linkName)) {
        return ld;
      }
    }
    return null;
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept AbstractConceptDeclaration$KA = MetaAdapterFactory.getConcept(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0x1103553c5ffL, "jetbrains.mps.lang.structure.structure.AbstractConceptDeclaration");
  }

  private static final class LINKS {
    /*package*/ static final SReferenceLink specializedLink$7ZCN = MetaAdapterFactory.getReferenceLink(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0xf979bd086aL, 0xf98051c244L, "specializedLink");
    /*package*/ static final SReferenceLink target$m40F = MetaAdapterFactory.getReferenceLink(0xc72da2b97cce4447L, 0x8389f407dc1158b7L, 0xf979bd086aL, 0xf98055fef0L, "target");
  }

  private static final class PROPS {
    /*package*/ static final SProperty name$MnvL = MetaAdapterFactory.getProperty(0xceab519525ea4f22L, 0x9b92103b95ca8c0cL, 0x110396eaaa4L, 0x110396ec041L, "name");
  }
}
