/*
 * Copyright 2003-2011 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.impl;

import jetbrains.mps.util.FileUtil;
import jetbrains.mps.util.annotation.Hack;
import jetbrains.mps.vfs.IFile;
import jetbrains.mps.vfs.path.Path;
import jetbrains.mps.vfs.path.UniPath;
import jetbrains.mps.vfs.ex.IFileEx;
import jetbrains.mps.vfs.openapi.FileSystem;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Todo rewrite using {@link Path}
 */
public class JarEntryFile implements IFileEx {
  private static final Logger LOG = LogManager.getLogger(JarEntryFile.class);

  public static final String JAR = "jar";
  private static final String DOT = ".";
  public static final String DOT_JAR = DOT + JAR;
  private static final IoFileSystem FS = IoFileSystem.INSTANCE;

  private final AbstractJarFileData myJarFileData;
  private final File myJarFile;
  private final String myEntryPath;
  private final IoFileSystem myFileSystem;

  JarEntryFile(AbstractJarFileData jarFileData, File jarFile, String path, IoFileSystem fileSystem) {
    myJarFileData = jarFileData;
    myJarFile = jarFile;
    myEntryPath = FileUtil.normalize(path);
    myFileSystem = fileSystem;
  }

  @NotNull
  @Override
  public FileSystem getFileSystem() {
    return FS;
  }

  @NotNull
  @Override
  public String getName() {
    String result = myEntryPath;

    int index = result.lastIndexOf('/');
    if (index != -1) {
      result = result.substring(index + 1);
    }
    if (myEntryPath.isEmpty()) {
      return "/";
    }
    return result;
  }

  @Override
  public IFile getParent() {
    if (myEntryPath.isEmpty()) {
      return null;
    } else {
      return new JarEntryFile(myJarFileData, myJarFile, myJarFileData.getParentDirectory(myEntryPath), myFileSystem);
    }
  }

  @Override
  public List<IFile> getChildren() {
    if (!isDirectory()) return Collections.emptyList();

    List<IFile> result = new ArrayList<IFile>();
    for (String e : myJarFileData.getSubdirectories(myEntryPath)) {
      result.add(new JarEntryFile(myJarFileData, myJarFile, e, myFileSystem));
    }
    for (String e : myJarFileData.getFiles(myEntryPath)) {
      result.add(new JarEntryFile(myJarFileData, myJarFile, myEntryPath.length() > 0 ? myEntryPath + "/" + e : e, myFileSystem));
    }

    return result;
  }

  @Override
  public boolean isArchive() {
    return true;
  }

  @Override
  public boolean isInArchive() {
    return true;
  }

  @Override
  @NotNull
  public IFile getDescendant(@NotNull String suffix) {
    String path = myEntryPath.length() > 0 ? myEntryPath + "/" + suffix : suffix;
    return new JarEntryFile(myJarFileData, myJarFile, path, myFileSystem);
  }

  @Override
  public boolean isDirectory() {
    return myJarFileData != null && myJarFileData.isDirectory(myEntryPath);
  }

  @NotNull
  @Override
  public String getPath() {
    return myJarFile.getAbsolutePath() + "!/" + myEntryPath;
  }

  @NotNull
  @Override
  public UniPath toPath() {
    return UniPath.fromString(getPath());
  }

  @Override
  public long lastModified() {
    return myJarFile.lastModified();
  }

  @Override
  public boolean exists() {
    return myJarFileData != null && myJarFileData.exists(myEntryPath);
  }

  @Override
  public boolean createNewFile() {
    return false;
  }

  @Override
  public boolean mkdirs() {
    return false;
  }

  @Override
  public boolean delete() {
    return false;
  }

  @Override
  public boolean rename(String newName) {
    return false;
  }

  @Override
  public boolean move(IFile newParent) {
    return false;
  }

  @Override
  public InputStream openInputStream() throws IOException {
    if (myJarFileData == null) {
      throw new IOException("File is not found " + getPath());
    }
    return myJarFileData.openStream(myEntryPath);
  }

  @Override
  public OutputStream openOutputStream() throws IOException {
    throw new UnsupportedOperationException();
  }

  @Override
  public boolean isReadOnly() {
    return true;
  }

  @Override
  public long length() {
    if (myJarFileData == null) {
      return -1;
    }
    return myJarFileData.getLength(myEntryPath);
  }

  @NotNull
  @Override
  public String toString() {
    return getPath();
  }

  @Override
  public IFile getBundleHome() {
    return new IoFile(myJarFile, myFileSystem);
  }

  @Override
  public boolean setTimeStamp(long time) {
    return false;
  }


  /**
   * The problem here is with jar URLs.
   * JDK does not allow us to work efficiently with jar files via URI.
   * 0. I want to create URL which can be converted to URI.
   * 1. Unfortunately that means that we need to escape space characters.
   *    (unescaped URL cannot be converted to the URI due to the #toURI method contract)
   * 2. We are urged to use multi-argument <code>URI</code> constructor in order to properly escape all characters (spaces for instance).
   *    Moreover it is a recommended way to comply to all URI protocol conventions.
   * 3. URI multi-arg constructor also asks for its path to be absolute (which -- in his understanding -- means a path starting with '/')
   * 4. In jdk a default URL for a jar-file looks like 'jar:file://a.jar!/b.txt' and calling URL#getPath returns
   *    'file://a.jar!/b.txt' which does not start with the slash and hence is not absolute and therefore cannot be simply passed to the URI constructor.
   * 5. That is why we are compelled to use such a hack in constructor arguments which namely passes
   *    a jar:file scheme and passes an honest absolute path '/a.jar!/b.txt'.
   *
   * [AP: I would rather return URLs which can be converted to URIs than the ones which can not]
   * Hopefully this hell will be replaced by the upcoming <code>vfs.Path</code> features.
   */
  @Hack
  @Override
  public URL getUrl() throws MalformedURLException {
//    try {
//      return new URI("jar:file", "", myJarFile.getAbsolutePath() + "!/" + myEntryPath, null, null).toURL();
//    } catch (URISyntaxException e) {
//      throw new RuntimeException(e);
//    }
    return new URL("jar:file://" + myJarFile.getAbsolutePath() + "!/" + myEntryPath);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    JarEntryFile that = (JarEntryFile) o;

    if (myEntryPath != null ? !myEntryPath.equals(that.myEntryPath) : that.myEntryPath != null) return false;
    if (myJarFile != null ? !myJarFile.equals(that.myJarFile) : that.myJarFile != null) return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = myJarFile != null ? myJarFile.hashCode() : 0;
    result = 31 * result + (myEntryPath != null ? myEntryPath.hashCode() : 0);
    return result;
  }
}
