/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.netbeans.modules.refactoring.java.plugins;

import com.sun.source.util.TreePath;
import java.io.IOException;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.ElementFilter;
import org.netbeans.api.java.source.CodeStyle;
import org.netbeans.api.java.source.CodeStyleUtils;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.Task;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.modules.refactoring.api.AbstractRefactoring;
import org.netbeans.modules.refactoring.api.Problem;
import org.netbeans.modules.refactoring.api.RenameRefactoring;
import org.netbeans.modules.refactoring.java.RefactoringUtils;
import org.netbeans.modules.refactoring.java.spi.JavaRefactoringPlugin;
import org.netbeans.modules.refactoring.java.ui.JavaRenameProperties;
import org.netbeans.modules.refactoring.spi.RefactoringElementsBag;
import org.openide.util.Exceptions;
import org.openide.util.lookup.Lookups;

/**
 *
 * @author Jan Becicka
 */
public class RenamePropertyRefactoringPlugin extends JavaRefactoringPlugin {

    private RenameRefactoring refactoring;
    private TreePathHandle property;
    private CodeStyle codeStyle;
    private boolean isStatic;
    private boolean isBoolean;
    private RenameRefactoring getterDelegate;
    private RenameRefactoring setterDelegate;
    private RenameRefactoring parameterDelegate;

    /** Creates a new instance of RenamePropertyRefactoringPlugin */
    public RenamePropertyRefactoringPlugin(RenameRefactoring rename) {
        this.refactoring = rename;
        property = rename.getRefactoringSource().lookup(TreePathHandle.class);
    }

    @Override
    protected JavaSource getJavaSource(Phase phase) {
        return JavaSource.forFileObject(property.getFileObject());
    }

    @Override
    public Problem checkParameters() {
        if (!isRenameProperty()) {
            return null;
        }

        initDelegates();

        Problem p = null;
        if (getterDelegate != null) {
            p = JavaPluginUtils.chainProblems(p, getterDelegate.checkParameters());
            if (p != null && p.isFatal()) {
                return p;
            }
        }
        if (setterDelegate != null) {
            p = JavaPluginUtils.chainProblems(p, setterDelegate.checkParameters());
            if (p != null && p.isFatal()) {
                return p;
            }
        }
        if (parameterDelegate != null) {
            p = JavaPluginUtils.chainProblems(p, parameterDelegate.checkParameters());
            if (p != null && p.isFatal()) {
                return p;
            }
        }
        return p = JavaPluginUtils.chainProblems(p, super.checkParameters());
    }

    @Override
    protected Problem fastCheckParameters(CompilationController info) throws IOException {
        if (!isRenameProperty()) {
            return null;
        }
        initDelegates();
        
        info.toPhase(JavaSource.Phase.RESOLVED);
        Element el = property.resolveElement(info);
        if (el == null || el.getKind() != ElementKind.FIELD) {
            return null;
        }
        String oldName = el.getSimpleName().toString();
        String bareName = RefactoringUtils.removeFieldPrefixSuffix(el, codeStyle);

        boolean isStatic = el.getModifiers().contains(Modifier.STATIC);
        String bareNewName = CodeStyleUtils.removePrefixSuffix(refactoring.getNewName(),
                isStatic ? codeStyle.getStaticFieldNamePrefix() : codeStyle.getFieldNamePrefix(),
                isStatic ? codeStyle.getStaticFieldNameSuffix() : codeStyle.getFieldNameSuffix());
        
        if (bareName.equals(bareNewName)) {
            return null;
        }
        
        Problem p = null;
        JavaRenameProperties renameProps = refactoring.getContext().lookup(JavaRenameProperties.class);
        boolean saveNoChange = false;
        if (renameProps != null) {
            saveNoChange = renameProps.isNoChangeOK();
            renameProps.setNoChangeOK(true);
        }
        try {
            if (getterDelegate != null) {
                String gettername = CodeStyleUtils.computeGetterName(
                                        refactoring.getNewName(), isBoolean, isStatic, codeStyle);
                getterDelegate.setNewName(gettername);
                p = JavaPluginUtils.chainProblems(p, getterDelegate.fastCheckParameters());
                if (p != null && p.isFatal()) {
                    return p;
                }
            }
            if (setterDelegate != null) {
                String settername = CodeStyleUtils.computeSetterName(
                                        refactoring.getNewName(), isStatic, codeStyle);
                setterDelegate.setNewName(settername);
                p = JavaPluginUtils.chainProblems(p, setterDelegate.fastCheckParameters());
                if (p != null && p.isFatal()) {
                    return p;
                }
            }
            if (parameterDelegate != null) {
                String newParam = RefactoringUtils.addParamPrefixSuffix(
                                CodeStyleUtils.removePrefixSuffix(
                                refactoring.getNewName(),
                                isStatic ? codeStyle.getStaticFieldNamePrefix() : codeStyle.getFieldNamePrefix(),
                                isStatic ? codeStyle.getStaticFieldNameSuffix() : codeStyle.getFieldNameSuffix()), codeStyle);
                parameterDelegate.setNewName(newParam);
                p = JavaPluginUtils.chainProblems(p, parameterDelegate.fastCheckParameters());
                if (p != null && p.isFatal()) {
                    return p;
                }
            }
        } finally {
            if (renameProps != null) {
                renameProps.setNoChangeOK(saveNoChange);
            }
        }
        return p = JavaPluginUtils.chainProblems(p, super.fastCheckParameters(info));
    }

