/*
 * 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.ui;

import com.sun.javadoc.Doc;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Scope;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ItemEvent;
import java.io.IOException;
import javax.lang.model.element.Element;
import javax.lang.model.type.TypeMirror;
import javax.swing.*;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Position;
import org.netbeans.api.editor.DialogBinding;
import org.netbeans.api.java.source.CancellableTask;
import org.netbeans.api.java.source.CodeStyle;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.editor.Utilities;
import org.netbeans.modules.refactoring.java.RefactoringModule;
import org.netbeans.modules.refactoring.java.plugins.JavaPluginUtils;
import org.netbeans.modules.refactoring.java.ui.ChangeParametersPanel.Javadoc;
import org.netbeans.modules.refactoring.spi.ui.CustomRefactoringPanel;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;

/**
 * @author Jan Becicka
 */
public class IntroduceParameterPanel extends JPanel implements CustomRefactoringPanel {
    private static final String OVERLOADMETHOD = "overloadmethod.introduceParameter"; // NOI18N
    private static final String REPLACEALL = "replaceall.introduceParameter"; // NOI18N
    private static final String DECLAREFINAL = "declarefinal.introduceParameter"; // NOI18N
    private static final String UPDATEJAVADOC = "updateJavadoc.introduceParameters"; // NOI18N
    private static final String GENJAVADOC = "generateJavadoc.introduceParameters"; // NOI18N
    private static final String MIME_JAVA = "text/x-java"; // NOI18N
    private static final String DEFAULT_NAME = "par"; // NOI18N

    TreePathHandle refactoredObj;
    private ChangeListener parent;
    private final JComponent[] singleLineEditor;
    private final DocumentListener nameChangedListener;
    private int startOffset;
    
    @Override
    public Component getComponent() {
        return this;
    }


    /** Creates new form ChangeMethodSignature */
    public IntroduceParameterPanel(TreePathHandle refactoredObj, final ChangeListener parent) {
        this.refactoredObj = refactoredObj;
        this.parent = parent;
        singleLineEditor = Utilities.createSingleLineEditor(MIME_JAVA);
        initComponents();
        nameChangedListener = new DocumentListener() {

            @Override
            public void insertUpdate(DocumentEvent de) {
                postUpdate();
            }

            @Override
            public void removeUpdate(DocumentEvent de) {
                postUpdate();
            }

            @Override
            public void changedUpdate(DocumentEvent de) {
                postUpdate();
            }
        };
        ((JEditorPane) singleLineEditor[1]).getDocument().addDocumentListener(nameChangedListener);
    }

