/*
 * Copyright 2003-2017 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.project.facets;

import jetbrains.mps.generator.fileGenerator.FileGenerationUtil;
import jetbrains.mps.project.AbstractModule;
import jetbrains.mps.project.ProjectPathUtil;
import jetbrains.mps.vfs.IFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SModuleFacet;

import java.util.Set;

// FIXME why some API is with String for files (like getLibraryClassPath, getClassPath, getAdditionalSourcePaths), not IFile?
public interface JavaModuleFacet extends SModuleFacet, GenerationTargetFacet {
  String FACET_TYPE = "java";

  // flag for internal use (we can compile either in MPS or in Idea)
  // for "generate" task
  boolean isCompileInMps();

  /**
   * @return root folder where generated classes of any module's model reside, or {@code null} if associated module doesn't expect class files to be written.
   *         Note, although generated classes are part of {@linkplain #getClassPath() classpath}, this method is to access design-time location of generated
   *         classes, not that at deployment time. For deployed modules, this method may return {@code null}, while generated module classes would be available
   *         for classloading through {@link #getClassPath()}.
   */
  // for "generate" task if isCompileInMPS == true
  @Nullable
  IFile getClassesGen();

  /**
   *
   * @param model model of a module this facet is associated with
   * @return FS location where model class files reside, {@code null} for deployed modules
   */
  @Nullable
  default IFile getClassesLocation(@NotNull SModel model) {
    IFile classesGen = getClassesGen();
    if (classesGen == null) {
      return null;
    }
    return FileGenerationUtil.getDefaultOutputDir(model.getReference(), classesGen);
  }

  /**
   * PROVISIONAL Perhaps, worth moving to {@link GenerationTargetFacet}. OTOH, don't see a reason to restrict output of the facet to single root
   * Need one here for transition period to have default implementations for {@link #getOutputLocation(SModel)} and {@link #getOutputCacheLocation(SModel)}
   * Perhaps, shall be private to JavaModuleFacetImpl (need to get rid of default implementations of the dependant methods first).
   *
   * Top location where all 'primary' output goes, generally bound to module location, although no assumption shall be made about that.
   * Known as {@code source_gen}
   *
   * @return {@code null} if associated module doesn't allow generation.
   */
  @Nullable
  default IFile getOutputRoot() {
    if (getModule() instanceof AbstractModule ) {
      // there's no output location for packaged/deployed modules
      String outputPath = ProjectPathUtil.getGeneratorOutputPath(((AbstractModule) getModule()).getModuleDescriptor());
      return outputPath == null ? null : ((AbstractModule) getModule()).getFileSystem().getFile(outputPath);
    }
    return null;
  }

  /**
   * PROVISIONAL, see {@link #getOutputRoot()} for details.
   *
   * Top location where auxiliary (dependencies, hashes) output goes, generally bound to {@linkplain #getOutputRoot() module's output location}.
   * Known as {@code source_gen.caches}
   *
   * @return {@code null} if associated module doesn't have sources/doesn't allow generation
   */
  @Nullable
  default IFile getOutputCacheRoot() {
    IFile outputRoot = getOutputRoot();
    return outputRoot == null ? null : FileGenerationUtil.getCachesDir(outputRoot);
  }


  /**
   * E.g. source_gen/qualified/model/name/
   * FIXME decide whether shall look into model output overrides (see DefaultStreamManager.Provider.getOverriddenOutputDir()). Didn't check for
   *       override right away as I don't like use of distinct boolean option isGenerateIntoModelFolder() to describe an output alternative, and
   *       would like to come up with a better alternative
   * @return {@code null} if this module doesn't allow generation (e.g. packaged)
   */
  @Nullable
  @Override
  default IFile getOutputLocation(@NotNull SModel model) {
    final SModule associatedModule = getModule();
    assert model.getModule() == associatedModule;
    IFile outputRoot = getOutputRoot();
    return outputRoot == null ? null : FileGenerationUtil.getDefaultOutputDir(model.getReference(), outputRoot);
  }

  /**
   * E.g. source_gen.caches/qualified/model/name/
   * @return {@code null} if this module doesn't allow generation (e.g. packaged)
   */
  @Nullable
  @Override
  default IFile getOutputCacheLocation(@NotNull SModel model) {
    final SModule associatedModule = getModule();
    assert model.getModule() == associatedModule;
    IFile outputRoot = getOutputRoot();
    return outputRoot == null ? null : FileGenerationUtil.getDefaultOutputDir(model.getReference(), FileGenerationUtil.getCachesDir(outputRoot));
  }

  // for "compilation" task
  // contains classes folder if isCompileInMPS == false && folder exists
  Set<String> getLibraryClassPath();

  // for "run" task, classpath == classes folder + library class path
  Set<String> getClassPath();

  /**
   * @return extra locations with source files to compile along with module's own generated artifacts from {@link #getOutputRoot()}, or empty collection.
   */
  Set<String> getAdditionalSourcePaths();
}
