package jetbrains.mps.idea.java.psiStubs;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import jetbrains.mps.smodel.RegularModelDescriptor;
import jetbrains.mps.logging.Logger;
import java.util.Map;
import java.util.Set;
import org.jetbrains.mps.openapi.model.SNodeId;
import jetbrains.mps.internal.collections.runtime.MapSequence;
import java.util.HashMap;
import com.intellij.psi.PsiElement;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.module.SRepository;
import java.util.Collection;
import org.jetbrains.mps.openapi.language.SLanguage;
import java.util.Arrays;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import java.util.Collections;
import java.util.List;
import org.jetbrains.mps.openapi.module.SModuleReference;
import java.util.Collections;
import jetbrains.mps.smodel.ModelLoadResult;
import jetbrains.mps.smodel.SModel;
import jetbrains.mps.smodel.loading.ModelLoadingState;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.psi.PsiJavaFile;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.internal.collections.runtime.SetSequence;
import java.util.HashSet;
import com.intellij.psi.PsiClass;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.IAttributeDescriptor;
import org.jetbrains.mps.openapi.persistence.DataSource;
import jetbrains.mps.smodel.CopyUtil;
import jetbrains.mps.idea.java.psi.JavaPsiListener;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiImportList;
import com.intellij.psi.PsiImportStatementBase;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SConceptOperations;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SPropertyOperations;
import com.intellij.psi.PsiImportStaticStatement;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import org.jetbrains.mps.openapi.util.Consumer;
import jetbrains.mps.util.Pair;
import jetbrains.mps.internal.collections.runtime.IVisitor;
import org.jetbrains.mps.openapi.language.SConcept;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.language.SContainmentLink;

@GeneratedClass(node = "r:e32ada56-1876-4524-85c0-29075cbd8d32(jetbrains.mps.idea.java.psiStubs)/3118313698925873538", model = "r:e32ada56-1876-4524-85c0-29075cbd8d32(jetbrains.mps.idea.java.psiStubs)")
public class PsiJavaStubModelDescriptor extends RegularModelDescriptor implements PsiJavaStubListener {
  /*package*/ Logger LOG = Logger.getLogger(PsiJavaStubModelDescriptor.class);

  private Map<String, Set<SNodeId>> myRootsPerFile = MapSequence.fromMap(new HashMap<String, Set<SNodeId>>());
  private Map<String, Set<SNodeId>> myAllNodesPerFile = MapSequence.fromMap(new HashMap<String, Set<SNodeId>>());
  private Map<SNodeId, PsiElement> myGlobalMps2PsiMapping = MapSequence.fromMap(new HashMap<SNodeId, PsiElement>());
  private MyMps2PsiMapper myMps2PsiMapper = new MyMps2PsiMapper();

  public PsiJavaStubModelDescriptor(SModelReference modelRef, JavaFilesHolder dataSource) {
    super(modelRef, dataSource);
  }
  @Override
  @NotNull
  public JavaFilesHolder getSource() {
    return (JavaFilesHolder) super.getSource();
  }
  @Override
  public void attach(SRepository repository) {
    super.attach(repository);
    getSource().addListener(this);
  }
  @Override
  public void detach() {
    super.detach();
    getSource().removeListener(this);
  }

  @Override
  public Collection<SLanguage> importedLanguageIds() {
    return Arrays.asList(new SLanguage[]{MetaAdapterFactory.getLanguage(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, "jetbrains.mps.baseLanguage"), MetaAdapterFactory.getLanguage(0xf280165065d5424eL, 0xbb1b463a8781b786L, "jetbrains.mps.baseLanguage.javadoc")});
  }

  @NotNull
  @Override
  public Collection<SLanguage> getLanguagesEngagedOnGeneration() {
    return Collections.emptyList();
  }

  @Override
  public List<SModuleReference> importedDevkits() {
    return Collections.emptyList();
  }

  @NotNull
  @Override
  protected ModelLoadResult<SModel> createModel() {
    SModel m = new SModel(getReference());
    loadContents(m);
    return new ModelLoadResult<SModel>(m, ModelLoadingState.FULLY_LOADED);
  }

