/*
 * 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.structure.modules;

import jetbrains.mps.project.structure.modules.mappingpriorities.MappingPriorityRule;
import jetbrains.mps.smodel.Generator;
import jetbrains.mps.util.annotation.ToRemove;
import jetbrains.mps.util.io.ModelInputStream;
import jetbrains.mps.util.io.ModelOutputStream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.module.SRepository;

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import static java.util.stream.Collectors.toList;

public class GeneratorDescriptor extends ModuleDescriptor {
  private String myAlias;

  private Set<SModuleReference> myDepGenerators;
  private List<MappingPriorityRule> myPriorityRules;

  private boolean myGenerateTemplates = false;
  private boolean myQueriesViaReflection = true;
  private String myGenOutputPath;
  private SModuleReference mySourceLanguage;

  public GeneratorDescriptor() {
    super();
    myDepGenerators = new LinkedHashSet<>();
    myPriorityRules = new ArrayList<>();
  }

  /**
   * for whatever reason, getGeneratorUID() is in fact module name, or what {@link #getNamespace()} is for other modules.
   * In generators, {@link #getNamespace()} is employed for module alias, instead. Shall refactor this, use namespace uniformly among modules
   * to represent its name, and use dedicated {@code getAlias()} to keep generator short name (if needed).
   * @deprecated use {@link #getNamespace()}
   */
  @Deprecated
  @ToRemove(version = 2017.1)
  public String getGeneratorUID() {
    return getNamespace();
  }

  /**
   * @deprecated identical to {@link #setNamespace(String)}
   */
  @Deprecated
  @ToRemove(version = 2017.1)
  public void setGeneratorUID(String generatorUID) {
    setNamespace(generatorUID);
  }

  public void setSourceLanguage(SModuleReference sourceLanguage) {
    mySourceLanguage = sourceLanguage;
  }

  public void setAlias(String alias) {
    myAlias = alias;
  }

  /**
   * While we keep machine-generated module name for Generator modules, alias is a part
   * which Language Designer may pick to his liking. Note, {@link Generator#getAlias()} includes
   * qualifed name of source language as prefix, and value of this method is only a suffix, likely kept unchanged during module renames.
   */
  public String getAlias() {
    return myAlias;
  }

  /**
   * @return identifier of a language this generator transforms
   */
  @NotNull
  public SModuleReference getSourceLanguage() {
    return mySourceLanguage;
  }

  public Set<SModuleReference> getDepGenerators() {
    return myDepGenerators;
  }

  public List<MappingPriorityRule> getPriorityRules() {
    return myPriorityRules;
  }

  public boolean isGenerateTemplates() {
    return myGenerateTemplates;
  }

  public void setGenerateTemplates(boolean generateTemplates) {
    myGenerateTemplates = generateTemplates;
  }

  /**
   * @return <code>true</code> to execute method in QueriesGenerated via reflection,
   * <code>false</code> to provide mechanism for direct calls
   */
  public boolean isReflectiveQueries() {
    return myQueriesViaReflection;
  }
  public void setReflectiveQueries(boolean value) {
    myQueriesViaReflection = value;
  }

  /**
   * Expects at least model read access (although likely no reason to invoke in any other case but from write that modified smth)
   * @param repository where to look old/new module references up.
   */
  @Override
  public boolean updateModuleRefs(SRepository repository) {
    RefUpdateUtil uu = new RefUpdateUtil(repository);
    return RefUpdateUtil.composeUpdates(
      super.updateModuleRefs(repository),
      uu.updateModuleRefs(myDepGenerators),
      uu.updateMappingPriorityRules(myPriorityRules));
  }

  /**
   * FIXME Likely, {@link SolutionDescriptor#getOutputPath()}, {@link LanguageDescriptor#getGenPath()} and this one need to move to
   *       ModuleDescriptor. I would do that once there's clear idea whether to use IFile, java.io.File or Path for these locations,
   *       and whether to keep these as module descriptor attributes or inside relevant {@link jetbrains.mps.project.facets.GenerationTargetFacet facets}.
   * @return filesystem location where generated files for the generator go, or null if this module doesn't support output
   */
  @Nullable
  public String getOutputPath() {
    return myGenOutputPath;
  }

  public void setOutputPath(@Nullable String path) {
    myGenOutputPath = path;
  }


  @Override
  protected int getHeaderMarker() {
    return 0x45459797;
  }

  @Override
  public void save(ModelOutputStream stream) throws IOException {
    super.save(stream);
    stream.writeString(myAlias);
    stream.writeBoolean(myGenerateTemplates);
    stream.writeString(myGenOutputPath);
    stream.writeModuleReference(mySourceLanguage);

    stream.writeInt(myDepGenerators.size());
    for (SModuleReference ref : myDepGenerators) {
      stream.writeModuleReference(ref);
    }

    stream.writeInt(myPriorityRules.size());
    for (MappingPriorityRule rule : myPriorityRules) {
      rule.save(stream);
    }
  }

  @Override
  public void load(ModelInputStream stream) throws IOException {
    super.load(stream);
    myAlias = stream.readString();
    myGenerateTemplates = stream.readBoolean();
    myGenOutputPath = stream.readString();
    mySourceLanguage = stream.readModuleReference();

    myDepGenerators.clear();
    for (int size = stream.readInt(); size > 0; size--) {
      myDepGenerators.add(stream.readModuleReference());
    }

    for (int size = stream.readInt(); size > 0; size--) {
      MappingPriorityRule rule = new MappingPriorityRule();
      rule.load(stream);
      myPriorityRules.add(rule);
    }
  }

  @NotNull
  @Override
  public GeneratorDescriptor copy() {
    GeneratorDescriptor copy = super.copy0(GeneratorDescriptor::new);
    copy.setAlias(myAlias);
    copy.setGenerateTemplates(isGenerateTemplates());
    copy.setOutputPath(getOutputPath());
    copy.setReflectiveQueries(isReflectiveQueries());
    copy.setSourceLanguage(getSourceLanguage());
    copy.getDepGenerators().addAll(getDepGenerators());
    copy.getPriorityRules().addAll(getPriorityRules().stream().map(MappingPriorityRule::copy).collect(toList()));
    return copy;
  }
}
