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

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import jetbrains.mps.make.delta.IDelta;
import jetbrains.mps.logging.Logger;
import java.util.Map;
import jetbrains.mps.vfs.IFile;
import jetbrains.mps.internal.collections.runtime.MapSequence;
import java.util.HashMap;
import jetbrains.mps.make.delta.IDeltaVisitor;
import java.util.Set;
import jetbrains.mps.internal.collections.runtime.SetSequence;
import java.util.HashSet;

@GeneratedClass(node = "r:f8580193-afc4-4673-a635-d4757ca591cf(jetbrains.mps.internal.make.runtime.util)/505174985642693148", model = "r:f8580193-afc4-4673-a635-d4757ca591cf(jetbrains.mps.internal.make.runtime.util)")
public class FilesDelta implements IDelta {
  private static Logger LOG = Logger.getLogger(FilesDelta.class);
  private Map<IFile, Status> files = MapSequence.fromMap(new HashMap<IFile, Status>());
  private final DeltaKey key;

  public FilesDelta(DeltaKey dk) {
    this.key = dk;
  }
  private FilesDelta(FilesDelta copyFrom) {
    this.key = copyFrom.key;
    MapSequence.fromMap(this.files).putAll(copyFrom.files);
  }

  public boolean isEmpty() {
    // tell if there's any information has been recorded with this delta object
    return MapSequence.fromMap(files).isEmpty();
  }

  public void written(IFile file) {
    MapSequence.fromMap(files).put(file, Status.WRITTEN);
  }
  public void kept(IFile file) {
    MapSequence.fromMap(files).put(file, Status.KEPT);
  }
  public void deleted(IFile file) {
    MapSequence.fromMap(files).put(file, Status.DELETED);
  }
  public void stale(IFile file) {
    if (!(MapSequence.fromMap(files).containsKey(file))) {
      MapSequence.fromMap(files).put(file, Status.STALE);
    }
  }

  @Override
  public boolean reconcile() {
    return acceptVisitor(new Visitor() {
      @Override
      public boolean acceptDeleted(IFile file) {
        FilesDelta.LOG.debug("Reconciled: deleting " + file);
        try {
          file.deleteIfExists();
        } catch (RuntimeException ignored) {
          FilesDelta.LOG.error("Exception deleting file " + file, ignored);
        }
        return true;
      }
    });
  }

  @Override
  public boolean acceptVisitor(IDeltaVisitor visitor) {
    if (!(visitor instanceof Visitor)) {
      return true;
    }
    return acceptFilesVisitor(((Visitor) visitor));
  }

  @Override
  public IDelta merge(IDelta toMerge) {
    if (!(toMerge instanceof FilesDelta)) {
      throw new IllegalArgumentException();
    }
    FilesDelta that = (FilesDelta) toMerge;
    if (this.contains(that)) {
      return new FilesDelta(this).copy(that);
    } else if (toMerge.contains(this)) {
      return new FilesDelta(that).copy(this);
    }

    DeltaKey commonPrefix = key.commonPrefix(that.key);
    if (!(commonPrefix.isEmpty())) {
      return new FilesDelta(commonPrefix).copy(this).copy(that);
    }
    throw new IllegalArgumentException();
  }

  private boolean acceptFilesVisitor(final Visitor visitor) {
    MapSequence.fromMap(files).visitAll((m) -> {
      if (m.value() == Status.KEPT && !(m.key().isDirectory())) {
        visitor.acceptKept(m.key());
      } else if (m.value() == Status.WRITTEN) {
        visitor.acceptWritten(m.key());
      } else if (m.value() == Status.DELETED || m.value() == Status.STALE) {
        visitor.acceptDeleted(m.key());
      }
    });
    return true;
  }

  private FilesDelta copy(FilesDelta that) {
    // provided there's this.contains(that) call before copy()
    // DirUtil.startsWith(that, this) == true
    if (!(key.contains(that.key))) {
      throw new IllegalArgumentException();
    }

    Set<IFile> newlyTouchedDirs = SetSequence.fromSet(new HashSet<IFile>());
    // copy all but stale values, stale entries shall not override explicitly set
    for (IFile file : MapSequence.fromMap(that.files).keySet()) {
      Status newStatus = MapSequence.fromMap(that.files).get(file);
      if (newStatus == Status.STALE && MapSequence.fromMap(files).containsKey(file)) {
        continue;
      } else {
        MapSequence.fromMap(files).put(file, newStatus);
      }
      SetSequence.fromSet(newlyTouchedDirs).addElement((file.isDirectory() ? file : file.getParent()));
    }
    // in case we've got stale directory, check if any updates from that didn't touch it
    for (IFile file : MapSequence.fromMap(files).keySet()) {
      if (MapSequence.fromMap(files).get(file) == Status.STALE && file.isDirectory()) {
        String staleDir = DirUtil.normalizeAsDir(file.getPath());
        for (IFile touchDir : newlyTouchedDirs) {
          // if staleDir is parent of any newly touched directories, it's not stale any more
          if (DirUtil.startsWith(DirUtil.normalizeAsDir(touchDir.getPath()), staleDir)) {
            MapSequence.fromMap(files).put(file, Status.KEPT);
            break;
          }
        }
      }
    }
    return this;
  }
  @Override
  public boolean contains(IDelta other) {
    if (!(other instanceof FilesDelta)) {
      return false;
    }
    FilesDelta that = (FilesDelta) other;
    return key.contains(that.key);
  }
  public static class Visitor implements IDeltaVisitor {
    public Visitor() {
    }
    public boolean acceptWritten(IFile file) {
      return true;
    }
    public boolean acceptKept(IFile file) {
      return true;
    }
    public boolean acceptDeleted(IFile file) {
      return true;
    }
  }
  /**
   * DELETED are files explicitly requested to be removed
   * STALE are files that are likely to need removal, unless there's another subsequent 
   * delta that bring them back to life.
   */
  public enum Status {
    WRITTEN(),
    KEPT(),
    DELETED(),
    STALE();

    private Status() {
    }
  }
}
