package org.jetbrains.jps.incremental.java;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.incremental.Paths;

import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * @author Eugene Zhuravlev
 *         Date: 9/24/11
 */
class JavacFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {

  private final Context myContext;
  private Map<File, Set<File>> myOutputsMap = Collections.emptyMap();

  interface Context {
    StandardJavaFileManager getStandardFileManager();

    void consumeOutputFile(@NotNull OutputFileObject obj);

    void reportMessage(final Diagnostic.Kind kind, String message);

    void ensurePendingTasksCompleted();
  }

  public JavacFileManager(Context context) {
    super(context.getStandardFileManager());
    myContext = context;
  }

  public boolean setOutputDirectories(final Map<File, Set<File>> outputDirToSrcRoots) {
    for (File outputDir : outputDirToSrcRoots.keySet()) {
      // this will validate output dirs
      if (!setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(outputDir))) {
        return false;
      }
    }
    myOutputsMap = outputDirToSrcRoots;
    return true;
  }

  public boolean setLocation(Location location, Iterable<? extends File> path) {
    try {
      getStdManager().setLocation(location, path);
    }
    catch (IOException e) {
      myContext.reportMessage(Diagnostic.Kind.ERROR, e.getMessage());
      return false;
    }
    return true;
  }

  public boolean isSameFile(FileObject a, FileObject b) {
    if (a instanceof OutputFileObject && b instanceof OutputFileObject) {
      return a.equals(b);
    }
    return super.isSameFile(a, b);
  }

  public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
    if (kind != JavaFileObject.Kind.SOURCE && kind != JavaFileObject.Kind.CLASS) {
      throw new IllegalArgumentException("Invalid kind " + kind);
    }
    return getFileForOutput(location, kind, externalizeFileName(className, kind), className, sibling);
  }

  public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
    final StringBuilder name = new StringBuilder();
    if (packageName.isEmpty()) {
      name.append(relativeName);
    }
    else {
      name.append(externalizeFileName(packageName)).append(File.separatorChar).append(relativeName);
    }
    final String fileName = name.toString();
    return getFileForOutput(location, getKind(fileName), fileName, null, sibling);
  }

  private OutputFileObject getFileForOutput(Location location, JavaFileObject.Kind kind, String fileName, @Nullable String className, FileObject sibling) throws IOException {
    JavaFileObject src = null;
    if (sibling instanceof JavaFileObject) {
      final JavaFileObject javaFileObject = (JavaFileObject)sibling;
      if (javaFileObject.getKind() == JavaFileObject.Kind.SOURCE) {
        src = javaFileObject;
      }
    }

    File dir = getSingleOutputDirectory(location, src);

    if (location == StandardLocation.CLASS_OUTPUT) {
      if (dir == null) {
        throw new IOException("Output directory is not specified");
      }
    }
    else if (location == StandardLocation.SOURCE_OUTPUT) {
      if (dir == null) {
        dir = getSingleOutputDirectory(StandardLocation.CLASS_OUTPUT, src);
        if (dir == null) {
          throw new IOException("Neither class output directory nor source output are specified");
        }
      }
    }
    final File file = (dir == null? new File(fileName).getAbsoluteFile() : new File(dir, fileName));
    return new OutputFileObject(myContext, file, kind, className, src);
  }

  private File getSingleOutputDirectory(final Location loc, final JavaFileObject sourceFile) {
    if (loc == StandardLocation.CLASS_OUTPUT) {
      if (myOutputsMap.size() > 1 && sourceFile != null) {
        // multiple outputs case
        final File outputDir = findOutputDir(new File(Paths.toURI(sourceFile.toUri().getPath())));
        if (outputDir != null) {
          return outputDir;
        }
      }
    }

    final Iterable<? extends File> location = getStdManager().getLocation(loc);
    if (location != null) {
      final Iterator<? extends File> it = location.iterator();
      if (it.hasNext()) {
        return it.next();
      }
    }
    return null;
  }

  private File findOutputDir(File src) {
    File file = src.getParentFile();
    while (file != null) {
      for (Map.Entry<File, Set<File>> entry : myOutputsMap.entrySet()) {
        if (entry.getValue().contains(file)) {
          return entry.getKey();
        }
      }
      file = file.getParentFile();
    }
    return null;
  }

  @NotNull
  private StandardJavaFileManager getStdManager() {
    return fileManager;
  }

  public Iterable<? extends JavaFileObject> toJavaFileObjects(Iterable<? extends File> files) {
    return getStdManager().getJavaFileObjectsFromFiles(files);
  }

  @Override
  public void close() {
    try {
      super.close();
    }
    catch (IOException e) {
      throw new RuntimeException(e);
    }
    finally {
      myOutputsMap = Collections.emptyMap();
    }
  }

  private static JavaFileObject.Kind getKind(String name) {
    if (name.endsWith(JavaFileObject.Kind.CLASS.extension)){
      return JavaFileObject.Kind.CLASS;
    }
    if (name.endsWith(JavaFileObject.Kind.SOURCE.extension)) {
      return JavaFileObject.Kind.SOURCE;
    }
    if (name.endsWith(JavaFileObject.Kind.HTML.extension)) {
      return JavaFileObject.Kind.HTML;
    }
    return JavaFileObject.Kind.OTHER;
  }

  private static String externalizeFileName(CharSequence cs, JavaFileObject.Kind kind) {
    return externalizeFileName(cs) + kind.extension;
  }

  private static String externalizeFileName(CharSequence name) {
    return name.toString().replace('.', File.separatorChar);
  }

}

@@ -38,16 +38,62 @@
 .java;%0A%0A
+import com.intellij.openapi.util.io.FileUtil;%0A
 import o
@@ -4914,20 +4914,24 @@
  file = 
-f
+F
 ile
+Util
 .getPare
@@ -4933,24 +4933,28 @@
 tParentFile(
+file
 );%0A    %7D%0A   

use noncanonical paths aware FileUtil.getParent()