package jetbrains.mps.baseLanguage.util;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import java.util.Map;
import org.jetbrains.mps.openapi.model.SNode;
import java.util.Set;
import jetbrains.mps.internal.collections.runtime.MapSequence;
import java.util.HashMap;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import jetbrains.mps.internal.collections.runtime.Sequence;
import jetbrains.mps.baseLanguage.closures.runtime._FunctionTypes;
import jetbrains.mps.internal.collections.runtime.SetSequence;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SPropertyOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SConceptOperations;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import java.util.HashSet;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.smodel.behaviour.BHReflection;
import jetbrains.mps.core.aspects.behaviour.SMethodIdV2;
import java.util.List;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import jetbrains.mps.baseLanguage.search.VisibilityUtil;
import java.util.LinkedHashSet;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.language.SConcept;

/**
 * use the ClassifierMethodsHierarchyChecker idea instead of this class
 */
@GeneratedClass(node = "r:bf32fdbc-530f-4631-ba64-3e7b620ac47f(jetbrains.mps.baseLanguage.util)/8432634623182691821", model = "r:bf32fdbc-530f-4631-ba64-3e7b620ac47f(jetbrains.mps.baseLanguage.util)")
public final class OverridingMethodsCalculator {
  private final Map<SNode, Set<SNode>> myOverriding2BaseMethodsMap = MapSequence.fromMap(new HashMap<SNode, Set<SNode>>());

  public OverridingMethodsCalculator(SNode container) {
    this(container, getInstanceMethods(container));
  }

