/*
 * Copyright 2000-2009 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.intellij.refactoring.introduceparameterobject;

import com.intellij.ide.util.SuperMethodWarningUtil;
import com.intellij.lang.ContextAwareActionHandler;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.ScrollingModel;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.RefactorJBundle;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import org.jetbrains.annotations.NotNull;

public class IntroduceParameterObjectHandler implements RefactoringActionHandler, ContextAwareActionHandler {
  @Override
  public boolean isAvailableForQuickList(@NotNull Editor editor, @NotNull PsiFile file, @NotNull DataContext dataContext) {
    final PsiMethod selectedMethod = getSelectedMethod(editor, file, dataContext);
    if (selectedMethod != null) {
      final PsiMethod[] deepestSuperMethods = selectedMethod.findDeepestSuperMethods();
      return deepestSuperMethods.length > 0 || getErrorMessage(selectedMethod) == null;
    }
    return false;
  }

  @Override
  public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
    final ScrollingModel scrollingModel = editor.getScrollingModel();
    scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE);
    PsiMethod selectedMethod = getSelectedMethod(editor, file, dataContext);
    if (selectedMethod == null) {
      final String message = RefactorJBundle.message("cannot.perform.the.refactoring") +
                             RefactorJBundle.message("the.caret.should.be.positioned.at.the.name.of.the.method.to.be.refactored");
      CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HelpID.IntroduceParameterObject);
      return;
    }
    invoke(project, selectedMethod, editor);
  }

  private static PsiMethod getSelectedMethod(Editor editor, PsiFile file, DataContext dataContext) {
    final PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
    PsiMethod selectedMethod = null;
    if (element instanceof PsiMethod) {
      selectedMethod = (PsiMethod)element;
    }
    else if (element instanceof PsiParameter && ((PsiParameter)element).getDeclarationScope() instanceof PsiMethod){
      selectedMethod = (PsiMethod)((PsiParameter)element).getDeclarationScope();
    }
    else {
      final CaretModel caretModel = editor.getCaretModel();
      final int position = caretModel.getOffset();
      final PsiElement elementAt = file.findElementAt(position);
      final PsiMethodCallExpression methodCallExpression =
       PsiTreeUtil.getParentOfType(elementAt, PsiMethodCallExpression.class);
      if (methodCallExpression != null) {
        selectedMethod = methodCallExpression.resolveMethod();
      } else {
        final PsiParameterList parameterList = PsiTreeUtil.getParentOfType(elementAt, PsiParameterList.class);
        if (parameterList != null && parameterList.getParent() instanceof PsiMethod) {
          selectedMethod = (PsiMethod)parameterList.getParent();
        }
      }
    }
    return selectedMethod;
  }

  @Override
  public void invoke(@NotNull Project project, PsiElement @NotNull [] elements, DataContext dataContext) {
    if (elements.length != 1) {
      return;
    }
    final PsiMethod method = PsiTreeUtil.getParentOfType(elements[0], PsiMethod.class, false);
    if (method == null) {
      return;
    }
    Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
    invoke(project, method, editor);
  }

  private static void invoke(final Project project, final PsiMethod selectedMethod, Editor editor) {
    PsiMethod newMethod = SuperMethodWarningUtil.checkSuperMethod(selectedMethod, RefactoringBundle.message("to.refactor"));
    if (newMethod == null) return;
    final String message = getErrorMessage(newMethod);
    if (message != null) {
      CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), HelpID.IntroduceParameterObject);
      return;
    }
    if (!CommonRefactoringUtil.checkReadOnlyStatus(project, newMethod)) return;

    new IntroduceParameterObjectDialog(newMethod).show();
  }

  private static String getErrorMessage(PsiMethod newMethod) {
    final PsiParameter[] parameters = newMethod.getParameterList().getParameters();
    if (parameters.length == 0) {
     return RefactorJBundle.message("cannot.perform.the.refactoring") +
            RefactorJBundle.message("method.selected.has.no.parameters");
    }
    if (newMethod instanceof PsiCompiledElement) {
      return RefactorJBundle.message("cannot.perform.the.refactoring") +
             RefactorJBundle.message("the.selected.method.cannot.be.wrapped.because.it.is.defined.in.a.non.project.class");
    }
    return null;
  }

  private static String getRefactoringName() {
    return RefactorJBundle.message("introduce.parameter.object");
  }
}
