package jetbrains.mps.baseLanguage.textGen;

/*Generated by MPS */

import jetbrains.mps.logging.Logger;
import jetbrains.mps.baseLanguage.tuples.runtime.Tuples;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import java.util.Map;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import java.util.Collections;
import jetbrains.mps.baseLanguage.tuples.runtime.MultiTuple;
import java.util.HashMap;
import jetbrains.mps.baseLanguage.behavior.Classifier__BehaviorDescriptor;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SPropertyOperations;
import jetbrains.mps.lang.core.behavior.INamedConcept__BehaviorDescriptor;
import org.jetbrains.mps.openapi.language.SInterfaceConcept;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.language.SProperty;

public class ContextClassifiersInRoot {
  private static final Logger LOG = Logger.getLogger(ContextClassifiersInRoot.class);
  private SimpleCache<Tuples._2<SNode, SContainmentLink>, Map<String, String>> myContextClassifiersCache;
  private SimpleCache<SNode, Map<String, String>> myNestedClassifiersCache;
  public ContextClassifiersInRoot(SNode rootNode) {
    myContextClassifiersCache = new SimpleCache<Tuples._2<SNode, SContainmentLink>, Map<String, String>>() {
      @Override
      protected Map<String, String> innerGet(Tuples._2<SNode, SContainmentLink> key) {
        return getContextClassifiers(key._0(), key._1());
      }
    };
    myNestedClassifiersCache = new SimpleCache<SNode, Map<String, String>>() {
      @Override
      protected Map<String, String> innerGet(SNode key) {
        return getNestedClassifiers(key);
      }
    };
  }

  public Map<String, String> getContextClassifiers(SNode contextNode) {
    // only AnonymousClass has Classifier as reference
    // todo: make it clearer
    if (SNodeOperations.isInstanceOf(contextNode, CONCEPTS.IAnonymousClass$IC)) {
      contextNode = SNodeOperations.getParent(contextNode);
    }
    if (SNodeOperations.isInstanceOf(contextNode, CONCEPTS.Classifier$Ix)) {
      if (LOG.isWarningLevel()) {
        LOG.warning("contextNode is classifier in getContextClassifiers: " + SNodeOperations.present(contextNode));
      }
      return Collections.emptyMap();
    }

    // find first classifier in path
    SContainmentLink sourceChildRole = null;
    while ((contextNode != null) && !(SNodeOperations.isInstanceOf(contextNode, CONCEPTS.Classifier$Ix))) {
      sourceChildRole = contextNode.getContainmentLink();
      contextNode = SNodeOperations.getParent(contextNode);
    }

    if ((contextNode == null)) {
      // todo: impossible?
      return Collections.emptyMap();
    }

    return myContextClassifiersCache.get(MultiTuple.<SNode,SContainmentLink>from(SNodeOperations.cast(contextNode, CONCEPTS.Classifier$Ix), sourceChildRole));
  }

  private Map<String, String> getContextClassifiers(SNode contextNode, SContainmentLink sourceChildRole) {
    Map<String, String> bindings = new HashMap<String, String>();

    SNode current = contextNode;
    while ((current != null)) {
      if (SNodeOperations.isInstanceOf(current, CONCEPTS.Classifier$Ix)) {
        boolean processNestedClassifiers = false;
        if (SNodeOperations.isInstanceOf(current, CONCEPTS.AnonymousClass$Bt) || SNodeOperations.isInstanceOf(current, CONCEPTS.EnumClass$Vk)) {
          processNestedClassifiers = true;
        } else if (SNodeOperations.isInstanceOf(current, CONCEPTS.Interface$db)) {
          processNestedClassifiers = !(LINKS.extendedInterface$PDVO.equals(sourceChildRole));
        } else if (SNodeOperations.isInstanceOf(current, CONCEPTS.ClassConcept$bK)) {
          processNestedClassifiers = !(LINKS.superclass$Mp9$.equals(sourceChildRole) || LINKS.implementedInterface$rujG.equals(sourceChildRole));
        } else {
          if (LOG.isWarningLevel()) {
            LOG.warning("Illegal classifier node in bl textgen: " + SNodeOperations.present(current));
          }
        }

        // todo: is it true? had a bug with it. Look like nested classifier has more priority then class with same name
        addClassifierToBindingMap(bindings, SNodeOperations.cast(current, CONCEPTS.Classifier$Ix));
        if (processNestedClassifiers) {
          for (Map.Entry<String, String> simpleToFqName : myNestedClassifiersCache.get(SNodeOperations.cast(current, CONCEPTS.Classifier$Ix)).entrySet()) {
            if (!(bindings.containsKey(simpleToFqName.getKey()))) {
              bindings.put(simpleToFqName.getKey(), simpleToFqName.getValue());
            }
          }
        }
      }

      // todo: specialized links?
      // should not be a problem: superclass/extendedInterface/implementedInterface not specialized
      sourceChildRole = current.getContainmentLink();
      current = SNodeOperations.getParent(current);
    }

    return bindings;
  }

  private static Map<String, String> getNestedClassifiers(SNode classifier) {
    // returns map from simpleName to fqName
    Map<String, String> nestedClassifiers = new HashMap<String, String>();

    // todo: classifiers with same names in different supertypes?
    for (SNode superClassifier : Classifier__BehaviorDescriptor.getAllExtendedClassifiers_id2xreLMO8jma.invoke(SNodeOperations.cast(classifier, CONCEPTS.Classifier$Ix))) {
      for (SNode nestedClassifier : Classifier__BehaviorDescriptor.nestedClassifiers_id4_LVZ3pBjGQ.invoke(superClassifier)) {
        addClassifierToBindingMap(nestedClassifiers, nestedClassifier);
      }
    }

    return nestedClassifiers;
  }

  private static void addClassifierToBindingMap(Map<String, String> bindings, SNode classifier) {
    String simpleName = SPropertyOperations.getString(classifier, PROPS.name$MnvL);
    String fqName = INamedConcept__BehaviorDescriptor.getFqName_idhEwIO9y.invoke(classifier);

    if (!(bindings.containsKey(simpleName))) {
      bindings.put(simpleName, fqName);
    }
  }

  private static final class CONCEPTS {
    /*package*/ static final SInterfaceConcept IAnonymousClass$IC = MetaAdapterFactory.getInterfaceConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x2f89e470eed6258eL, "jetbrains.mps.baseLanguage.structure.IAnonymousClass");
    /*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 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 ClassConcept$bK = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, "jetbrains.mps.baseLanguage.structure.ClassConcept");
  }

  private static final class LINKS {
    /*package*/ static final SContainmentLink extendedInterface$PDVO = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101edd46144L, 0x101eddadad7L, "extendedInterface");
    /*package*/ static final SContainmentLink implementedInterface$rujG = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, 0xff2ac0b419L, "implementedInterface");
    /*package*/ static final SContainmentLink superclass$Mp9$ = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, 0x10f6353296dL, "superclass");
  }

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