package jetbrains.mps.internal.make.runtime.util;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import jetbrains.mps.vfs.IFile;
import java.util.Set;
import jetbrains.mps.internal.collections.runtime.SetSequence;
import java.util.HashSet;
import java.util.List;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import java.util.ArrayList;
import java.util.Queue;
import jetbrains.mps.internal.collections.runtime.QueueSequence;
import java.util.LinkedList;
import java.util.Arrays;
import jetbrains.mps.baseLanguage.tuples.runtime.Tuples;
import jetbrains.mps.internal.collections.runtime.Sequence;
import jetbrains.mps.baseLanguage.tuples.runtime.MultiTuple;

/**
 * IMPORTANT Use of this class is discouraged.
 * 
 * The class is informed about FS changes walks FS to find out existing files and report those unchanged as stale.
 * As a drawback, need an external mechanism to tell non-generated files under generated location.
 * MPS doesn't use it for TextGen as we record names of generated files and don't need to walk FS any more
 */
@GeneratedClass(node = "r:f8580193-afc4-4673-a635-d4757ca591cf(jetbrains.mps.internal.make.runtime.util)/3251655328352589723", model = "r:f8580193-afc4-4673-a635-d4757ca591cf(jetbrains.mps.internal.make.runtime.util)")
public class StaleFilesCollector {
  private final IFile rootDir;
  private final Set<IFile> filesToKeep;

  public StaleFilesCollector(IFile rootDir) {
    this.rootDir = rootDir;
    filesToKeep = SetSequence.fromSet(new HashSet<IFile>());
  }

  private List<IFile> collectFilesToDelete() {
    String[] pathsToKeep = ListSequence.fromList(SetSequence.fromSet(filesToKeep).select((f) -> (f.isDirectory() ? DirUtil.normalizeAsDir(f.getPath()) : DirUtil.normalize(f.getPath()))).sort((p) -> p, true).toList()).toGenericArray(String.class);

    List<IFile> filesToDelete = ListSequence.fromList(new ArrayList<IFile>());

    Queue<IFile> dirQueue = QueueSequence.fromQueueAndArray(new LinkedList<IFile>(), rootDir);
    while (QueueSequence.fromQueue(dirQueue).isNotEmpty()) {
      IFile dir = QueueSequence.fromQueue(dirQueue).removeFirstElement();
      String dirpath = DirUtil.normalizeAsDir(dir.getPath());
      int diridx = Arrays.binarySearch(pathsToKeep, dirpath);

      for (Tuples._2<IFile, String> fileAndPath : Sequence.fromIterable(getChildren(dir)).select((f) -> MultiTuple.<IFile,String>from(f, DirUtil.normalize(f.getPath()))).sort((t) -> t._1(), true)) {
        if (fileAndPath._0().isDirectory()) {
          int fidx = Arrays.binarySearch(pathsToKeep, DirUtil.normalizeAsDir(fileAndPath._1()));
          fidx = (fidx < 0 ? -1 - fidx : fidx);
          if (fidx >= pathsToKeep.length || !(DirUtil.startsWith(pathsToKeep[fidx], fileAndPath._1()))) {
            ListSequence.fromList(filesToDelete).addElement(fileAndPath._0());
            if (fidx >= pathsToKeep.length) {
              break;
            }
          } else if (fidx < pathsToKeep.length) {
            QueueSequence.fromQueue(dirQueue).addLastElement(fileAndPath._0());
          }
        } else {
          int fidx = Arrays.binarySearch(pathsToKeep, fileAndPath._1());
          if (fidx < 0 && diridx < 0) {
            ListSequence.fromList(filesToDelete).addElement(fileAndPath._0());
          }
        }
      }
    }

    return filesToDelete;
  }

  /**
   * May be invoked multiple times, updates internal state of what files are considered 'touched' according to delta supplied
   * These files are not reported as 'stale' by {@link jetbrains.mps.internal.make.runtime.util.StaleFilesCollector#reportStaleFilesInto(FilesDelta) }
   */
  public void recordFilesToKeep(FilesDelta delta) {
    delta.acceptVisitor(new FilesDelta.Visitor() {
      @Override
      public boolean acceptWritten(IFile file) {
        SetSequence.fromSet(filesToKeep).addElement(file);
        return true;
      }
      @Override
      public boolean acceptKept(IFile file) {
        SetSequence.fromSet(filesToKeep).addElement(file);
        return true;
      }
    });
  }

  public void reportStaleFilesInto(FilesDelta delta) {
    for (IFile f : collectFilesToDelete()) {
      delta.stale(f);
    }
  }

  /**
   * This method assumes there's no other delta for the root other than passed as an argument, which is wrong for forked generation plans with multiple models that save output into the same location. 
   * Then there are few deltas reported for the same location, some reporting files as stale while other report as added
   * 
   * @deprecated 
   */
  @Deprecated
  public void updateDelta(FilesDelta delta) {
    recordFilesToKeep(delta);
    reportStaleFilesInto(delta);
  }

  private Iterable<IFile> getChildren(IFile dir) {
    return dir.getChildren();
  }
}
