/*
 * Copyright 2003-2016 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.path;

import jetbrains.mps.vfs.Watchable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * Alike to the {@link java.nio.file.Path}.
 * Might be absolute or relative. The path is split up into so-called name components. Let us call
 * the first name component the <em>root</em> component.
 *
 * The aim of this class is to get rid of working with the file paths (simply Strings) in the client code.
 * This comprises working with separators as well as working with archives.
 *
 * Created by apyshkin on 6/17/16.
 */
public interface Path extends Comparable<Path>, /*AP: do I want this?*/ Watchable, PathUtil {
  /**
   * current system defaults
   */
  char SYSTEM_SEPARATOR_CHAR = File.separatorChar;
  String SYSTEM_SEPARATOR = File.separator;

  /**
   * any path one can get from this API must use this separator
   * default separator is UNIX-style
   */
  char UNIX_SEPARATOR_CHAR = '/'; // this is used for so-called independent paths
  String UNIX_SEPARATOR = String.valueOf(UNIX_SEPARATOR_CHAR);

  char WIN_SEPARATOR_CHAR = '\\'; // this is used for so-called dependent paths
  String WIN_SEPARATOR = String.valueOf(WIN_SEPARATOR_CHAR);

  String ZIP = "zip";
  String DOT_ZIP = "." + ZIP;

  String JAR = "jar";
  String DOT_JAR = "." + JAR;

  /**
   * e.g /Users/ap/foo/bar/abc/my-archive.jar!/my/path/within/jar/module.xml
   *
   * NB: might be any ZIP as well
   */
  String ARCHIVE_SEPARATOR = "!/";

  /**
   * @return whether the given path is relative or absolute
   */
  boolean isRelative();

  /**
   * @return the separator of this path
   */
  char getSeparator();

  /**
   * @return null iff it is a root folder, the parent Path instance otherwise
   * note that this method will not eliminate special path parts like '..' and '.'
   * please
   * @see #toNormal()
   * @see #toCanonical()
   */
  @Nullable Path getParent();

  /**
   * simply a shortcut to the last element of the {@link #getNames()}
   * if the list is empty then this method returns null
   *
   * @return the actual file name (the last in the whole path)
   */
  @Nullable String getFileName();

  /**
   * @return an immutable list of names of folder/file(s)
   */
  @NotNull List<String> getNames();

  /**
   * Returns the root component of this path as a {@code Path} object,
   * or {@code null} if this path does not have a root component.
   *
   * @return  a path representing the root component of this path,
   *          or {@code null}
   */
  @Nullable Path getRoot();

  /**
   * Separator becomes {@link #UNIX_SEPARATOR_CHAR}, replacing drive letter is up to implementation
   */
  @NotNull Path toIndependentPath();

  /**
   * Separator becomes {@link #SYSTEM_SEPARATOR_CHAR}, replacing drive letter is up to implementation
   */
  @NotNull Path toSystemPath();

  /**
   * Tests if this path ends with the given path.
   *
   * If the given path has <em>N</em> elements, and it is relative
   * and this path has <em>N</em> or more elements, then this path ends with
   * the given path if the last <em>N</em> elements of each path, starting at
   * the element farthest from the root, are equal.
   *
   * If the given path is absolute then this path ends with the
   * given path if the root component of this path ends with the root
   * component of the given path, and the corresponding elements of both paths
   * are equal.
   * If this path does not have a root component and the given path has a root component
   * then this path does not end with the given path.
   */
  boolean endsWith(@NotNull String other);

  boolean endsWith(@NotNull Path other);

  /**
   * Tests if this path starts with the given path.
   *
   * The logic is absolutely the same as in the {@link #endsWith(String)}
   *
   * Whether or not the root component of this path starts with the root
   * component of the given path is file system specific. If this path does
   * not have a root component and the given path has a root component then
   * this path does not start with the given path.
   */
  boolean startsWith(@NotNull Path other);

  boolean startsWith(@NotNull String other);

  /**
   * Constructs a relative path between this path and a given path.
   *
   * p2.relativize(p1.resolve(p2)) must be equal to p2
   *
   * <p> Relativization is the inverse of {@link #resolve(Path) resolution}.
   * This method attempts to construct a {@link #isRelative()}  relative} path
   * that when {@link #resolve(Path) resolved} against this path, yields a
   * path that locates the same file as the given path. For example, on UNIX,
   * if this path is {@code "/a/b"} and the given path is {@code "/a/b/c/d"}
   * then the resulting relative path would be {@code "c/d"}.
   * When either paths are relative then a relative path also can be constructed (as if we are looking from the current directory).
   * A relative path cannot be constructed if only one of the paths {@link #isRelative()}.
   * If this path and the given path are * {@link #equals equal} then an <i>empty path</i> is returned.
   *
   * @return  the resulting relative path, or an empty path if both paths are
   *          equal
   *
   * @throws  IllegalArgumentException
   *          if {@code other} is not a {@code Path} that can be relativized
   *          against this path
   */
  @NotNull Path relativize(@NotNull Path other); // TODO

  @NotNull Path resolve(@NotNull Path other); // TODO

  @NotNull Path resolve(@NotNull String other);

  /**
   * @return the path, which is equals to this one but not the same
   */
  @NotNull Path copy();

  /**
   * Returns a {@code Path} object representing the absolute path of this
   * path.
   *
   * <p> If this path is not {@link Path#isRelative()} relative} then it is absolute and this
   * method simply returns this path. Otherwise, this method resolves the path
   * in an implementation dependent manner, typically by resolving the path
   * against a file system default directory. Depending on the implementation,
   * this method may throw an I/O error if the file system is not accessible.
   *
   * @return  a {@code Path} object representing the absolute path
   *
   * @throws  java.io.IOError
   *          if an I/O error occurs
   */
  @NotNull Path toAbsolute();

  /**
   * it tries to do the same that the <code>#toCanonical</code>
   * without actual access to the file system.
   * That means resolving all "." and ".." symbols.
   * Note that in the case there are symlinks in the path string we might end
   * with the quite different path.
   *
   * @see #toCanonical()
   */
  @NotNull Path toNormal();

  /**
   * Returns the canonical pathname string of this abstract pathname.
   *
   * A canonical pathname is both absolute and unique.  The precise
   * definition of canonical form is system-dependent.  This method first
   * converts this pathname to absolute form if necessary, as if by invoking the
   * {@link #toAbsolute()} method, and then maps it to its unique form in a
   * system-dependent way.  This typically involves removing redundant names
   * such as "." and ".." from the pathname, resolving
   * symbolic links (on UNIX platforms), and converting drive letters to a
   * standard case (on Microsoft Windows platforms).
   *
   * Every pathname that denotes an existing file or directory has a
   * unique canonical form.  Every pathname that denotes a nonexistent file
   * or directory also has a unique canonical form.  The canonical form of
   * the pathname of a nonexistent file or directory may be different from
   * the canonical form of the same pathname after the file or directory is
   * created.  Similarly, the canonical form of the pathname of an existing
   * file or directory may be different from the canonical form of the same
   * pathname after the file or directory is deleted.
   *
   * @return  The canonical pathname string denoting the same file or
   *          directory as this abstract pathname
   *
   * @throws  IOException
   *          If an I/O error occurs, which is possible because the
   *          construction of the canonical pathname may require
   *          filesystem queries
   *
   * @see File#getCanonicalFile()
   */
  @NotNull Path toCanonical() throws IOException;

//  @NotNull Path subpath(int beginIndex, int endIndex);
//
//
//  Path resolveSibling(Path other);
//
//  Path resolveSibling(String other);
}
