/*
 * Copyright 2000-2014 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 com.jetbrains.python.refactoring.classes.membersManager.vp;

import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.PyMemberInfoStorage;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Collection;

/**
 * All presenters that use members inherits this class.
 * <strong>Warning</strong>: Do not inherit it directly.
 * Check {@link MembersBasedPresenterNoPreviewImpl}
 * or {@link MembersBasedPresenterWithPreviewImpl} instead
 *
 * @param <T> view for that presenter
 * @param <M> Type of model {@link #myModel}
 * @author Ilya.Kazakevich
 */
abstract class MembersBasedPresenterImpl<T extends MembersBasedView<?>,
  M extends MemberInfoModel<PyElement, PyMemberInfo<PyElement>>> implements MembersBasedPresenter {
  @NotNull
  protected final T myView;
  @NotNull
  protected final PyClass myClassUnderRefactoring;
  @NotNull
  protected final PyMemberInfoStorage myStorage;
  /**
   * Member model
   */
  @NotNull
  protected final M myModel;

  /**
   * @param view                  View for presenter
   * @param classUnderRefactoring class to be refactored
   * @param infoStorage           info storage
   * @param model                 Member model (to be used for dependencies checking)
   */
  MembersBasedPresenterImpl(@NotNull final T view,
                            @NotNull final PyClass classUnderRefactoring,
                            @NotNull final PyMemberInfoStorage infoStorage,
                            @NotNull final M model) {
    myView = view;
    myClassUnderRefactoring = classUnderRefactoring;
    myStorage = infoStorage;
    myModel = model;
  }

  //TODO: Mark Async ?
  @Override
  public void okClicked() {
    final MultiMap<PyClass, PyMemberInfo<?>> conflicts = getConflicts();
    final Collection<PyMemberInfo<?>> dependencyConflicts = new ArrayList<>();
    for (final PyMemberInfo<PyElement> memberInfo : myStorage.getClassMemberInfos(myClassUnderRefactoring)) {
      if (myModel.checkForProblems(memberInfo) != MemberInfoModel.OK) {
        dependencyConflicts.add(memberInfo);
      }
    }

    if ((conflicts.isEmpty() && dependencyConflicts.isEmpty()) || myView.showConflictsDialog(conflicts, dependencyConflicts)) {
      try {
        validateView();
        doRefactor();
      }
      catch (final BadDataException e) {
        myView.showError(e.getMessage()); //Show error message if presenter says view in invalid
      }
    }
  }

  /**
   * Validates view (used by presenter to check if view is valid).
   * When overwrite, <strong>always</strong> call "super" <strong>first</strong>!
   * Throw {@link BadDataException} in case of error.
   * Do nothing, otherwise.
   * Method is designed to be overwritten and exception is used to simplify this process: children do not need parent's result.
   * They just call super.
   *
   */
  protected void validateView() throws BadDataException {
    if (myView.getSelectedMemberInfos().isEmpty()) {
      throw new BadDataException(RefactoringBundle.message("no.members.selected"));
    }
  }

  /**
   * Does refactoring itself
   */
  abstract void doRefactor();

  /**
   * Checks if one of destination classes already has members that should be moved, so conflict would take place.
   *
   * @return map of conflicts (if any)
   * @see #getDestClassesToCheckConflicts()
   */
  @NotNull
  protected final MultiMap<PyClass, PyMemberInfo<?>> getConflicts() {
    final MultiMap<PyClass, PyMemberInfo<?>> result = new MultiMap<>();
    final Collection<PyMemberInfo<PyElement>> memberInfos = myView.getSelectedMemberInfos();
    for (final PyClass destinationClass : getDestClassesToCheckConflicts()) {
      for (final PyMemberInfo<PyElement> pyMemberInfo : memberInfos) {
        if (pyMemberInfo.hasConflict(destinationClass)) {
          result.putValue(destinationClass, pyMemberInfo);
        }
      }
    }
    return result;
  }

  /**
   * @return classes where this refactoring will move members. To be used to check for conflicts (if one of target classes already has members)
   * @see #getConflicts()
   */
  @NotNull
  protected abstract Iterable<? extends PyClass> getDestClassesToCheckConflicts();
}
