/*
 * Copyright 2003-2017 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrains.mps.vfs;

import jetbrains.mps.util.annotation.ToRemove;
import jetbrains.mps.vfs.path.Path;
import jetbrains.mps.vfs.path.UniPath;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.util.ProgressMonitor;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

/**
 * AP:
 * An abstraction of path names similar to the {@link java.io.File}.
 * Represents an abstract absolute path (or location) on disk (might be non-existent).
 * Moreover might represents a path to some archive + path within the archive (unlike the {@link java.io.File}).
 *
 * FIXME
// * Another difference from the {@link java.io.File} is the one that this abstraction is supposed to be
// * platform-independent (sic!) meaning that the path
 * @see #getPath() for an example
 * FIXME
 *
 * Also it is an MPS abstraction around the IDEA platform file system {@link com.intellij.openapi.vfs.VirtualFile}.
 * IDEA provides an intelligent caching mechanism which might boost up the file system traversal (comparing to the {@link java.io.File}).
 * @see CachingFile
 *
 * It has no fallback implementation base on {@link java.io.File},
 * however there is a special singleton class {@link jetbrains.mps.vfs.FileSystem} with
 * method #getFileByPath which creates a suitable instance of IFile.
 *
 * These mechanism nevertheless is deprecated and must not be used.
 *
 * From 3.4 you are supposed to use the direct implementing classes of IFile.
 *
 * //TODO continue
 *
 * AP: I suggest to continue using this interface because dropping it might cause too much pain.
 * As long as it is (technically speaking) our internal api, we are more or less free to change it.
 *
 * This class mixes two different issues (as well as the {@link java.io.File}): it works with paths (which are strings) and
 * also it accesses the physical fs from time to time. I'd rather split it up.
 *
 * FIXME IFile is mutable
 * (as well as the java.io.File) I think it is unacceptable.
 * we define it as a pathname abstraction. That means that we cannot rename the IFile, we can only rename something
 * that lies at this pathname on disk. The IFile itself must not be touched in any way. Otherwise it is cumbersome.
 * The alternative is to reconsider the IFile contract.
 *
 * AP
 */
public interface IFile {
  /**
   * @return the file system which this file belongs to
   */
  @NotNull jetbrains.mps.vfs.openapi.FileSystem getFileSystem();

  /**
   * @return simply the last name of the file (the furthest one)
   */
  @NotNull String getName();

  /**
   * @return the whole path to the abstract location.
   * The current contract:
   * the resulting path will be canonical, absolute
   * and the folders are separated with {@link Path#UNIX_SEPARATOR_CHAR} // todo explain more
   *
   * @see File#getCanonicalPath()
   * @see Path
   */
  @ToRemove(version = 3.5)
  /*@Deprecated*/
  @NotNull String getPath();

  /**
   * @return the comprised path corresponding to this file
   */
  @NotNull
  UniPath toPath();

  URL getUrl() throws MalformedURLException;

  /**
   * @return null iff the instance is root and has no parent, the parent folder otherwise
   */
  @Nullable IFile getParent();

  /**
   * shortcut to {@link #toPath()} {@link UniPath#isArchive()}
   * @return whether the underlying pathname points exactly to an archive file
   */
  boolean isArchive();

  /**
   * shortcut to {@link #toPath()} {@link UniPath#isInArchive()}
   * @return whether the underlying pathname points to an archive file or some of its contents
   */
  boolean isInArchive();

  /**
   * @deprecated use {@link #isArchive()} or {@link #isInArchive()}
   */
  @ToRemove(version = 3.4)
  @Deprecated
  default boolean isPackaged() {
    return isInArchive();
  }

  /**
   * @deprecated use {@link CachingFile#refresh(CachingContext)}
   */
  @ToRemove(version = 3.4)
  @Deprecated
  default void refresh() {
    if (this instanceof CachingFile) {
      CachingFile me = (CachingFile) this;
      me.refresh(new DefaultCachingContext(true, false));
    }
  }

  /**
   * @return the jar or folder which contains this file
   * @deprecated use {@link #toPath()} and extract the path you need
   */
  @Deprecated
  @ToRemove(version = 3.4)
  IFile getBundleHome();

  // accessing physical fs

  boolean isDirectory();
  boolean isReadOnly();


  /**
   * TODO will be like this in some implementations of these two methods after 3.4:
   //   * the files in the archive root in the case when {@link #isArchive()} is true.
   //   *
   //   * Thus the client of this method need not to bother to expand the archives on his own: the implementing class
   //   * must do it automatically. Probably cosy recursive processing also will be provided.
   * TODO
   * FIXME please document whether suffix may include path separators (i.e. if one could query folder.getDescendant("my/relative/path/to/file")
   * @return the file which has this file's path + suffix
   */
  @NotNull IFile getDescendant(@NotNull String suffix);

  /**
   * @return the children of this file in case when it is a folder,
   * null iff it is a file and therefore has no children
   */
  @Nullable List<IFile> getChildren();

  default void addListener(@NotNull FileListener listener) {
    getFileSystem().addListener(new FileListenerAdapter(this, listener));
  }

  default void removeListener(@NotNull FileListener listener) {
    getFileSystem().removeListener(new FileListenerAdapter(this, listener));
  }

  /**
   * fixme if it is the same as in java.io.File then we need to enforce it
   */
  long lastModified();
  long length();
  boolean exists();
  boolean setTimeStamp(long time);

  /**
   * creates a physical file
   * @return whether it is a success
   */
  boolean createNewFile();
  boolean mkdirs();

  /**
   * FIXME document what happens if one deletes non-empty folder. IoFile seems to force deletion. Is it the contract?
   * FIXME document what happens if this file doesn't exist (false == exists()).
   */
  boolean delete();

  boolean rename(String newName);
  boolean move(IFile newParent);

  InputStream openInputStream() throws IOException;
  OutputStream openOutputStream() throws IOException;
}
