package jetbrains.mps.idea.java.psiStubs;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import jetbrains.mps.extapi.persistence.ModelRootBase;
import jetbrains.mps.idea.java.psi.JavaPsiListener;
import org.jetbrains.annotations.NotNull;
import com.intellij.openapi.module.Module;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelId;
import jetbrains.mps.idea.java.psi.PsiChangesWatcher;
import java.util.Map;
import org.jetbrains.mps.openapi.model.SModelReference;
import java.util.List;
import com.intellij.psi.PsiDirectory;
import jetbrains.mps.internal.collections.runtime.MapSequence;
import java.util.HashMap;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.psi.PsiManager;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import java.util.ArrayList;
import jetbrains.mps.internal.collections.runtime.IMapping;
import jetbrains.mps.internal.collections.runtime.Sequence;
import com.intellij.psi.PsiJavaFile;
import jetbrains.mps.java.stub.JavaPackageNameStub;
import com.intellij.psi.PsiFileSystemItem;

@GeneratedClass(node = "r:e32ada56-1876-4524-85c0-29075cbd8d32(jetbrains.mps.idea.java.psiStubs)/3118313698926457061", model = "r:e32ada56-1876-4524-85c0-29075cbd8d32(jetbrains.mps.idea.java.psiStubs)")
public class PsiJavaStubModelRoot extends ModelRootBase implements JavaPsiListener {
  private static final String TYPE = "JavaPsiStubs";

  @NotNull
  private Module myIdeaModule;

  public PsiJavaStubModelRoot(Module module) {
    myIdeaModule = module;
  }

  /**
   * Equals is defined only by our ideaModule, all the state is not taken into acount
   * We should be careful when working with ModelRoots in collections (see AbstractModule.doUpdateModelSet)
   */
  @Override
  public boolean equals(Object root) {
    if (!(root instanceof PsiJavaStubModelRoot)) {
      return false;
    }
    return myIdeaModule.equals(((PsiJavaStubModelRoot) root).myIdeaModule);
  }
  @Override
  public int hashCode() {
    return myIdeaModule.hashCode();
  }
  @Override
  public String getType() {
    return TYPE;
  }
  @Override
  public String getPresentation() {
    return "Java PSI stubs";
  }
  @Override
  public SModel getModel(SModelId id) {
    // TODO
    return null;
  }
  @Override
  public void attach() {
    super.attach();

    //  start to listen
    PsiChangesWatcher w = myIdeaModule.getProject().getComponent(PsiChangesWatcher.class);
    w.addListener(this);
  }
  @Override
  public void dispose() {
    super.dispose();
    PsiChangesWatcher w = myIdeaModule.getProject().getComponent(PsiChangesWatcher.class);
    w.removeListener(this);
  }
  @Override
  public Iterable<SModel> loadModels() {
    Map<SModelReference, List<PsiDirectory>> packageToDirs = MapSequence.fromMap(new HashMap<SModelReference, List<PsiDirectory>>());

    final VirtualFile[] sourceRoots = ModuleRootManager.getInstance(myIdeaModule).getSourceRoots(false);
    PsiManager psiMgr = PsiManager.getInstance(myIdeaModule.getProject());

    for (VirtualFile root : sourceRoots) {
      PsiDirectory dir = psiMgr.findDirectory(root);
      // judging by RootModelBase.getSourceRoots() only valid source roots will be returned, but we'll be paranoid
      if (dir == null) {
        continue;
      }
      collectPackagesInDir(dir, dir, packageToDirs);
    }

    List<SModel> models = ListSequence.fromList(new ArrayList<SModel>());

    for (IMapping<SModelReference, List<PsiDirectory>> pair : MapSequence.fromMap(packageToDirs)) {
      SModelReference modelRef = pair.key();
      List<PsiDirectory> dirs = pair.value();

      ListSequence.fromList(models).addElement(makeModelDescriptor(modelRef, dirs));
    }

    return models;
  }
  private void collectPackagesInDir(PsiDirectory sourceRoot, PsiDirectory dir, Map<SModelReference, List<PsiDirectory>> result) {
    if (Sequence.fromIterable(Sequence.fromArray(dir.getFiles())).ofType(PsiJavaFile.class).isNotEmpty()) {

      SModelReference modelRef = makeModelReference(sourceRoot, dir);
      if (modelRef != null) {
        List<PsiDirectory> dirs = MapSequence.fromMap(result).get(modelRef);
        if (dirs == null) {
          dirs = ListSequence.fromList(new ArrayList<PsiDirectory>());
          MapSequence.fromMap(result).put(modelRef, dirs);
        }
        ListSequence.fromList(dirs).addElement(dir);
      }
    }

    for (PsiDirectory subDir : dir.getSubdirectories()) {
      collectPackagesInDir(sourceRoot, subDir, result);
    }
  }
  private PsiJavaStubModelDescriptor makeModelDescriptor(SModelReference modelRef, Iterable<PsiDirectory> dirs) {
    MultiplePsiJavaStubDataSource ds = new MultiplePsiJavaStubDataSource(myIdeaModule, dirs);
    PsiJavaStubModelDescriptor md = new PsiJavaStubModelDescriptor(modelRef, ds);
    md.setModelRoot(this);
    return md;
  }
  private SModelReference makeModelReference(PsiDirectory sourceRoot, PsiDirectory dir) {
    int skipPrefix = sourceRoot.toString().length();
    String relativeDirName = dir.toString().substring(skipPrefix);
    String packageName = relativeDirName.replace('/', '.').replace('\\', '.');

    if ((packageName == null || packageName.length() == 0)) {
      return null;
    }

    if (packageName.length() > 0 && packageName.charAt(0) == '.') {
      packageName = packageName.substring(1);
    }

    return new JavaPackageNameStub(packageName).asModelReference(getModule().getModuleReference());
  }

  @Override
  public void psiChanged(JavaPsiListener.PsiEvent event) {

    // TODO re-write to sequences and any

    // here we simply decide if we have to update
    for (PsiFileSystemItem fsItem : event.getCreated()) {
      if (importantFsItem(fsItem)) {
        update();
        return;
      }
    }

    for (PsiFileSystemItem fsItem : event.getRemoved()) {
      if (importantFsItem(fsItem)) {
        update();
        return;
      }
    }

    for (JavaPsiListener.FSRename rename : event.getRenamed()) {
      if (importantFsItem(rename.item)) {
        update();
        return;
      }
    }

    for (JavaPsiListener.FSMove move : event.getMoved()) {
      if (importantFsItem(move.item)) {
        update();
        return;
      }
    }
  }


  private boolean importantFsItem(PsiFileSystemItem fsItem) {
    return (fsItem instanceof PsiDirectory || fsItem instanceof PsiJavaFile) && findOurSourceRoot(fsItem) != null;
  }

  private PsiDirectory findOurSourceRoot(PsiFileSystemItem item) {
    for (VirtualFile sourceRoot : ModuleRootManager.getInstance(myIdeaModule).getSourceRoots()) {
      String rootPath = sourceRoot.toString();
      String itemPath = item.getVirtualFile().toString();
      if (itemPath.startsWith(rootPath)) {
        return PsiManager.getInstance(myIdeaModule.getProject()).findDirectory(sourceRoot);
      }
    }
    return null;
  }
}