  private void loadContents(SModel into) {
    ProgressManager.getInstance().executeNonCancelableSection(new Runnable() {
      public void run() {
        try {
          for (PsiJavaFile jf : getSource().getJavaFiles()) {
            SNode javaImports = getImports(jf.getImportList());

            ASTConverter converter = new ASTConverter(myMps2PsiMapper);
            Set<SNodeId> roots = SetSequence.fromSet(new HashSet<SNodeId>());

            for (PsiClass cls : jf.getClasses()) {
              SNode node = converter.convertClass(cls);
              if (SNodeOperations.isInstanceOf(node, CONCEPTS.Classifier$Ix) && (javaImports != null)) {
                new IAttributeDescriptor.NodeAttribute(CONCEPTS.JavaImports$b_).set(SNodeOperations.cast(node, CONCEPTS.Classifier$Ix), javaImports);
              }
              // TODO check for duplicate ids (in java sources there may be 2 classes with the same name
              //  which is an error but none the less)
              into.addRootNode(node);
              SetSequence.fromSet(roots).addElement(node.getNodeId());
            }

            if (SetSequence.fromSet(roots).isNotEmpty()) {
              MapSequence.fromMap(myRootsPerFile).put(jf.getName(), roots);
            }
          }

        } catch (Exception e) {
          LOG.error("could not build stub model for code PSI: you may not be able to reference java code from MPS", e);

        }
      }
    });
  }
  @Override
  public void changed(DataSource source) {
    // ignore, we should never receive this one
    // always the more detailed changed(source, psiEvent)
  }
  @Override
  public synchronized void changed(DataSource source, final PsiJavaStubEvent event) {

    // locking could possibly be made more fine-grained

    // already attached, but not createModel'd yet?
    final SModel actualModel = getCurrentModelInternal();
    if (actualModel == null) {
      return;
    }

    final SModel modelCopy = CopyUtil.copyModel(actualModel);

    for (PsiJavaFile file : event.removed()) {
      myMps2PsiMapper.clearFile(modelCopy, file.getName());
    }

    for (JavaPsiListener.FSRename rename : event.renamed()) {
      String oldName = rename.oldName;
      myMps2PsiMapper.clearFile(modelCopy, oldName);
    }

    for (PsiJavaFile file : event.needReparse()) {
      if (!(file.isValid())) {
        // going upwards and trying to find the valid file with this filename...
        // it should probably be removed, looks like a hack
        String name = file.getName();
        for (PsiFile f : file.getParent().getFiles()) {
          if (name.equals(f.getName()) && f instanceof PsiJavaFile) {
            file = (PsiJavaFile) f;
            break;
          }
        }
      }
      // it's still not valid
      if (!(file.isValid())) {
        continue;
      }

      myMps2PsiMapper.clearFile(modelCopy, file.getName());

      SNode javaImports = getImports(file.getImportList());
      ASTConverter converter = new ASTConverter(myMps2PsiMapper);

      Set<SNodeId> roots = SetSequence.fromSet(new HashSet<SNodeId>());

      for (PsiClass cls : file.getClasses()) {
        SNode node = converter.convertClass(cls);
        if (SNodeOperations.isInstanceOf(node, CONCEPTS.Classifier$Ix) && (javaImports != null)) {
          new IAttributeDescriptor.NodeAttribute(CONCEPTS.JavaImports$b_).set(SNodeOperations.cast(node, CONCEPTS.Classifier$Ix), javaImports);
        }

        modelCopy.addRootNode(node);
        SetSequence.fromSet(roots).addElement(node.getNodeId());
      }
      if (SetSequence.fromSet(roots).isNotEmpty()) {
        MapSequence.fromMap(myRootsPerFile).put(file.getName(), roots);
      }
    }

    replace(new ModelLoadResult<SModel>(modelCopy, ModelLoadingState.FULLY_LOADED));
  }


