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

import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.InvalidDataException;
import jetbrains.mps.ide.vfs.ProjectRootListenerComponent;
import jetbrains.mps.project.persistence.ProjectDescriptorPersistence;
import jetbrains.mps.project.structure.project.ModulePath;
import jetbrains.mps.project.structure.project.ProjectDescriptor;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.util.Computable;
import jetbrains.mps.util.MacroHelper.MacroNoHelper;
import jetbrains.mps.util.annotation.ToRemove;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.module.SModule;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * FIXME: AP what is the difference with the MPSProject? Both are based on the idea platform... Merge?
 *
 * It must save/load its state only via the platform methods #saveState, #loadState
 * The project may be changed externally via addModule/removeModule methods,
 *
 * ProjectDescriptor of the Project is supposed to be always in sync with the project state.
 *
 * evgeny, 11/10/11
 */
@State(
  name = "MPSProject",
  storages = @Storage("modules.xml")
)
public class StandaloneMPSProject extends MPSProject implements PersistentStateComponent<Element> {
  private static final Logger LOG = LogManager.getLogger(StandaloneMPSProject.class);

  @SuppressWarnings("UnusedParameters")
  public StandaloneMPSProject(final Project project, ProjectLibraryManager projectLibraryManager, ProjectRootListenerComponent unused) {
    super(project, unused);
  }

  @Override
  public Element getState() {
    if (getProject().isDefault()) {
      return null;
    }
    return new ModelAccessHelper(getModelAccess()).runReadAction(() -> {
      ProjectDescriptor descriptor = getProjectDescriptor();

      String presentableUrl = getProject().getPresentableUrl();
      assert presentableUrl != null; // by contract the project is default <=> url == null
      File projectFile = new File(presentableUrl);
      return new ProjectDescriptorPersistence(projectFile, new MacroNoHelper()).save(descriptor);
    });
  }

  @Override
  public void loadState(Element state) {
    LOG.info("Loading the project '" + getName() + "' from disk");
    if (!getProject().isDefault()) {
      if (state == null) {
        throw new IllegalArgumentException("State is null");
      }
      loadDescriptor(new ElementProjectDataSource(state, getProjectFile()));
      if (ProjectManager.getInstance().getOpenedProjects().contains(this)) {
        update();
      }
    }
  }

  /**
   * @deprecated remove in 3.4 and make final
   */
  @NotNull
  @Deprecated
  public String getErrors() {
    return super.getErrors();
  }

  @Override
  public void disposeComponent() {
    super.disposeComponent();
    dispose();
  }

  @NotNull
  public List<ModulePath> getAllModulePaths() {
    return Collections.unmodifiableList(myProjectDescriptor.getModulePaths());
  }

  // todo remove; project descriptor is its internal substance which represents the persistence data
  @NotNull
  @ToRemove(version = 3.3)
  public ProjectDescriptor getProjectDescriptor() {
    return myProjectDescriptor;
  }

  // todo remove
  @ToRemove(version = 3.3)
  public void setProjectDescriptor(ProjectDescriptor projectDescriptor) {
    myProjectDescriptor = projectDescriptor;
    update();
  }

  // AP fixme : public update exposes the project internals too much (as it looks for me)
  public final void update() {
    ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
    long beginTime = System.nanoTime();
    LOG.info("Updating " + this);
    try {
      if (progressIndicator != null) {
        progressIndicator.setText2("Loading Project Modules");
      }
      super.update();
      if (progressIndicator != null) {
        progressIndicator.setText2("");
      }
    } finally {
      LOG.info(String.format("Updating %s took %.3f s", this, (System.nanoTime() - beginTime) / 1e9));
    }
  }

  public static StandaloneMPSProject open(@NotNull String projectPath) throws JDOMException, InvalidDataException, IOException {
    return (StandaloneMPSProject) MPSProject.open(projectPath);
  }

  // AP: fixme these two methods are working with the UI virtual paths; I want them to be extracted somewhere else
  @Nullable
  public String getFolderFor(@NotNull SModule module) {
    ModulePath modulePath = getPath(module);
    if (modulePath != null) {
      return modulePath.getVirtualFolder();
    } else {
      LOG.warn("Could not find module path for the module " + module);
      return null;
    }
  }

  // XXX there's no reason to keep this method if ProjectBase#setVirtualFolder get exposed and MPS model references to this one get updated.
  public void setFolderFor(@NotNull SModule module, String newFolder) {
    super.setVirtualFolder(module, newFolder);
  }
}