    @Override
    protected Problem preCheck(CompilationController javac) throws IOException {
        if (!isRenameProperty()) {
            return null;
        }
        initDelegates();
        Problem p = null;
        if (getterDelegate != null) {
            p = JavaPluginUtils.chainProblems(p, getterDelegate.preCheck());
            if (p != null && p.isFatal()) {
                return p;
            }
        }
        if (setterDelegate != null) {
            p = JavaPluginUtils.chainProblems(p, setterDelegate.preCheck());
            if (p != null && p.isFatal()) {
                return p;
            }
        }
        if (parameterDelegate != null) {
            p = JavaPluginUtils.chainProblems(p, parameterDelegate.preCheck());
            if (p != null && p.isFatal()) {
                return p;
            }
        }
        return p = JavaPluginUtils.chainProblems(p, super.preCheck(javac));
    }

    @Override
    public Problem prepare(RefactoringElementsBag reb) {
        if (!isRenameProperty()) {
            return null;
        }
        initDelegates();
        fireProgressListenerStart(AbstractRefactoring.PREPARE, 3);
        Problem p = null;
        if (getterDelegate != null) {
            p = JavaPluginUtils.chainProblems(p, getterDelegate.prepare(reb.getSession()));
            if (p != null && p.isFatal()) {
                return p;
            }
        }
        fireProgressListenerStep();
        if (setterDelegate != null) {
            p = JavaPluginUtils.chainProblems(p, setterDelegate.prepare(reb.getSession()));
            if (p != null && p.isFatal()) {
                return p;
            }
        }
        fireProgressListenerStep();
        if (parameterDelegate != null) {
            p = JavaPluginUtils.chainProblems(p, parameterDelegate.prepare(reb.getSession()));
            if (p != null && p.isFatal()) {
                return p;
            }
        }
        fireProgressListenerStep();

        fireProgressListenerStop();

        return p;
    }

    private boolean isRenameProperty() {
        JavaRenameProperties renameProps = refactoring.getContext().lookup(JavaRenameProperties.class);
        if (renameProps != null && renameProps.isIsRenameGettersSetters()) {
            return true;
        }
        return false;
    }

    private boolean inited = false;

    private void initDelegates() {
        if (inited) {
            return;
        }
        try {
            getJavaSource(Phase.PREPARE).runUserActionTask(new Task<CompilationController>() {

                @Override
                public void run(CompilationController p) throws Exception {
                    JavaRenameProperties renameProps = refactoring.getContext().lookup(JavaRenameProperties.class);
                    
                    p.toPhase(JavaSource.Phase.RESOLVED);
                    codeStyle = RefactoringUtils.getCodeStyle(p);
                    Element propertyElement = property.resolveElement(p);
                    isStatic = propertyElement.getModifiers().contains(Modifier.STATIC);
                    isBoolean = propertyElement.asType().getKind() == TypeKind.BOOLEAN;
                    String propName = RefactoringUtils.removeFieldPrefixSuffix(propertyElement, codeStyle);
                    String paramName = RefactoringUtils.addParamPrefixSuffix(propName, codeStyle);
                    String newParam = RefactoringUtils.addParamPrefixSuffix(
                            CodeStyleUtils.removePrefixSuffix(
                            refactoring.getNewName(),
                            isStatic ? codeStyle.getStaticFieldNamePrefix() : codeStyle.getFieldNamePrefix(),
                            isStatic ? codeStyle.getStaticFieldNameSuffix() : codeStyle.getFieldNameSuffix()), codeStyle);
                    for (ExecutableElement el : ElementFilter.methodsIn(propertyElement.getEnclosingElement().getEnclosedElements())) {
                        TreePath elPath = p.getTrees().getPath(el);
                        if(elPath == null || p.getTreeUtilities().isSynthetic(elPath)) {
                            continue;
                        } else if (RefactoringUtils.isGetter(p, el, propertyElement)) {
                            getterDelegate = new RenameRefactoring(Lookups.singleton(TreePathHandle.create(el, p)));
                            getterDelegate.getContext().add(renameProps);
                            String gettername = CodeStyleUtils.computeGetterName(
                                    refactoring.getNewName(), isBoolean, isStatic, codeStyle);
                            getterDelegate.setNewName(gettername);
                            getterDelegate.setSearchInComments(refactoring.isSearchInComments());
                        } else if (RefactoringUtils.isSetter(p, el, propertyElement)) {
                            setterDelegate = new RenameRefactoring(Lookups.singleton(TreePathHandle.create(el, p)));
                            setterDelegate.getContext().add(renameProps);
                            String settername = CodeStyleUtils.computeSetterName(
                                    refactoring.getNewName(), isStatic, codeStyle);
                            setterDelegate.setNewName(settername);
                            setterDelegate.setSearchInComments(refactoring.isSearchInComments());
                            VariableElement par = el.getParameters().iterator().next();
                            if (par.getSimpleName().contentEquals(paramName)) {
                                parameterDelegate = new RenameRefactoring(Lookups.singleton(TreePathHandle.create(p.getTrees().getPath(par), p)));
                                parameterDelegate.getContext().add(RenamePropertyRefactoringPlugin.this);
                                parameterDelegate.setNewName(newParam);
                                parameterDelegate.setSearchInComments(refactoring.isSearchInComments());
                                parameterDelegate.getContext().add(renameProps);
                            }
                        }
                    }
                }
            }, true);
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }
        inited = true;
    }
}