  private SNode getImports(PsiImportList importList) {
    PsiImportStatementBase[] imports = importList.getAllImportStatements();
    if (imports == null) {
      return null;
    }
    SNode javaImports = SConceptOperations.createNewNode(MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x53f7c33f069862f2L, "jetbrains.mps.baseLanguage.structure.JavaImports"));

    for (PsiImportStatementBase imp : imports) {
      PsiJavaCodeReferenceElement ref = imp.getImportReference();
      if (ref == null) {
        continue;
      }

      SNode javaImport = SConceptOperations.createNewNode(MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x64c0181e603bcfL, "jetbrains.mps.baseLanguage.structure.JavaImport"));
      SPropertyOperations.assign(javaImport, PROPS.onDemand$Gmdi, imp.isOnDemand());
      SPropertyOperations.assign(javaImport, PROPS.static$JAuQ, imp instanceof PsiImportStaticStatement);
      String qName = ref.getQualifiedName();
      SPropertyOperations.assign(javaImport, PROPS.tokens$J1uk, qName);

      ListSequence.fromList(SLinkOperations.getChildren(javaImports, LINKS.entries$neZo)).addElement(javaImport);
    }
    return javaImports;
  }

  public PsiElement getPsiSource(SNode node) {
    return MapSequence.fromMap(myGlobalMps2PsiMapping).get(node.getNodeId());
  }

  private class MyMps2PsiMapper implements Consumer<Pair<SNode, PsiElement>> {
    @Override
    public void consume(Pair<SNode, PsiElement> pair) {
      SNode node = pair.o1;
      PsiElement element = pair.o2;
      MapSequence.fromMap(myGlobalMps2PsiMapping).put(node.getNodeId(), element);

      PsiFile file = element.getContainingFile();
      Set<SNodeId> mapForFile = MapSequence.fromMap(myAllNodesPerFile).get(file.getName());
      if (mapForFile == null) {
        mapForFile = SetSequence.fromSet(new HashSet<SNodeId>());
        MapSequence.fromMap(myAllNodesPerFile).put(file.getName(), mapForFile);
      }
      SetSequence.fromSet(mapForFile).addElement(node.getNodeId());
    }

    /*package*/ void clearFile(final SModel model, String fileName) {

      if (MapSequence.fromMap(myRootsPerFile).get(fileName) != null) {
        SetSequence.fromSet(MapSequence.fromMap(myRootsPerFile).get(fileName)).visitAll(new IVisitor<SNodeId>() {
          public void visit(SNodeId it) {
            SNode node = model.getNode(it);
            model.removeRootNode(node);
          }
        });
        MapSequence.fromMap(myRootsPerFile).removeKey(fileName);
      }

      Set<SNodeId> mapForFile = MapSequence.fromMap(myAllNodesPerFile).get(fileName);
      if (mapForFile == null) {
        return;
      }
      MapSequence.fromMap(myAllNodesPerFile).removeKey(fileName);
      for (SNodeId node : mapForFile) {
        MapSequence.fromMap(myGlobalMps2PsiMapping).removeKey(node);
      }
    }
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept Classifier$Ix = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101d9d3ca30L, "jetbrains.mps.baseLanguage.structure.Classifier");
    /*package*/ static final SConcept JavaImports$b_ = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x53f7c33f069862f2L, "jetbrains.mps.baseLanguage.structure.JavaImports");
  }

  private static final class PROPS {
    /*package*/ static final SProperty onDemand$Gmdi = MetaAdapterFactory.getProperty(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x64c0181e603bcfL, 0x64c0181e603bd0L, "onDemand");
    /*package*/ static final SProperty static$JAuQ = MetaAdapterFactory.getProperty(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x64c0181e603bcfL, 0x4d5c30eb30af1572L, "static");
    /*package*/ static final SProperty tokens$J1uk = MetaAdapterFactory.getProperty(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x5a98df4004080866L, 0x1996ec29712bdd92L, "tokens");
  }

  private static final class LINKS {
    /*package*/ static final SContainmentLink entries$neZo = MetaAdapterFactory.getContainmentLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x53f7c33f069862f2L, 0x64c0181e6020a7L, "entries");
  }
}