  public OverridingMethodsCalculator(SNode container, SNode enumConstant) {
    Iterable<SNode> result = SLinkOperations.getChildren(enumConstant, LINKS.method$pGvv);

    Map<String, Set<SNode>> nameToMethodsMap = MapSequence.fromMap(new HashMap<String, Set<SNode>>());
    for (SNode methodToCheck : Sequence.fromIterable(result).where(new _FunctionTypes._return_P1_E0<Boolean, SNode>() {
      public Boolean invoke(SNode it) {
        return canOverride(it);
      }
    })) {
      SetSequence.fromSet(safeGet(nameToMethodsMap, SPropertyOperations.getString(methodToCheck, PROPS.name$MnvL))).addElement(methodToCheck);
    }
    if (MapSequence.fromMap(nameToMethodsMap).isNotEmpty()) {
      SNode dummy = SConceptOperations.createNewNode(MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x1107e0cb103L, "jetbrains.mps.baseLanguage.structure.AnonymousClass"));
      SLinkOperations.setTarget(dummy, LINKS.classifier$q_Y$, container);
      collectOverridingMethodsInClassifierHierarchy(dummy, nameToMethodsMap, SetSequence.fromSet(new HashSet<SNode>()));
    }
  }

  public OverridingMethodsCalculator(SNode container, Iterable<SNode> methods) {
    Map<String, Set<SNode>> nameToMethodsMap = MapSequence.fromMap(new HashMap<String, Set<SNode>>());
    for (SNode methodToCheck : Sequence.fromIterable(methods).where(new _FunctionTypes._return_P1_E0<Boolean, SNode>() {
      public Boolean invoke(SNode it) {
        return canOverride(it);
      }
    })) {
      SetSequence.fromSet(safeGet(nameToMethodsMap, SPropertyOperations.getString(methodToCheck, PROPS.name$MnvL))).addElement(methodToCheck);
    }
    if (MapSequence.fromMap(nameToMethodsMap).isNotEmpty()) {
      collectOverridingMethodsInClassifierHierarchy(container, nameToMethodsMap, SetSequence.fromSet(new HashSet<SNode>()));
    }
  }

  public Set<SNode> getOverridingMethods() {
    return SetSequence.fromSet(MapSequence.fromMap(myOverriding2BaseMethodsMap).keySet()).asUnmodifiable();
  }

  public Set<SNode> getBaseMethods(SNode overridingMethod) {
    return SetSequence.fromSet(MapSequence.fromMap(myOverriding2BaseMethodsMap).get(overridingMethod)).asUnmodifiable();
  }

  private void collectOverridingMethodsInClassifierHierarchy(final SNode classifier, final Map<String, Set<SNode>> nameToMethodsMap, final Set<SNode> visitedClassifiers) {
    if (SNodeOperations.isInstanceOf(classifier, CONCEPTS.ClassConcept$bK)) {
      SNode clazz = SNodeOperations.cast(classifier, CONCEPTS.ClassConcept$bK);
      SNode superClass;
      if (SNodeOperations.isInstanceOf(clazz, CONCEPTS.AnonymousClass$Bt)) {
        superClass = SLinkOperations.getTarget(SNodeOperations.cast(clazz, CONCEPTS.AnonymousClass$Bt), LINKS.classifier$q_Y$);
      } else {
        superClass = ((SLinkOperations.getTarget(SLinkOperations.getTarget(clazz, LINKS.superclass$Mp9$), LINKS.classifier$cxMr) != null) ? SLinkOperations.getTarget(SLinkOperations.getTarget(clazz, LINKS.superclass$Mp9$), LINKS.classifier$cxMr) : SNodeOperations.getNode("6354ebe7-c22a-4a0f-ac54-50b52ab9b065/java:java.lang(JDK/)", "~Object"));
      }
      if (addIfNotContains(visitedClassifiers, superClass)) {
        collectOverridingMethods(classifier, superClass, nameToMethodsMap, visitedClassifiers);
      }
      Sequence.fromIterable(SLinkOperations.collect(SLinkOperations.getChildren(clazz, LINKS.implementedInterface$rujG), LINKS.classifier$cxMr)).where(new _FunctionTypes._return_P1_E0<Boolean, SNode>() {
        public Boolean invoke(SNode implementedInterface) {
          return implementedInterface != null && addIfNotContains(visitedClassifiers, implementedInterface);
        }
      }).visitAll(new _FunctionTypes._void_P1_E0<SNode>() {
        public void invoke(SNode intfc) {
          collectOverridingMethods(classifier, intfc, nameToMethodsMap, visitedClassifiers);
        }
      });
    } else if (SNodeOperations.isInstanceOf(classifier, CONCEPTS.Interface$db)) {
      Sequence.fromIterable(SLinkOperations.collect(SLinkOperations.getChildren(SNodeOperations.cast(classifier, CONCEPTS.Interface$db), LINKS.extendedInterface$PDVO), LINKS.classifier$cxMr)).where(new _FunctionTypes._return_P1_E0<Boolean, SNode>() {
        public Boolean invoke(SNode extendedInterface) {
          return extendedInterface != null && addIfNotContains(visitedClassifiers, extendedInterface);
        }
      }).visitAll(new _FunctionTypes._void_P1_E0<SNode>() {
        public void invoke(SNode intfc) {
          collectOverridingMethods(classifier, intfc, nameToMethodsMap, visitedClassifiers);
        }
      });
    }
  }

  private void collectOverridingMethods(final SNode classifier, final SNode superClassifier, Map<String, Set<SNode>> nameToMethodsMap, Set<SNode> visitedClassifiers) {
    Map<String, Set<SNode>> methodNameToMethodMapCopy = copyMap(nameToMethodsMap);
    for (final SNode superClassifierMethod : Sequence.fromIterable(((Iterable<SNode>) BHReflection.invoke0(superClassifier, CONCEPTS.Classifier$Ix, SMethodIdV2.create("methods", 5292274854859311639L, 0x5745e3015c8914d3L)))).where(new _FunctionTypes._return_P1_E0<Boolean, SNode>() {
      public Boolean invoke(SNode it) {
        // not very comprehensible condition (it is a dummy anonymous stub for enum constant) AP
        boolean enumConstant = SNodeOperations.isInstanceOf(classifier, CONCEPTS.AnonymousClass$Bt) && SNodeOperations.getModel(classifier) == null;
        return canBeOverridden(it) && ((SLinkOperations.getTarget(it, LINKS.visibility$Yyua) != null) || enumConstant || packagesAreTheSame(superClassifier, classifier));
      }
    })) {
      Set<SNode> methodsWithNameOfSuperMethod = MapSequence.fromMap(methodNameToMethodMapCopy).get(SPropertyOperations.getString(superClassifierMethod, PROPS.name$MnvL));
      if (methodsWithNameOfSuperMethod == null) {
        continue;
      }
      List<SNode> overridingMethods = SetSequence.fromSet(methodsWithNameOfSuperMethod).where(new _FunctionTypes._return_P1_E0<Boolean, SNode>() {
        public Boolean invoke(SNode it) {
          return ((boolean) (Boolean) BHReflection.invoke0(superClassifierMethod, CONCEPTS.BaseMethodDeclaration$kD, SMethodIdV2.create("hasSameSignature", 1213877350435L, 0x5745e3015c8914d3L), it));
        }
      }).toList();
      for (SNode overridingMethod : ListSequence.fromList(overridingMethods)) {
        SetSequence.fromSet(safeGet(myOverriding2BaseMethodsMap, overridingMethod)).addElement(superClassifierMethod);
      }
      SetSequence.fromSet(methodsWithNameOfSuperMethod).removeSequence(ListSequence.fromList(overridingMethods));
      if (SetSequence.fromSet(methodsWithNameOfSuperMethod).isEmpty()) {
        MapSequence.fromMap(methodNameToMethodMapCopy).removeKey(SPropertyOperations.getString(superClassifierMethod, PROPS.name$MnvL));
      }
    }
    if (!(MapSequence.fromMap(methodNameToMethodMapCopy).isEmpty())) {
      collectOverridingMethodsInClassifierHierarchy(superClassifier, methodNameToMethodMapCopy, visitedClassifiers);
    }
  }

  private static Map<String, Set<SNode>> copyMap(Map<String, Set<SNode>> nameToMethodsMap) {
    Map<String, Set<SNode>> methodNameToMethodMapCopy = MapSequence.fromMap(new HashMap<String, Set<SNode>>());
    for (String methodName : MapSequence.fromMap(nameToMethodsMap).keySet()) {
      SetSequence.fromSet(safeGet(methodNameToMethodMapCopy, methodName)).addSequence(SetSequence.fromSet(MapSequence.fromMap(nameToMethodsMap).get(methodName)));
    }
    return methodNameToMethodMapCopy;
  }

  private boolean packagesAreTheSame(SNode superClassifier, SNode classifier) {
    return VisibilityUtil.packageName(classifier).equals(VisibilityUtil.packageName(superClassifier));
  }

  public static Iterable<SNode> getInstanceMethods(SNode containingClassifier) {
    Iterable<SNode> result = ((Iterable<SNode>) BHReflection.invoke0(containingClassifier, CONCEPTS.Classifier$Ix, SMethodIdV2.create("methods", 5292274854859311639L, 0x5745e3015c8914d3L)));
    if (SNodeOperations.isInstanceOf(containingClassifier, CONCEPTS.EnumClass$Vk)) {
      for (SNode enumConstant : ListSequence.fromList(SLinkOperations.getChildren(SNodeOperations.cast(containingClassifier, CONCEPTS.EnumClass$Vk), LINKS.enumConstant$qtgW))) {
        result = Sequence.fromIterable(result).concat(ListSequence.fromList(SLinkOperations.getChildren(enumConstant, LINKS.method$pGvv)));
      }
    }
    return result;
  }

  public static boolean canOverride(SNode method) {
    return !(SNodeOperations.isInstanceOf(SLinkOperations.getTarget(method, LINKS.visibility$Yyua), CONCEPTS.PrivateVisibility$l0)) && isNotEmptyString(SPropertyOperations.getString(method, PROPS.name$MnvL));
  }

  public static boolean canBeOverridden(SNode method) {
    return !(SNodeOperations.isInstanceOf(SLinkOperations.getTarget(method, LINKS.visibility$Yyua), CONCEPTS.PrivateVisibility$l0)) && isNotEmptyString(SPropertyOperations.getString(method, PROPS.name$MnvL));
  }

  public static <K, V> Set<V> safeGet(Map<K, Set<V>> map, K key) {
    if (!(MapSequence.fromMap(map).containsKey(key))) {
      MapSequence.fromMap(map).put(key, SetSequence.fromSet(new LinkedHashSet<V>()));
    }
    return MapSequence.fromMap(map).get(key);
  }

  private static boolean addIfNotContains(Set<SNode> classifierSet, SNode classifier) {
    if (!(SetSequence.fromSet(classifierSet).contains(classifier))) {
      SetSequence.fromSet(classifierSet).addElement(classifier);
      return true;
    }
    return false;
  }
  private static boolean isNotEmptyString(String str) {
    return str != null && str.length() > 0;
  }

  private static final class LINKS {
    /*package*/ static final SContainmentLink method$pGvv = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xfc367388b3L, 0x6d60019ab157734L, "method");
    /*package*/ static final SReferenceLink classifier$q_Y$ = MetaAdapterFactory.getReferenceLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x1107e0cb103L, 0x1107e0fd2a0L, "classifier");
    /*package*/ static final SContainmentLink superclass$Mp9$ = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, 0x10f6353296dL, "superclass");
    /*package*/ static final SReferenceLink classifier$cxMr = MetaAdapterFactory.getReferenceLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101de48bf9eL, 0x101de490babL, "classifier");
    /*package*/ static final SContainmentLink implementedInterface$rujG = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, 0xff2ac0b419L, "implementedInterface");
    /*package*/ static final SContainmentLink extendedInterface$PDVO = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101edd46144L, 0x101eddadad7L, "extendedInterface");
    /*package*/ static final SContainmentLink visibility$Yyua = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x112670d273fL, 0x112670d886aL, "visibility");
    /*package*/ static final SContainmentLink enumConstant$qtgW = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xfc367070a5L, 0xfc367503acL, "enumConstant");
  }

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

  private static final class CONCEPTS {
    /*package*/ static final SConcept ClassConcept$bK = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, "jetbrains.mps.baseLanguage.structure.ClassConcept");
    /*package*/ static final SConcept AnonymousClass$Bt = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x1107e0cb103L, "jetbrains.mps.baseLanguage.structure.AnonymousClass");
    /*package*/ static final SConcept Interface$db = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101edd46144L, "jetbrains.mps.baseLanguage.structure.Interface");
    /*package*/ static final SConcept BaseMethodDeclaration$kD = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8cc56b1fcL, "jetbrains.mps.baseLanguage.structure.BaseMethodDeclaration");
    /*package*/ static final SConcept Classifier$Ix = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101d9d3ca30L, "jetbrains.mps.baseLanguage.structure.Classifier");
    /*package*/ static final SConcept EnumClass$Vk = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xfc367070a5L, "jetbrains.mps.baseLanguage.structure.EnumClass");
    /*package*/ static final SConcept PrivateVisibility$l0 = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x10af9586f0cL, "jetbrains.mps.baseLanguage.structure.PrivateVisibility");
  }
}
