// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.jps.incremental.resources;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.builders.BuildOutputConsumer;
import org.jetbrains.jps.builders.DirtyFilesHolder;
import org.jetbrains.jps.builders.java.ResourceRootDescriptor;
import org.jetbrains.jps.builders.java.ResourcesTargetType;
import org.jetbrains.jps.builders.storage.BuildDataCorruptedException;
import org.jetbrains.jps.incremental.CompileContext;
import org.jetbrains.jps.incremental.ProjectBuildException;
import org.jetbrains.jps.incremental.ResourcesTarget;
import org.jetbrains.jps.incremental.TargetBuilder;
import org.jetbrains.jps.incremental.messages.BuildMessage;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
import org.jetbrains.jps.incremental.messages.ProgressMessage;
import org.jetbrains.jps.model.module.JpsModule;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;

/**
 * @author Eugene Zhuravlev
 */
public class ResourcesBuilder extends TargetBuilder<ResourceRootDescriptor, ResourcesTarget> {
  private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.resources.ResourcesBuilder");

  public static final String BUILDER_NAME = "Resource Compiler";

  private static final List<StandardResourceBuilderEnabler> ourEnablers = Collections.synchronizedList(new ArrayList<>());

  public ResourcesBuilder() {
    super(ResourcesTargetType.ALL_TYPES);
  }

  public static void registerEnabler(StandardResourceBuilderEnabler enabler) {
    ourEnablers.add(enabler);
  }

  @Override
  public void build(@NotNull ResourcesTarget target,
                    @NotNull DirtyFilesHolder<ResourceRootDescriptor, ResourcesTarget> holder,
                    @NotNull BuildOutputConsumer outputConsumer,
                    @NotNull CompileContext context) throws ProjectBuildException, IOException {
    if (!isResourceProcessingEnabled(target.getModule())) {
      return;
    }

    try {
      final Map<ResourceRootDescriptor, Boolean> skippedRoots = new HashMap<>();
      holder.processDirtyFiles((t, f, srcRoot) -> {
        Boolean isSkipped = skippedRoots.get(srcRoot);
        if (isSkipped == null) {
          File outputDir = t.getOutputDir();
          isSkipped = Boolean.valueOf(outputDir == null || FileUtil.filesEqual(outputDir, srcRoot.getRootFile()));
          skippedRoots.put(srcRoot, isSkipped);
        }
        if (isSkipped.booleanValue()) {
          return true;
        }
        try {
          copyResource(context, srcRoot, f, outputConsumer);
          return !context.getCancelStatus().isCanceled();
        }
        catch (IOException e) {
          LOG.info(e);
          context.processMessage(
            new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, e.getMessage(), FileUtil.toSystemIndependentName(f.getPath()))
          );
          return false;
        }
      });

      context.checkCanceled();

      context.processMessage(new ProgressMessage(""));
    }
    catch(BuildDataCorruptedException | ProjectBuildException e) {
      throw e;
    }
    catch (Exception e) {
      throw new ProjectBuildException(e.getMessage(), e);
    }
  }

  private static boolean isResourceProcessingEnabled(JpsModule module) {
    synchronized (ourEnablers) {
      for (StandardResourceBuilderEnabler enabler : ourEnablers) {
        if (!enabler.isResourceProcessingEnabled(module)) {
          return false;
        }
      }
    }
    return true;
  }

  private static void copyResource(CompileContext context, ResourceRootDescriptor rd, File file, BuildOutputConsumer outputConsumer) throws IOException {
    final File outputRoot = rd.getTarget().getOutputDir();
    if (outputRoot == null) {
      return;
    }
    final String sourceRootPath = FileUtil.toSystemIndependentName(rd.getRootFile().getAbsolutePath());
    final String relativePath = FileUtil.getRelativePath(sourceRootPath, FileUtil.toSystemIndependentName(file.getPath()), '/');
    final String prefix = rd.getPackagePrefix();

    final StringBuilder targetPath = new StringBuilder();
    targetPath.append(FileUtil.toSystemIndependentName(outputRoot.getPath()));
    if (prefix.length() > 0) {
      targetPath.append('/').append(prefix.replace('.', '/'));
    }
    targetPath.append('/').append(relativePath);

    context.processMessage(new ProgressMessage("Copying resources... [" + rd.getTarget().getModule().getName() + "]"));

    final File targetFile = new File(targetPath.toString());
    try {
      final Path from = file.toPath();
      final Path to = targetFile.toPath();
      try {
        Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING);
      }
      catch (NoSuchFileException e) {
        final File parent = targetFile.getParentFile();
        if (parent != null && parent.mkdirs()) {
          Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING); // repeat on successful target dir creation
        }
      }
      outputConsumer.registerOutputFile(targetFile, Collections.singletonList(file.getPath()));
    }
    catch (Exception e) {
      context.processMessage(new CompilerMessage(BUILDER_NAME, BuildMessage.Kind.ERROR, CompilerMessage.getTextFromThrowable(e)));
    }
  }

  @Override
  @NotNull
  public String getPresentableName() {
    return BUILDER_NAME;
  }
}
@@ -4952,32 +4952,48 @@
 ();%0A      try %7B%0A
+        try %7B%0A  
         Files.co
@@ -5045,26 +5045,30 @@
 ING);%0A      
-%7D%0A
+  %7D%0A  
       catch 
@@ -5085,32 +5085,34 @@
 eException e) %7B%0A
+  
         final Fi
@@ -5155,24 +5155,26 @@
 ();%0A        
+  
 if (parent !
@@ -5198,24 +5198,26 @@
 mkdirs()) %7B%0A
+  
           Fi
@@ -5317,33 +5317,353 @@
 reation%0A        
-%7D
+  %7D%0A          else %7B%0A            throw e;%0A          %7D%0A        %7D%0A      %7D%0A      catch (IOException e) %7B%0A        // fallback: trying 'classic' copying via streams%0A        LOG.info(%22Error copying resource %22+ file.getPath() + %22 to %22 + targetFile.getPath() + %22 with NIO API%22, e);%0A        FileUtil.copyContent(file, targetFile);
 %0A      %7D%0A      o

add fallback resource copying via steams if NIO-based approach fails (IDEA-201797)