/*
 * Copyright 2003-2016 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.idea.core.actions;

import com.intellij.ide.projectView.ProjectView;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import jetbrains.mps.extapi.model.SModelBase;
import jetbrains.mps.extapi.module.SModuleBase;
import jetbrains.mps.extapi.persistence.ModelFactoryService;
import jetbrains.mps.extapi.persistence.SourceRoot;
import jetbrains.mps.extapi.persistence.datasource.DataSourceFactoryFromName;
import jetbrains.mps.extapi.persistence.datasource.PreinstalledDataSourceTypes;
import jetbrains.mps.ide.actions.MPSCommonDataKeys;
import jetbrains.mps.ide.icons.IdeIcons;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.ide.vfs.VirtualFileUtils;
import jetbrains.mps.persistence.FilePerRootDataSource;
import jetbrains.mps.persistence.ModelCannotBeCreatedException;
import jetbrains.mps.persistence.PreinstalledModelFactoryTypes;
import jetbrains.mps.project.LanguageImportHelper;
import jetbrains.mps.project.MPSProject;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.smodel.SModelInternal;
import jetbrains.mps.util.Computable;
import jetbrains.mps.vfs.IFile;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.EditableSModel;
import org.jetbrains.mps.openapi.model.SModelName;
import org.jetbrains.mps.openapi.module.ModelAccess;
import org.jetbrains.mps.openapi.module.SRepository;
import org.jetbrains.mps.openapi.persistence.DataSource;
import org.jetbrains.mps.openapi.persistence.ModelFactory;
import org.jetbrains.mps.openapi.persistence.ModelRoot;
import org.jetbrains.mps.openapi.persistence.datasource.DataSourceType;

/**
 * Created by danilla on 28/10/15.
 */
public class MakeDirAModel extends NewModelActionBase {
  private static Logger LOG = LogManager.getLogger(MakeDirAModel.class);
  public static final DataKey<LanguageImportHelper.Interaction> LANGUAGE_IMPORT_INTERACTION = DataKey.create("languageImportInteraction");

  public MakeDirAModel() {
    super("Use MPS language here", null, IdeIcons.LANGUAGE_ICON);
  }

  @Override
  public void actionPerformed(AnActionEvent anActionEvent) {
    EditableSModel model = createModel(anActionEvent);
    if (model == null) {
      return;
    }

    MPSProject mpsProject = ProjectHelper.fromIdeaProject(myProject);
    SRepository repository = ProjectHelper.getProjectRepository(myProject);
    assert mpsProject != null;
    assert repository != null;

    LanguageImportHelper importHelper = ApplicationManager.getApplication().isUnitTestMode() ?
      new LanguageImportHelper(mpsProject, LANGUAGE_IMPORT_INTERACTION.getData(anActionEvent.getDataContext())) :
      new LanguageImportHelper(mpsProject);

    importHelper.setOnCloseActivity(
      new Runnable() {
        @Override
        public void run() {
          ModelAccess modelAccess = repository.getModelAccess();
          boolean noImportHasBeenAdded = new ModelAccessHelper(modelAccess).runReadAction(new Computable<Boolean>() {
            @Override
            public Boolean compute() {
              return ((SModelInternal) model).importedLanguageIds().isEmpty();
            }
          });
          if (noImportHasBeenAdded) {
            // was cancelled
            // todo have better way to signal cancellation
            // fixme DefaultModelRoot currently registers model when created, which is bad
            // hence, here we have to deregister it in case of cancellation
            // another solution is to extend api of LanguageImportHelper and have means to choose language
            // _before_ creating the model
            modelAccess.runWriteAction(new Runnable() {
              @Override
              public void run() {
                ((SModuleBase) model.getModule()).unregisterModel((SModelBase) model);
              }
            });
            return;
          }

          // writing file only now, this way VCS dialog doesn't get in the way and make the language chooser disappear
          modelAccess.runWriteAction(new Runnable() {
            @Override
            public void run() {
              model.save();
            }
          });

          ProjectView.getInstance(myProject).refresh();
        }
      }
    ).addUsedLanguage(model);
  }

  private EditableSModel createModel(final AnActionEvent e) {
    return new ModelAccessHelper(ProjectHelper.getModelAccess(myProject)).executeCommand(() -> {
      EditableSModel model = null;
      try {
        SModelName newModelName = new SModelName(myModelPrefix);
        PsiDirectory psiDir = (PsiDirectory) e.getData(LangDataKeys.PSI_ELEMENT);
        if (psiDir == null) {
          throw new IllegalStateException("Could not find psi directory in the context");
        }
        VirtualFile targetFile = psiDir.getVirtualFile();
        DataSourceFactoryFromName dataSourceFactory = createDataSourceFactory(targetFile);
        ModelFactory modelFactory = ModelFactoryService.getInstance().getFactoryByType(PreinstalledModelFactoryTypes.PER_ROOT_XML);
        model = (EditableSModel) myModelRoot.createModel(newModelName, mySourceRoot, dataSourceFactory, modelFactory);
      } catch (ModelCannotBeCreatedException ex) {
        LOG.error("", ex);
        return null;
      }

      model.setChanged(true);
      model.load();
      model.save();

      //TODO: This methods are from SModuleOperations.createModelWithAdjustments. Need to check them really needed.
//        ModelsAutoImportsManager.doAutoImport(myModelRoot.getModule(), model);
//        new MissingDependenciesFixer(model).fixModuleDependencies();

      return model;
    });
  }

  @NotNull
  private DataSourceFactoryFromName createDataSourceFactory(VirtualFile targetFile) {
    return new DataSourceFactoryFromName() {
      @NotNull
      @Override
      public DataSourceType getType() {
        return PreinstalledDataSourceTypes.MODEL_ROOT;
      }

      @NotNull
      @Override
      public DataSource create(@NotNull SModelName modelName, @NotNull SourceRoot sourceRoot, @Nullable ModelRoot modelRoot) {
        IFile folder = VirtualFileUtils.toIFile(targetFile);
        return new FilePerRootDataSource(folder, modelRoot);
      }
    };
  }

  @Override
  protected boolean isEnabled(AnActionEvent e) {
    if (!super.isEnabled(e)) {
      return false;
    }
    PsiElement psiElement = e.getData(LangDataKeys.PSI_ELEMENT);
    if (psiElement == null || !(psiElement instanceof PsiDirectory)) {
      return false;
    }
    boolean modelExists = MPSCommonDataKeys.CONTEXT_MODEL.getData(e.getDataContext()) != null;
    return myModelPrefix != null && !myModelPrefix.isEmpty() && !modelExists;
  }
}
