/*
 * 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.jps.build;

import com.intellij.openapi.util.io.FileUtil;
import jetbrains.mps.extapi.persistence.FileSystemBasedDataSource;
import jetbrains.mps.idea.core.make.MPSMakeConstants;
import jetbrains.mps.project.MPSExtentions;
import jetbrains.mps.tool.builder.make.ReducedMakeFacetConfiguration;
import jetbrains.mps.vfs.IFile;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.jps.builders.logging.ProjectBuilderLogger;
import org.jetbrains.jps.incremental.CompileContext;
import org.jetbrains.jps.incremental.FSOperations;
import org.jetbrains.jps.incremental.ModuleBuildTarget;
import org.jetbrains.jps.incremental.ModuleLevelBuilder.OutputConsumer;
import org.jetbrains.jps.incremental.fs.CompilationRound;
import org.jetbrains.jps.incremental.java.JavaBuilder;
import org.jetbrains.jps.incremental.messages.BuildMessage.Kind;
import org.jetbrains.jps.incremental.messages.CompilerMessage;
import org.jetbrains.jps.model.java.JpsJavaExtensionService;
import org.jetbrains.jps.util.JpsPathUtil;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.persistence.DataSource;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;

public class MPSMakeFilesAfterProcessor {
  @NonNls
  private static final ResourceBundle BUNDLE = ResourceBundle.getBundle("jetbrains.mps.idea.core.MPSCoreBundle");

  @NonNls
  private static final String MPS_GENERATED_FILES = "MPS generated files:";
  private final Map<SModel, ModuleBuildTarget> myModelToTargetMap;
  private final GenerationPathsController myPathsController;
  private final OutputConsumer myOutputConsumer;
  private final CompileContext myContext;

  public MPSMakeFilesAfterProcessor(Map<SModel, ModuleBuildTarget> modelToTargetMap, GenerationPathsController pathsController, OutputConsumer outputConsumer, CompileContext context) {
    myModelToTargetMap = modelToTargetMap;
    myPathsController = pathsController;
    myOutputConsumer = outputConsumer;
    myContext = context;
  }

  public boolean process(final ReducedMakeFacetConfiguration makeConfig) {
    boolean success = processWrittenFiles(makeConfig);
    success &= processDeletedFiles(makeConfig);
    return success;
  }

  private ProjectBuilderLogger getBuilderLogger() {
    return myContext.getLoggingManager().getProjectBuilderLogger();
  }

  private boolean processDeletedFiles(ReducedMakeFacetConfiguration makeConfig) {
    boolean success = true;
    List<String> deletedFiles = makeConfig.getDeletedFiles();
    ProjectBuilderLogger logger = getBuilderLogger();
    if (logger.isEnabled()) {
      logger.logDeletedFiles(deletedFiles);
    }

    for (String deletedFile : deletedFiles) {
      try {
        FSOperations.markDeleted(myContext, new File(deletedFile));
      } catch (IOException e) {
        reportError(BUNDLE.getString("io.problem.while.deleting.files.with.fs"), e);
        success = false;
      }
    }
    return success;
  }

  private boolean processWrittenFiles(ReducedMakeFacetConfiguration makeConfig) {
    Collection<String> writtenPaths = makeConfig.getWrittenFiles();
    boolean success = true;
    logGenerated(makeConfig);
    for (String writtenPath : writtenPaths) {
      SModel source = makeConfig.getSource(writtenPath);
      ModuleBuildTarget target = myModelToTargetMap.get(source);
      File writtenFile = new File(writtenPath);

      try {
        // all written java files need to be marked as dirty to get compiled by the JavaBuilder
        FSOperations.markDirty(myContext, CompilationRound.CURRENT, new File(writtenPath));
        myOutputConsumer.registerOutputFile(target, writtenFile, getFilesFromDataSource(source.getSource()));
      } catch (IOException e) {
        reportError(BUNDLE.getString("io.problem.while.marking.java.sources.dirty"), e);
        success = false;
      }
      if (!isJava(writtenFile)) {
        // all non-java files got to be copied (which are not in the caches folder, e.g. dependencies/generated)
        if (!myPathsController.getRedirects().isInCacheOutput(writtenPath)) {
          try {
            copyResource(target, writtenFile);
          } catch (IOException e) {
            reportError(BUNDLE.getString("io.problem.during.resources.copying"), e);
            success = false;
          }
        }
      }
    }
    return success;
  }

  private void logGenerated(ReducedMakeFacetConfiguration makeFacetConfiguration) {
    ProjectBuilderLogger logger = getBuilderLogger();
    if (logger.isEnabled()) {
      try {
        logger.logCompiledPaths(makeFacetConfiguration.getWrittenFiles(), MPSMakeConstants.BUILDER_ID, MPS_GENERATED_FILES);
      } catch (IOException ignored) {
      }
    }
  }

  // FIXME: FileSystemBasedDataSource#getAffectedFiles() needs to be rewritten in a way, where it gives no directories only files
  private List<String> getFilesFromDataSource(DataSource dataSource) {
    if (!(dataSource instanceof FileSystemBasedDataSource)) {
      throw new IllegalArgumentException("MPS Idea plugin does not support the data source root formats other than FileDataSource and FilePerRootDataSource");
    }
    List<String> result = new ArrayList<String>();
    Collection<IFile> affectedFiles = ((FileSystemBasedDataSource) dataSource).getAffectedFiles();
    for (IFile file : affectedFiles) {
      if (!file.isDirectory()) {
        result.add(file.getPath());
      } else {
        for (IFile child : file.getChildren()) {
          if (FileUtil.extensionEquals(child.getName(), MPSExtentions.MODEL_HEADER)
            || FileUtil.extensionEquals(child.getName(), MPSExtentions.MODEL_ROOT)) {
            result.add(child.getPath());
          }
        }
      }
    }
    return result;
  }

  private boolean isJava(File file) {
    return JavaBuilder.JAVA_SOURCES_FILTER.accept(file);
  }

  private void copyResource(ModuleBuildTarget target, File file) throws IOException {
    // the file is created in the output directory, which may have been redirected to a "hidden" location
    File root = myPathsController.getOutputRoot(target);
    String relativePath = FileUtil.getRelativePath(root, file);
    if (relativePath == null) {
      throw new IllegalStateException("File resource at " + file.getAbsolutePath() + " is not located under the root path " + root.getAbsolutePath());
    }
    relativePath = FileUtil.toSystemIndependentName(relativePath);

    final String outputRootUrl = JpsJavaExtensionService.getInstance().getOutputUrl(target.getModule(), target.isTests());
    final String targetPath = JpsPathUtil.urlToPath(outputRootUrl) + '/' + relativePath;

    final File targetFile = new File(targetPath).getCanonicalFile();
    FileUtil.copyContent(file, targetFile);
    myOutputConsumer.registerOutputFile(target, targetFile, Collections.singletonList(file.getPath()));
  }

  private void reportError(String msg, Throwable e) {
    myContext.processMessage(new CompilerMessage(msg, Kind.ERROR, e.getMessage()));
  }
}