    private void postUpdate() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                parent.stateChanged(null);
            }
        });
    }
    
    private boolean initialized = false;
    @Override
    public void initialize() {
        try {
            if (initialized) {
                return;
            }
            JavaSource source = JavaSource.forFileObject(refactoredObj.getFileObject());
            source.runUserActionTask(new CancellableTask<CompilationController>() {
                
                @Override
                public void run(org.netbeans.api.java.source.CompilationController info) {
                    try {
                        info.toPhase(org.netbeans.api.java.source.JavaSource.Phase.RESOLVED);
                        
                        final FileObject fileObject = refactoredObj.getFileObject();
                        DataObject dob = DataObject.find(fileObject);
                        ((JEditorPane)singleLineEditor[1]).getDocument().putProperty(
                                Document.StreamDescriptionProperty,
                                dob);
                        final TreePath path = refactoredObj.resolve(info);
                        
                        final TreePath methodPath = JavaPluginUtils.findMethod(path);
                        MethodTree methodTree = (MethodTree) methodPath.getLeaf();
                        final int[] parameterSpan = info.getTreeUtilities().findMethodParameterSpan(methodTree);
                        TypeMirror typeMirror = info.getTrees().getTypeMirror(path);
                        final String tm = typeMirror == null ? "Object" : typeMirror.toString();
                        
                        Element methodElement = info.getTrees().getElement(methodPath);
                        Doc javadocDoc = info.getElementUtilities().javaDocFor(methodElement);
                        if(javadocDoc != null && javadocDoc.commentText() == null || javadocDoc.getRawCommentText().equals("")) {
                            chkGenJavadoc.setEnabled(true);
                            chkGenJavadoc.setVisible(true);
                            chkUpdateJavadoc.setVisible(false);
                        } else {
                            chkUpdateJavadoc.setEnabled(true);
                            chkUpdateJavadoc.setVisible(true);
                            chkGenJavadoc.setVisible(false);
                        }
                                                
                        String name = JavaPluginUtils.getName(path.getLeaf());
                        if (name == null) {
                            name = DEFAULT_NAME;
                        }
                        
                        Scope scope =  null;
                        if(methodTree.getBody() != null) {
                            TreePath bodyPath = new TreePath(methodPath, methodTree.getBody());
                            scope = info.getTrees().getScope(bodyPath);
                        }
                        CodeStyle cs;
                        Document doc = info.getDocument();
                        if(doc != null) {
                            cs = CodeStyle.getDefault(doc);
                        } else {
                            cs = CodeStyle.getDefault(info.getFileObject());
                        }
                        final String parameterName = JavaPluginUtils.makeNameUnique(info, scope, name, cs.getParameterNamePrefix(), cs.getParameterNameSuffix());
                        final boolean variableRewrite = path.getLeaf().getKind() == Tree.Kind.VARIABLE;
                        
                        SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                final JEditorPane editorPane = (JEditorPane)singleLineEditor[1];
                                
                                DialogBinding.bindComponentToFile(fileObject, parameterSpan[0] + 1, parameterSpan[1] - parameterSpan[0], editorPane);
                                editorPane.setText(tm + " " + parameterName); //NOI18N
                                startOffset = tm.length() + 1;
                                int endOffset = parameterName.length() + startOffset;
                                editorPane.select(startOffset, endOffset);
                                try {
                                    Position startPos = editorPane.getDocument().createPosition(startOffset);
                                    Position endPos = editorPane.getDocument().createPosition(endOffset);
                                    editorPane.putClientProperty("document-view-start-position", startPos); //NOI18N
                                    editorPane.putClientProperty("document-view-end-position", endPos); //NOI18N
                                } catch (BadLocationException ex) {
                                    Exceptions.printStackTrace(ex);
                                }

                                ((JEditorPane)singleLineEditor[1]).getDocument().addDocumentListener(nameChangedListener);
                                if(variableRewrite) {
                                    chkIsReplaceAll.setEnabled(false);
                                    chkIsReplaceAll.setSelected(false);
                                }
                                initialized = true;
                            }});
                    }
                    catch (IOException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }

                @Override
                public void cancel() {
                }
            }, true);
            initialized = true;
        }
        catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        bgOverloadMethod = new javax.swing.ButtonGroup();
        lblName = new javax.swing.JLabel();
        chkIsDeclareFinal = new javax.swing.JCheckBox();
        chkIsReplaceAll = new javax.swing.JCheckBox();
        jScrollPane1 = (JScrollPane)singleLineEditor[0];
        chkGenJavadoc = new javax.swing.JCheckBox();
        chkUpdateJavadoc = new javax.swing.JCheckBox();
        rbUpdateMethod = new javax.swing.JRadioButton();
        rbOverloadMethod = new javax.swing.JRadioButton();
        lblCodeGeneration = new javax.swing.JLabel();

        setBorder(javax.swing.BorderFactory.createEmptyBorder(12, 12, 11, 11));
        setAutoscrolls(true);
        setName(getString("LBL_TitleIntroduceParameter"));

        lblName.setLabelFor(jScrollPane1);
        org.openide.awt.Mnemonics.setLocalizedText(lblName, org.openide.util.NbBundle.getMessage(IntroduceParameterPanel.class, "IntroduceParameterPanel.lblName.text")); // NOI18N

        chkIsDeclareFinal.setSelected(((Boolean) RefactoringModule.getOption(DECLAREFINAL, Boolean.FALSE)).booleanValue());
        org.openide.awt.Mnemonics.setLocalizedText(chkIsDeclareFinal, org.openide.util.NbBundle.getMessage(IntroduceParameterPanel.class, "IntroduceParameterPanel.chkIsDeclareFinal.text")); // NOI18N
        chkIsDeclareFinal.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                chkIsDeclareFinalItemStateChanged(evt);
            }
        });

        chkIsReplaceAll.setSelected(((Boolean) RefactoringModule.getOption(REPLACEALL, Boolean.FALSE)).booleanValue());
        org.openide.awt.Mnemonics.setLocalizedText(chkIsReplaceAll, org.openide.util.NbBundle.getMessage(IntroduceParameterPanel.class, "IntroduceParameterPanel.chkIsReplaceAll.text")); // NOI18N
        chkIsReplaceAll.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                chkIsReplaceAllItemStateChanged(evt);
            }
        });

        chkGenJavadoc.setSelected(((Boolean) RefactoringModule.getOption(GENJAVADOC, Boolean.FALSE)).booleanValue());
        org.openide.awt.Mnemonics.setLocalizedText(chkGenJavadoc, org.openide.util.NbBundle.getMessage(IntroduceParameterPanel.class, "IntroduceParameterPanel.chkGenJavadoc.text")); // NOI18N
        chkGenJavadoc.setEnabled(false);
        chkGenJavadoc.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                chkGenJavadocItemStateChanged(evt);
            }
        });

        chkUpdateJavadoc.setSelected(((Boolean) RefactoringModule.getOption(UPDATEJAVADOC, Boolean.FALSE)).booleanValue());
        org.openide.awt.Mnemonics.setLocalizedText(chkUpdateJavadoc, org.openide.util.NbBundle.getMessage(IntroduceParameterPanel.class, "IntroduceParameterPanel.chkUpdateJavadoc.text")); // NOI18N
        chkUpdateJavadoc.setEnabled(false);
        chkUpdateJavadoc.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                chkUpdateJavadocItemStateChanged(evt);
            }
        });

        bgOverloadMethod.add(rbUpdateMethod);
        rbUpdateMethod.setSelected(!((Boolean) RefactoringModule.getOption(OVERLOADMETHOD, Boolean.FALSE)).booleanValue());
        org.openide.awt.Mnemonics.setLocalizedText(rbUpdateMethod, org.openide.util.NbBundle.getMessage(IntroduceParameterPanel.class, "IntroduceParameterPanel.rbUpdateMethod.text")); // NOI18N

        bgOverloadMethod.add(rbOverloadMethod);
        rbOverloadMethod.setSelected(((Boolean) RefactoringModule.getOption(OVERLOADMETHOD, Boolean.FALSE)).booleanValue());
        org.openide.awt.Mnemonics.setLocalizedText(rbOverloadMethod, org.openide.util.NbBundle.getMessage(IntroduceParameterPanel.class, "IntroduceParameterPanel.rbOverloadMethod.text")); // NOI18N
        rbOverloadMethod.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                rbOverloadMethodItemStateChanged(evt);
            }
        });

        org.openide.awt.Mnemonics.setLocalizedText(lblCodeGeneration, org.openide.util.NbBundle.getMessage(IntroduceParameterPanel.class, "IntroduceParameterPanel.lblCodeGeneration.text")); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(lblName)
                .addGap(2, 2, 2)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 428, Short.MAX_VALUE))
            .addComponent(chkIsDeclareFinal)
            .addComponent(chkIsReplaceAll)
            .addComponent(chkUpdateJavadoc)
            .addComponent(chkGenJavadoc)
            .addComponent(lblCodeGeneration)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(rbUpdateMethod))
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(rbOverloadMethod))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addComponent(lblName, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(jScrollPane1))
                .addGap(18, 18, 18)
                .addComponent(chkIsDeclareFinal)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(chkIsReplaceAll)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(chkUpdateJavadoc)
                    .addComponent(chkGenJavadoc))
                .addGap(18, 18, 18)
                .addComponent(lblCodeGeneration)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(rbUpdateMethod)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(rbOverloadMethod)
                .addContainerGap(24, Short.MAX_VALUE))
        );
    }// </editor-fold>//GEN-END:initComponents

    private void chkIsReplaceAllItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkIsReplaceAllItemStateChanged
        Boolean b = evt.getStateChange() == ItemEvent.SELECTED ? Boolean.TRUE : Boolean.FALSE;
        RefactoringModule.setOption(REPLACEALL, b);
        parent.stateChanged(null);
    }//GEN-LAST:event_chkIsReplaceAllItemStateChanged

    private void chkIsDeclareFinalItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkIsDeclareFinalItemStateChanged
        Boolean b = evt.getStateChange() == ItemEvent.SELECTED ? Boolean.TRUE : Boolean.FALSE;
        RefactoringModule.setOption(DECLAREFINAL, b);
        parent.stateChanged(null);
    }//GEN-LAST:event_chkIsDeclareFinalItemStateChanged

    private void chkGenJavadocItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkGenJavadocItemStateChanged
        Boolean b = evt.getStateChange() == ItemEvent.SELECTED ? Boolean.TRUE : Boolean.FALSE;
	RefactoringModule.setOption(GENJAVADOC, b); // NOI18N
	parent.stateChanged(null);
    }//GEN-LAST:event_chkGenJavadocItemStateChanged

    private void chkUpdateJavadocItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkUpdateJavadocItemStateChanged
        Boolean b = evt.getStateChange() == ItemEvent.SELECTED ? Boolean.TRUE : Boolean.FALSE;
	RefactoringModule.setOption(UPDATEJAVADOC, b); // NOI18N
	parent.stateChanged(null);
    }//GEN-LAST:event_chkUpdateJavadocItemStateChanged

    private void rbOverloadMethodItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_rbOverloadMethodItemStateChanged
        Boolean b = evt.getStateChange() == ItemEvent.SELECTED ? Boolean.TRUE : Boolean.FALSE;
	RefactoringModule.setOption(OVERLOADMETHOD, b);
	parent.stateChanged(null);
    }//GEN-LAST:event_rbOverloadMethodItemStateChanged
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.ButtonGroup bgOverloadMethod;
    private javax.swing.JCheckBox chkGenJavadoc;
    private javax.swing.JCheckBox chkIsDeclareFinal;
    private javax.swing.JCheckBox chkIsReplaceAll;
    private javax.swing.JCheckBox chkUpdateJavadoc;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JLabel lblCodeGeneration;
    private javax.swing.JLabel lblName;
    private javax.swing.JRadioButton rbOverloadMethod;
    private javax.swing.JRadioButton rbUpdateMethod;
    // End of variables declaration//GEN-END:variables

    private static String getString(String key) {
        return NbBundle.getMessage(ChangeParametersPanel.class, key);
    }

    public boolean isCompatible() {
        return rbOverloadMethod.isSelected();
    }

    public boolean isDeclareFinal() {
        return chkIsDeclareFinal.isSelected();
    }

    public boolean isReplaceAll() {
        return chkIsReplaceAll.isSelected();
    }
    
    public String getParameterName() {
        final String text = ((JEditorPane)singleLineEditor[1]).getText();
        final String substring;
        if(text.length() < startOffset) {
            // try to recover from not fully implemented document-view-start-position #204788
            substring = text;
        } else {
            substring = text.substring(startOffset);
        }
        return substring;
    }
    
    protected Javadoc getJavadoc() {
        if(chkUpdateJavadoc.isVisible() && chkUpdateJavadoc.isSelected()) {
            return Javadoc.UPDATE;
        } else if(chkGenJavadoc.isVisible() && chkGenJavadoc.isSelected()) {
            return Javadoc.GENERATE;
        } else {
            return Javadoc.NONE;
        }
    }

    @Override
    public boolean requestFocusInWindow() {
        ((JEditorPane)singleLineEditor[1]).requestFocusInWindow();
        return true;
    }
}
