/*
 * Copyright 2003-2011 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.workbench.dialogs.project.newproject;

import com.intellij.ide.startup.StartupManagerEx;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.components.StorageScheme;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ex.ProjectManagerEx;
import com.intellij.openapi.startup.StartupManager;
import jetbrains.mps.ide.newSolutionDialog.NewModuleUtil;
import jetbrains.mps.ide.projectPane.ProjectPane;
import jetbrains.mps.migration.global.ProjectMigrationUtil;
import jetbrains.mps.project.MPSExtentions;
import jetbrains.mps.project.MPSProject;
import jetbrains.mps.project.MPSProjectVersion;
import jetbrains.mps.project.SModuleOperations;
import jetbrains.mps.project.Solution;
import jetbrains.mps.project.StandaloneMPSProject;
import jetbrains.mps.smodel.Language;
import jetbrains.mps.smodel.SModelInternal;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import jetbrains.mps.util.annotation.ToRemove;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.EditableSModel;

import java.io.File;

public class ProjectFactory {
  private ProjectOptions myOptions;
  private Project myCreatedProject;

  private Language myCreatedLanguage;
  private Solution myCreatedSolution;

  public ProjectFactory(ProjectOptions options) {
    myOptions = options;
  }

  /**
   * @deprecated parameter {@code currentProject} is not used any more.
   * Use {@link ProjectFactory#ProjectFactory(jetbrains.mps.workbench.dialogs.project.newproject.ProjectOptions)} constructor instead
   */
  @Deprecated
  @ToRemove(version = 2017.1)
  public ProjectFactory(Project currentProject, ProjectOptions options) {
    this(options);
  }

  public Project createProject() throws ProjectNotCreatedException {
    final String[] error = new String[]{null};
    ProgressManager.getInstance().run(new Task.Modal(null, "Creating Project", false) {
      @Override
      public void run(@NotNull() ProgressIndicator indicator) {
        indicator.setIndeterminate(true);
        error[0] = createDirs();
        if (error[0] != null) {
          return;
        }
        String suffix;
        if (myOptions.getStorageScheme().equals(StorageScheme.DIRECTORY_BASED)) {
          suffix = Project.DIRECTORY_STORE_FOLDER;
        } else {
          suffix = myOptions.getProjectName() + MPSExtentions.DOT_MPS_PROJECT;
        }

        final String projectFilePath = myOptions.getProjectPath() + File.separator + suffix;
        //MPS-22895 need to run in EDT
        ApplicationManager.getApplication().invokeAndWait(new Runnable() {
          @Override
          public void run() {
            try {
              myCreatedProject = ProjectManagerEx.getInstanceEx().newProject(myOptions.getProjectName(), projectFilePath, true, false);
            } catch (Exception e) {
              throw new RuntimeException(e);
            }
          }
        }, indicator.getModalityState());
      }
    });

    if (error[0] != null) {
      throw new ProjectNotCreatedException(error[0]);
    }

    boolean doNotSave = ApplicationManagerEx.getApplicationEx().isDoNotSave();
    try {
      ApplicationManagerEx.getApplicationEx().doNotSave(false);
      myCreatedProject.save();
    } finally {
      ApplicationManagerEx.getApplicationEx().doNotSave(doNotSave);
    }

    //noinspection ConstantConditions
    final MPSProject mpsProject = myCreatedProject.getComponent(MPSProject.class);
    assert mpsProject != null;

    StartupManager.getInstance(myCreatedProject).registerPostStartupActivity(() -> mpsProject.getModelAccess().executeCommand(() -> {
      if (myOptions.getCreateNewLanguage()) {
        myCreatedLanguage = NewModuleUtil.createLanguage(myOptions.getLanguageNamespace(), myOptions.getLanguagePath(), mpsProject);
      }

      if (myOptions.getCreateNewSolution()) {
        myCreatedSolution = NewModuleUtil.createSolution(myOptions.getSolutionNamespace(), myOptions.getSolutionPath(), mpsProject);
      }

      if (myCreatedSolution != null && myCreatedLanguage != null) {
        myCreatedSolution.save();
        if (myOptions.getCreateModel()) {
          EditableSModel model = SModuleOperations.createModelWithAdjustments(
              myCreatedSolution.getModuleReference().getModuleName() + ".sandbox",
              myCreatedSolution.getModelRoots().iterator().next());
          ((SModelInternal) model).addLanguage(MetaAdapterFactory.getLanguage(myCreatedLanguage.getModuleReference()));
          model.save();
        }
      }
      if (myOptions.getCreateNewSolution() || myOptions.getCreateNewLanguage()) {
        ((StandaloneMPSProject) mpsProject).update();
      }
    }));
    return myCreatedProject;
  }

  public void activate() {
    if (myCreatedProject == null) {
      return;
    }
    myCreatedProject.getComponent(MPSProjectVersion.class).setVersion(MPSProjectVersion.CURRENT);
    ProjectMigrationUtil.skipMigrationsOnProjectCreation(myCreatedProject);

    ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx();
    boolean opened = projectManager.openProject(myCreatedProject);

    if (opened) {
      StartupManagerEx startupManager = StartupManagerEx.getInstanceEx(myCreatedProject);
      // extra .invokeLater() was added here (copied from IDEA platform) see: https://youtrack.jetbrains.com/issue/IDEA-158859
      Runnable projectPaneActivator =
          () -> ApplicationManager.getApplication().invokeLater(ProjectPane.getInstance(myCreatedProject)::activate, ModalityState.NON_MODAL);
      if (startupManager.postStartupActivityPassed()) {
        startupManager.runWhenProjectIsInitialized(projectPaneActivator);
      } else {
        startupManager.registerPostStartupActivity(projectPaneActivator);
      }
    }
  }

  @Nullable
  private String createDirs() {
    File projectDirFile = new File(myOptions.getProjectPath());
    if (!(projectDirFile.exists())) {
      if (!projectDirFile.mkdirs()) {
        return "Couldn't create project directory";
      }
    }

    if (myOptions.getCreateNewLanguage()) {
      File languageDirFile = new File(myOptions.getLanguagePath());
      if (!(languageDirFile.exists())) {
        if (!languageDirFile.mkdirs()) {
          return "Couldn't create language directory";
        }
      }
    }

    if (myOptions.getCreateNewSolution()) {
      String path = myOptions.getSolutionPath() + File.separator + myOptions.getSolutionNamespace() + MPSExtentions.DOT_SOLUTION;
      File solutionDescriptorFile = new File(path);
      File dir = solutionDescriptorFile.getParentFile();
      if (!(dir.exists())) {
        if (!dir.mkdirs()) {
          return "Couldn't create solution directory";
        }
      }
    }

    return null;
  }

  public static class ProjectNotCreatedException extends Exception {
    public ProjectNotCreatedException(String message) {
      super(message);
    }
  }
}
