/*
 * Copyright 2003-2015 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.ide.vfs;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.vfs.SafeWriteRequestor;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.RefreshQueue;
import jetbrains.mps.ide.platform.watching.FileSystemListenersContainer;
import jetbrains.mps.util.FileUtil;
import jetbrains.mps.vfs.CachingContext;
import jetbrains.mps.vfs.CachingFile;
import jetbrains.mps.vfs.CachingFileSystem;
import jetbrains.mps.vfs.FileSystem;
import jetbrains.mps.vfs.FileSystemListener;
import jetbrains.mps.vfs.IFile;
import jetbrains.mps.vfs.DefaultCachingContext;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author Evgeny Gerashchenko
 */
public class IdeaFileSystem extends IdeaFSComponent implements SafeWriteRequestor {
  private static final Logger LOG = LogManager.getLogger(IdeaFileSystem.class);

  private FileSystemListenersContainer myListeners = new FileSystemListenersContainer();

  public IdeaFileSystem() {
  }

  @Override
  public void refresh(@NotNull CachingContext context, Collection<CachingFile> files) {
    Set<VirtualFile> virtualFiles = files.stream().map(file -> ((IdeaFile) file).getVirtualFile()).filter(Objects::nonNull).collect(Collectors.toSet());
    virtualFiles.forEach(VirtualFile::getChildren); // to enforce refresh for this file
    RefreshQueue.getInstance().refresh(!context.isSynchronous(), context.isRecursive(), null, virtualFiles);
  }

  @NotNull
  @Override
  public IdeaFile getFile(@NotNull String path) {
    return new IdeaFile(this, path);
  }

  @Override
  public void addListener(@NotNull FileSystemListener listener) {
    myListeners.addListener(listener);
  }

  @Override
  public void removeListener(@NotNull FileSystemListener listener) {
    myListeners.removeListener(listener);
  }

  public FileSystemListenersContainer getListenersContainer() {
    return myListeners;
  }

  @Override
  public boolean isFileIgnored(@NotNull String name) {
    return FileTypeManager.getInstance().isFileIgnored(name);
  }

  @Override
  public void scheduleUpdateForWrittenFiles(Iterable<IFile> writtenFiles) {
    final List<IFile> newFiles = new ArrayList<>();
    final List<IFile> updatedFiles = new ArrayList<>();
    for (IFile file : writtenFiles) {
      if (file.exists()) {
        updatedFiles.add(file);
      } else {
        newFiles.add(file);
      }
    }
    ApplicationManager.getApplication().invokeLater(new IdeaWriteAction(() -> {
      // Recreate files using VFS
      for (IFile file : newFiles) {
        OutputStream out = null;
        try {
          // No need to close InputStream: it will be closed by loadFromStream()
          byte[] content = StreamUtil.loadFromStream(new FileInputStream(file.getPath()));

          out = file.openOutputStream();
          out.write(content);
        } catch (IOException e) {
          LOG.error(null, e);
        } finally {
          FileUtil.closeFileSafe(out);
        }
      }

      // Refresh added files
      Set<CachingFile> ideaFiles = updatedFiles.stream()
          .filter(file -> file instanceof IdeaFile)
          .map(file -> ((IdeaFile) file)).collect(Collectors.toSet());
      refresh(new DefaultCachingContext(true, false), ideaFiles);
    })
    );
  }

  @Override
  public boolean runWriteTransaction(@NotNull Runnable r) {
    final IdeaWriteAction action = new IdeaWriteAction(r);
    ApplicationManager.getApplication().invokeAndWait(action, ModalityState.defaultModalityState());
    return action.getFailure() == null;
  }

  private static class IdeaWriteAction implements Runnable {
    private final Runnable myDelegate;
    private Exception myException;

    public IdeaWriteAction(@NotNull Runnable delegate) {
      myDelegate = delegate;
    }

    @Override
    public void run() {
      try {
        ApplicationManager.getApplication().runWriteAction(myDelegate);
      } catch (Exception ex) {
        LOG.error(ex.getMessage(), ex);
        myException = ex;
      }
    }

    public Exception getFailure() {
      return myException;
    }
  }

  @NotNull
  @Override
  public String getComponentName() {
    return "IdeaFileSystemProvider";
  }
}
